Это просто запутывание, чтобы скрыть тот факт, что введенный номер используется в качестве счетчика. Я надеюсь, что если бы вы увидели нечто подобное, вы бы поняли, почему:
fib2 n = fastFib2 0 1 0 n
fastFib2 current previous count 0 = 0
fastFib2 current previous count 1 = 1
fastFib2 current previous count n
| count == n = current
| otherwise =
fastFib2 (current + previous) current (count + 1) n
В приведенном выше коде мы сделали счетчик явным: когда он равен нашему входу, n
, мы возвращаем наш аккумулятор, current
; в противном случае мы отслеживаем эту «прямую» рекурсию текущего и предыдущего чисел (« двух предыдущих »)), всего, что необходимо для построения последовательности Фибоначчи.
Код, которым вы поделились, делает то же самое. (c - 1)
делает его похожим на более традиционное «обратное» повторение, когда он фактически запускает аккумулятор при первом вызове, а затем добавляет к нему
.
Другие указали на проблему, заключающуюся в том, что step всегда оценивает свой второй аргумент, прежде чем вообще производить какой-либо вывод, но его второй аргумент в конечном итоге будет зависеть от результата другого вызова step, когда foldr применяется к бесконечному списку.
Это не обязательно должно быть написано таким образом, но ваша вторая версия выглядит некрасиво, потому что она полагается на исходный аргумент для step, имеющий определенный формат, и довольно трудно понять, что голова / хвост никогда не ошибется. (Я даже не уверен на 100%, что они этого не сделают!)
Что вам следует сделать, так это реструктурировать первую версию, чтобы она производила вывод независимо от входного списка, по крайней мере, в некоторых ситуациях. В частности, мы можем видеть, что когда символ не является пробелом, в списке вывода всегда есть хотя бы один элемент. Поэтому отложите сопоставление с образцом для второго аргумента до тех пор, пока не будет создан этот первый элемент. Случай, когда символом является пробел, по-прежнему будет зависеть от списка, но это нормально, потому что единственный способ, которым этот случай может бесконечно рекурсировать, - это передать бесконечный список пробелов, и в этом случае не производить никакого вывода и переходить в цикл - это ожидаемое поведение слов (что еще он мог сделать?)
Попробуйте развернуть выражение вручную:
take 5 (myWords_FailsOnInfiniteList (cycle "why "))
take 5 (foldr step [] (dropWhile charIsSpace (cycle "why ")))
take 5 (foldr step [] (dropWhile charIsSpace ("why " ++ cycle "why ")))
take 5 (foldr step [] ("why " ++ cycle "why "))
take 5 (step 'w' (foldr step [] ("hy " ++ cycle "why ")))
take 5 (step 'w' (step 'h' (foldr step [] ("y " ++ cycle "why "))))
Какое следующее расширение? Вы должны увидеть, что для сопоставления с образцом для шага
вам необходимо знать, пустой это список или нет. Чтобы это выяснить, нужно хотя бы немного его оценить. Но этот второй член оказывается сокращением foldr
самой функцией, для которой вы выполняете сопоставление с образцом. Другими словами, пошаговая функция не может смотреть на свои аргументы, не вызывая себя, и поэтому у вас бесконечная рекурсия.
Сравните это с расширением вашей второй функции:
myWords_anotherReader (cycle "why ")
foldr step [""] (cycle "why ")
foldr step [""] ("why " ++ cycle "why ")
step 'w' (foldr step [""] ("hy " ++ cycle "why ")
let result = foldr step [""] ("hy " ++ cycle "why ") in
['w':(head result)] ++ tail result
let result = step 'h' (foldr step [""] ("y " ++ cycle "why ") in
['w':(head result)] ++ tail result
Вы, вероятно, видите, что это расширение будет продолжаться, пока не будет достигнуто пространство. Как только пробел будет достигнут, «результат заголовка» получит значение, и вы получите первый элемент ответа.
Я подозреваю, что эта вторая функция переполнится для бесконечных строк, не содержащих пробелов. Вы понимаете, почему?
Вторая версия фактически не оценивает результат
до тех пор, пока после не начнет производить часть своего собственного ответа. Первая версия оценивает результат
немедленно путем сопоставления с ним.
Ключ к этим бесконечным спискам заключается в том, что вы должны создать что-то , прежде чем вы начнете требовать элементы списка, поэтому что выходные данные всегда могут «опережать» входные.
(мне кажется, что это объяснение не очень ясное, но это лучшее, что я могу сделать)
The library function foldr
has this implementation (or similar):
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f k (x:xs) = f x (foldr f k xs)
foldr _ k _ = k
The result of myWords_FailsOnInfiniteList
depends on the result of foldr
which depends on the result of step
which depends on the result of the inner foldr
which depends on ... and so on an infinite list, myWords_FailsOnInfiniteList
will use up an infinite amount of space and time before producing its first word.
The step
function in myWords_anotherReader
does not require the result of the inner foldr
until after it has produced the first letter of the first word. Unfortunately, as Apocalisp says, it uses O(length of first word) space before it produces the next word, because as the first word is being produced, the tail thunk keeps growing tail ([...] ++ tail ([...] ++ tail (...)))
.
In contrast, compare to
myWords :: String -> [String]
myWords = myWords' . dropWhile isSpace where
myWords' [] = []
myWords' string =
let (part1, part2) = break isSpace string
in part1 : myWords part2
using library functions which may be defined as
break :: (a -> Bool) -> [a] -> ([a], [a])
break p = span $ not . p
span :: (a -> Bool) -> [a] -> ([a], [a])
span p xs = (takeWhile p xs, dropWhile p xs)
takeWhile :: (a -> Bool) -> [a] -> [a]
takeWhile p (x:xs) | p x = x : takeWhile p xs
takeWhile _ _ = []
dropWhile :: (a -> Bool) -> [a] -> [a]
dropWhile p (x:xs) | p x = dropWhile p xs
dropWhile _ xs = xs
Notice that producing the intermediate results is never held up by future computation, and only O(1) space is needed as each element of the result is made available for consumption.
So, here's the revised code. I usually try to avoid head and tail, merely because they are partial functions, and also because I need practice writing the pattern matching equivalent.
myWords :: String -> [String] myWords string = foldr step [""] (dropWhile charIsSpace строка) где шаг пространство соотв | charIsSpace space = "": согласно шаг char (x: xs) = (char: x): xs step _ [] = ошибка «это должно быть невозможно»
(Aside: You may not care, but the words "" == []
from the library, but your myWords "" = [""]
. Similar issue with trailing spaces.)
Looks much-improved over myWords_anotherReader
, and is pretty good for a foldr
-based solution.
\n -> tail $ myWords $ replicate n 'a' ++ " b"
It's not possible to do better than O(n) time, but both myWords_anotherReader
and myWords
take O(n) space here. This may be inevitable given the use of foldr
.
Worse,
\n -> head $ head $ myWords $ replicate n 'a' ++ " b"
myWords_anotherReader
was O(1) but the new myWords
is O(n), because pattern matching (x:xs)
requires the further result.
You can work around this with
myWords :: String -> [String]
myWords = foldr step [""] . dropWhile isSpace
where
step space acc | isSpace space = "":acc
step char ~(x:xs) = (char:x):xs
The ~
introduces an "irrefutable pattern". Irrefutable patterns never fail and do not force immediate evaluation.