Есть несколько способов сделать это без с использованием изменяемой ячейки.Вы уже сделали это со второй попытки, только небольшая ошибка.Вам нужно передать начальное значение в функцию tick'
, а не «установить его» (haskell не имеет представления о присвоении переменных - только определения. Если появится строка x = y
, x
будет y
за всю свою жизнь).
tick = tick' 0
where ...
Линия counter = 0
ничего не делает;это определение имени, которое никогда не используется.counter
, используемый в функции tick'
, связан как один из ее аргументов (и скрывает тот, который определен как 0).Потратьте некоторое время, чтобы взглянуть на это с учетом этого, посмотрите, имеет ли это смысл.
Есть хороший способ «высшего порядка», которым мы тоже можем это сделать.По сути, мы хотим запустить бесконечно длинный блок кода:
do
print 0
print 1
print 2
...
Существует функция с именем sequence :: [IO a] -> IO [a]
(см. Предостережение ниже), которая возьмет список действий и создаст действие.Поэтому, если мы можем построить список [print 0, print 1, print 2, ...]
, тогда мы можем передать его в sequence
, чтобы построить бесконечно длинный блок, который мы ищем.
Обратите внимание, это очень важная концепция в Haskell: [print 0, print 1, print 2]
не печатает эти три числа, затем строит список [0,1,2]
.Вместо этого он представляет собой список действий , тип которых [IO ()]
.Составление списка ничего не делает;только когда вы привязываете действие к main
, оно будет выполнено.Например, мы можем сказать:
main = do
let xs = [putStrLn "hello", getLine >> putStrLn "world"]
xs !! 0
xs !! 0
xs !! 1
xs !! 1
xs !! 0
Это будет дважды печатать hello
, дважды получать строку и печатать world
после каждого, затем один раз печатать hello
снова.
С помощью этой концепции легко создать список действий [print 0, print 1, ...]
с пониманием списка:
main = sequence [ print x | x <- [0..] ]
Мы можем немного упростить:
main = sequence (map (\x -> print x) [0..])
main = sequence (map print [0..])
Так что map print [0..]
- этосписок действий [print 0, print 1, ...]
, которые мы искали, затем мы просто передаем это в sequence
, который объединяет их в цепочку.
Этот шаблон sequence
является общим и имеет свой собственный mapM
:
mapM :: (a -> IO b) -> [a] -> IO [b]
mapM f xs = sequence (map f xs)
Таким образом:
main = mapM print [0..]
Примерно так просто, как вы могли захотеть.
Одно замечание о производительности: поскольку мы не используем выходные данные этих функций, мыследует использовать sequence_
и mapM_
с последующим подчеркиванием, которые оптимизированы для этой цели.Обычно это не имеет значения в программе на Haskell из-за сборки мусора, но в данном конкретном случае это своего рода особый случай из-за различных тонкостей.Вы обнаружите, что без _
s использование памяти вашей программой постепенно возрастает, поскольку список результатов (в данном случае [(),(),(),...]
) создается, но никогда не используется.
Caveat : я дал сигнатуры типов sequence
и mapM
, специализированные для IO
, а не общей монады, так что читателю не нужно узнавать об ортогональных концепциях действий, имеющих типы и классы типов в одном и том жевремя.