императивный цикл в Haskell

Я пытаюсь понять систему монад в Haskell. Около 80% моего предыдущего опыта программирования находится в C, но по иронии судьбы императивная часть Haskell труднее всего понять. Список манипуляций и ленивая оценка были гораздо более ясными. В любом случае, я хочу, чтобы ghc принял этот код. Я знаю, что код вообще не имеет смысла. Наиболее очевидно, что я прохожу Bool здесь - это. Но это не единственная проблема. Я знаю, что это глупый вопрос, но пожалуйста, помогите мне чтобы углубить мое понимание языка Хаскелл.

import Control.Monad

while :: Monad m => m Bool -> m () -> m ()
while cond action = do
  c <- cond
  when c $ do
    action
    while cond action

main :: IO ()
main = do
  i <- 0
  while (i < 10) $ do
    i <- i + 1
    print i

вот как я, наконец, сделал это. Я знаю allocaArray не обязательно, но это было очень весело использовать. У Хаскелла действительно нет границ, Он очень силен.

import Control.Monad
import Data.IORef
import Foreign.Ptr
import Foreign.Storable
import Foreign.Marshal.Array

while :: Monad m => m Bool -> m () -> m ()
while cond action = do
  c <- cond
  if c then do
    action
    while cond action
  else return ()

main :: IO ()
main = do
  let n = 10
  allocaArray n $ p -> do
    i <- newIORef 0
    while (liftM (< n) (readIORef i)) $ do
      i2 <- readIORef i
      poke (advancePtr p i2) i2
      modifyIORef i (+ 1)
    writeIORef i 0
    while (liftM (< n) (readIORef i)) $ do
      i2 <- readIORef i
      (peek $ advancePtr p i2) >>= print
      modifyIORef i (+ 1)

3 ответов


есть две вещи, которые удерживают ваш код от проверки типа:

  1. код ждет IO Bool но вы даете ему i < 10 что является выражением типа Bool. Чтобы превратить Bool на IO Bool, просто использовать return.

  2. когда вы пишите i <- 0 вы пытаетесь использовать литеральный ноль в качестве монадического значения, которым он не является. Запомните это

    main = do
        i <- 0
        ...
    

    эквивалентно к

    main = 0 >>= \i -> do ...
    

чтобы исправить это, вы также можете продвигать 0 via return.

таким образом, вы в конечном итоге с

main :: IO ()
main = do
    i <- return 0
    while (return (i < 10)) $ do
        i <- return (i + 1)
        print i

однако это все равно не будет делать то, что вы собираетесь делать: причина в том, что первый (самый левый) i на i <- return (i + 1) is разные чем i на i <- return 0. Ты слежка переменная, создающая новую переменную с тем же именем, которую вы затем печатаете. Так что вам не на самом деле наткнуться на любой счетчик вообще.

Я не хочу портить удовольствие, но если вы действительно застряли: есть monad-loops пакет, который предоставляет пару полезных функций монадического цикла, включая whileM


проблема с этим подходом заключается в том, что i не является изменяемой переменной. Вы могли бы использовать IORef, однако более функциональным подходом было бы передать текущее состояние через каждую итерацию. Вы можете переписать свой whileM тело и условия, чтобы принять текущее значение:

whileM :: Monad m => (a -> Bool) -> (a -> m a) -> a -> m ()
whileM test act init =
   when (test init) $ (act init) >>= whileM test act

затем вы можете сделать

whileM (< 10) (\i -> print i >> return (i + 1)) 0

решение с локальным состоянием (состояние и связанный трансформатор монады), в отличие от глобального состояния (IORef и друзей):

import Control.Monad
import Control.Monad.State

while :: Monad m => m Bool -> m () -> m ()
while cond action = do
  c <- cond
  when c $ do
    action
    while cond action

main :: IO ()
main = do 
  runStateT (while cond body) 1 
  return ()

body :: StateT Integer IO ()
body = do
    x <- get
    liftIO $ print x
    put (x + 1)
    return ()

cond :: StateT Integer IO Bool
cond = do
    x <- get
    return (x < 10)

тело цикла и условие цикла явны и названы для ясности; можно написать, например,while (liftM (< 10) get) body.