fibs :: [Int]
fibs = 0 : 1 : [ a + b | (a, b) <- zip fibs (tail fibs)]
Это генерирует последовательность Фибоначчи.
Я понимаю поведение охранников, :
, zip
и tail
, но я не понимаю <-
. Что он здесь делает?
fibs :: [Int]
fibs = 0 : 1 : [ a + b | (a, b) <- zip fibs (tail fibs)]
Это генерирует последовательность Фибоначчи.
Я понимаю поведение охранников, :
, zip
и tail
, но я не понимаю <-
. Что он здесь делает?
Из-за повышений я сделал свой комментарий в ответ.
Что вы видите, это не страх, но это понимание списка. Для начала подумайте об этом как о способе выражения математического набора обозначений типа A = {x | x элемент N}, что означает что-то вдоль линий: Множество A является множеством всех натуральных чисел. В понимании списка будет [x | x <- [1..] ]
.
Вы также можете использовать ограничения на свои номера: [x | x <- [1..], x `mod` 2 == 0 ]
и многое другое.
Есть много хороших хакельских туринцев, которые освещают список и даже вопрос StackOverflow относительно ресурсов haskell.
Единственная сложная вещь - zip fibs (tail fibs)
. zip
просто делает попарный список из каждого из своих аргументов. Поэтому, если у вас есть два списка:
[ 1, 2, 3, 4 ]
[ "a", "b", "c", "d" ]
С помощью Zipping они сделают:
[ (1,"a"), (2,"b"), (3,"c"), (4,"d") ]
Левая стрелка (назначение в шаблон деструкции) просто извлекает парные элементы, чтобы их можно было добавить вместе. Два списка, зашифрованные, являются fibs
и (tail fibs)
- другими словами, последовательность Фибоначчи и последовательность Фибоначчи, смещенная на 1 элемент. Haskell лениво оценивается, поэтому он может рассчитать список, однако требуется множество элементов. Это также относится к почтовому индексу.
Разложим его.
zip
создает пары из содержимого двух списков. Итак, первая пара zip fibs (tail fibs)
дает нам (0, 1)
, которая добавляет до 1. Итак, теперь список [0,1,1]
. Теперь мы знаем три элемента в списке, поэтому понимание списка может продолжаться, захватывая следующий элемент из списка и следующий элемент из хвоста, который дает (1,1)
- добавляется вместе, делая 2. Затем мы получаем следующую пару, который равен (1,2)
, делая следующее число в последовательности 3. Это может продолжаться бесконечно, так как понимание всегда будет обеспечивать достаточное количество элементов.
Для чего это стоит, я считаю следующую версию более понятной:
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
Одним из преимуществ функционального программирования является то, что вы можете оценивать выражение вручную, как математическую задачу:
fibs = 0 : 1 : [ a + b | (a, b) <- zip fibs (tail fibs)]
= 0 : 1 : [ a + b | (a, b) <- zip [0, 1, ??] (tail [0, 1, ??])]
Здесь ??
- это часть, которая еще не была оценена. Мы заполним его, когда мы продолжим.
= 0 : 1 : [ a + b | (a, b) <- zip [0, 1, ??] [1, ??])]
= 0 : 1 : [ a + b | (a, b) <- (0, 1) : zip [1, ??] [??]]
Обратите внимание, что я возвращаю оценку zip
, так как ее определение здесь не приводится, и детали не актуальны для текущего вопроса. Это обозначение, которое я буду использовать для отображения каждой пары чисел, созданной zip
и потребляемой пониманием списка.
= 0 : 1 : 0+1 : [ a + b | (a, b) <- zip [1, ??] [??]]
= 0 : 1 : 1 : [ a + b | (a, b) <- zip [1, ??] [??]]
Теперь мы знаем, что следующий элемент в ??
равен 1
:
= 0 : 1 : 1 : [ a + b | (a, b) <- zip [1, 1, ??] [1, ??]]
= 0 : 1 : 1 : [ a + b | (a, b) <- (1, 1) : zip [1, ??] [??]]
= 0 : 1 : 1 : 1+1 : [ a + b | (a, b) <- zip [1, ??] [??]]
= 0 : 1 : 1 : 2 : [ a + b | (a, b) <- zip [1, ??] [??]]
И следующий элемент - это 2:
= 0 : 1 : 1 : 2 : [ a + b | (a, b) <- zip [1, 2, ??] [2, ??]]
Промойте и повторите.
Пояснение в скобках:
[ a + b | (a, b) <- zip fibs (tail fibs)]
возвращает список, содержащий вывод (a + b), где переменные a и b относятся к результату
zip fibs (tail fibs)
Он определяет эту диаграмму потоков
.----->>------->>----.
/ \
/ /
\ /
<---- 0 <---- 1 ---<<--- (+)
/ \
\ \
\ /
*---->>------*
который вытягивает новый вход из себя по мере его создания, но всегда на одну и две позиции перед точкой производства, поддерживая два "обратных указателя" в последовательности. Это отражено в определении fibs = 0:1:[ a+b | a <- fibs | b <- tail fibs]
, с пониманием параллельного списка (:set -XParallelListComp
и т.д.).
Поскольку он использует только свои последние два элемента, он эквивалентен
map fst . iterate (\(a, b) -> (b, a+b)) $ (0,1)
Я все еще не понимаю. Мне нравится этот ответ: fooobar.com/questions/512051/... (от code-apprentice).
но я не понимаю, как из этой строки:
= 0 : 1 : 1 : [ a + b | (a, b) <- zip [1, ??] [??]]
он переходит к следующему:
= 0 : 1 : 1 : [ a + b | (a, b) <- zip [1, 1, ??] [1, ??]]
и, кроме того, у меня есть что-то другое, что меня беспокоит:
как я могу использовать fib
внутри понимания списка, если у меня вообще нет fib
(так кажется, но уверен, что я ошибаюсь), потому что fib
еще не рассчитан.
он "ждет" (в левой части знака равенства) для вычисления в правой части (знака равенства).
Ключевым понятием здесь является ленивая оценка, которая означает, что если значение здесь, то возьмите его без дальнейших вычислений, скажите, что я получил значение и работа выполнена, мне не нужно вычислять будущую стоимость временно. а если значение недоступно, просто вычислите его и, конечно, оно ленивое, поэтому оно не будет мешать вычислению следующего необходимого значения.
я пишу другую реализацию, чтобы проиллюстрировать это, и использую ??
в качестве заполнителя значения, который должен быть вычислен при необходимости.
fibs = 1:1:[ y!!1 + y!!0 | x<-[2..], y <- [[last (take x fibs), last (take (x-1) fibs)]] ]
я использую x, чтобы указать количество доступных значений в fib (которое не нужно вычислять заново), а y - как [[last fib values]] вложенный список. его внутренний список содержит последние два значения доступных значений в FIBS.
так вот процесс вычисления:
[last (take 2 fibs), last (take 1 fibs)]
. Ленивая оценка позволяет нам просто принимать доступные значения и не беспокоиться о будущих ценностях. он говорит мне взять первые 2 и 1 значения FIBS, и значения прямо здесь 1,1 и 1. y теперь это [last [1,1], last [1]]
, то есть [1,1]
, и чтобы получить окончательное значение, нужно вычислить y!!1 + y!!0
. Это очевидное и окончательное значение равно 2. Так что теперь fibs - [1, 1, 2, ??]
.[last [take 3 fibs], last [take 2 fibs]]
, то есть [last [1,1,2], last [1,1]]
, теперь доступно значение, поэтому мы можем просто взять его и продолжить. Наконец, мы получили четвертое значение 3.Разве это не кажется знакомым? так же, как с помощью рекурсивной функции для вычисления выдумок. теперь мы позволяем самому компилятору делать вещи (ссылаясь). мы используем ленивую оценку здесь.
Реализация zip - это просто еще одно представление [значений last fibs] здесь. вам просто нужна небольшая модификация, чтобы понять zib-версию реализации fibs.
Haskell Wiki: ленивая оценка
для символа <-
: Понимание списка
Как бы анализатор знал, что происходит в (a, b) в противном случае?
EDIT: Благодаря ViralShah, я сделаю это немного менее гномическим. "< -" сообщает парсеру назначить список пар с правой стороны "zip fibs (tail fibs)" в левую сторону "(a, b)".