императивный цикл в 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 ответов
есть две вещи, которые удерживают ваш код от проверки типа:
код ждет
IO Bool
но вы даете емуi < 10
что является выражением типаBool
. Чтобы превратитьBool
наIO Bool
, просто использоватьreturn
.-
когда вы пишите
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
.