Понимание лени Хаскеля - PullRequest
2 голосов
/ 24 июня 2019

Я читал: Функция взаимодействия с Haskell

Итак, я попытался

interact (unlines . map (show . length) . lines)

И все заработало, как я и ожидал.Я набираю что-то, нажимаю ввод, затем я получаю напечатанную длину в приглашении.

Итак, я хотел попробовать заставить его просто повторить то, что я набрал, поэтому я попытался

interact (unlines . map id . lines)

Нотеперь он повторяет каждый символ, который я печатаю. Почему?Я думал, что трюк был в lines, за которым следует unlines - но это явно не так.lines "a" выдает ["a"], так почему же первая функция, когда я начинаю вводить свой ввод, не сразу дает «1» в качестве вывода?Очевидно, что я неправильно понимаю, что «Определение длины строки не таково - вся строка должна быть известна до того, как будет получен какой-либо вывод».

Ответы [ 2 ]

3 голосов
/ 24 июня 2019

Тот факт, что lines "a" производит ["a"], не означает, что если вы в данный момент вводите a, то lines просто обрабатывает ввод в список ["a"]. Вы должны увидеть ввод как (возможно) бесконечный список символов. Если запрос ожидает ввода пользователя, он блокирует следующий ввод.

Однако не означает, что такие функции, как lines, могут , а не , уже частично разрешают результат. lines реализован таким ленивым образом, что он обрабатывает поток символов, и каждый раз, когда он видит символ новой строки, он начинает испускать следующий элемент. Таким образом, это означает, что lines может обрабатывать бесконечную последовательность символов в бесконечном списке строк.

Однако если вы используете length :: Foldable f => f a -> Int, то для этого требуется оценка списка (однако не элементов списка). Это означает, что length будет отправлять ответ только с того момента, как lines начнет излучать следующий элемент.

Вы можете использовать seq (и варианты), чтобы форсировать оценку термина перед выполнением определенного действия. Например, seq :: a -> b -> b оценит первый параметр как Нормальная форма слабой головки (WHNF) , а затем вернет второй параметр.

На основе seq были созданы другие функции, такие как seqList :: [a] -> [a] в Data.Lists модуле из пакет lists.

Мы можем использовать это, чтобы отложить оценку до известной первой строки, например:

-- will echo full lines

import Data.Lists(seqList)

interact (unlines . map (\x -> seqList x x) . lines)
3 голосов
/ 24 июня 2019

Это связано с ленивой оценкой.Я попытаюсь объяснить это настолько интуитивно, насколько это возможно.

Когда вы пишете interact (unlines . map (show . length) . lines), каждый раз, когда вводится символ, мы фактически не знаем, каким может быть следующий выходной символ, пока вынажмите Ввод.Таким образом, вы получаете ожидаемое поведение.

Однако в каждой точке в interact (unlines . map id . lines) = interact id каждый раз, когда вы вводите символ, гарантируется, что этот символ будет включен в вывод.Таким образом, если вы вводите символ, этот символ также выводится немедленно.

Это одна из причин того, что слово «ленивый» немного неправильное.Это правда, что Haskell будет оценивать что-то только тогда, когда это необходимо, но оборотная сторона в том, что когда это нужно, он сделает это как можно скорее.Здесь Haskell нужно оценить вывод, так как вы хотите напечатать его, поэтому он оценивает его столько, сколько может - по одному символу за раз - по иронии судьбы, заставляя его казаться нетерпеливым!

В частности, interact isn 't предназначен для пользовательского ввода в реальном времени - он предназначен для ввода файлов, когда вы передаете файл в исполняемый файл с помощью bash.Это должно быть выполнено примерно так:

$ runhaskell Interactor.hs < my_big_file.txt > list_of_lengths.txt

Если вы хотите построчную буферизацию, вам, вероятно, придется делать это вручную, если вы не хотите «обманывать» компилятор, как это делает Виллем.Вот некоторый очень простой код, который работает так, как вы ожидаете, но обратите внимание, что у него нет состояния выхода в отличие от interact, который заканчивается в EOF.

main = do
  ln <- getLine -- Buffers until you press enter
  putStrLn ln   -- Print the line we just got
  main          -- Loop forever
...