Понимание фибоначчи Haskell

fibs :: [Int]
fibs = 0 : 1 : [ a + b | (a, b) <- zip fibs (tail fibs)]

Это генерирует последовательность Фибоначчи.

Я понимаю поведение охранников, :, zip и tail, но я не понимаю <-. Что он здесь делает?

Ответ 1

Из-за повышений я сделал свой комментарий в ответ.

Что вы видите, это не страх, но это понимание списка. Для начала подумайте об этом как о способе выражения математического набора обозначений типа A = {x | x элемент N}, что означает что-то вдоль линий: Множество A является множеством всех натуральных чисел. В понимании списка будет [x | x <- [1..] ].

Вы также можете использовать ограничения на свои номера: [x | x <- [1..], x `mod` 2 == 0 ] и многое другое.

Есть много хороших хакельских туринцев, которые освещают список и даже вопрос StackOverflow относительно ресурсов haskell.

Ответ 2

Единственная сложная вещь - 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 лениво оценивается, поэтому он может рассчитать список, однако требуется множество элементов. Это также относится к почтовому индексу.

Ответ 3

Разложим его.

zip создает пары из содержимого двух списков. Итак, первая пара zip fibs (tail fibs) дает нам (0, 1), которая добавляет до 1. Итак, теперь список [0,1,1]. Теперь мы знаем три элемента в списке, поэтому понимание списка может продолжаться, захватывая следующий элемент из списка и следующий элемент из хвоста, который дает (1,1) - добавляется вместе, делая 2. Затем мы получаем следующую пару, который равен (1,2), делая следующее число в последовательности 3. Это может продолжаться бесконечно, так как понимание всегда будет обеспечивать достаточное количество элементов.

Ответ 4

Для чего это стоит, я считаю следующую версию более понятной:

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

Ответ 5

Одним из преимуществ функционального программирования является то, что вы можете оценивать выражение вручную, как математическую задачу:

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, ??]]

Промойте и повторите.

Ответ 6

Пояснение в скобках:

[ a + b | (a, b) <- zip fibs (tail fibs)]

возвращает список, содержащий вывод (a + b), где переменные a и b относятся к результату

zip fibs (tail fibs)

Ответ 7

Он определяет эту диаграмму потоков

           .----->>------->>----.
          /                      \
         /                       /     
         \                      /        
     <---- 0 <---- 1 ---<<--- (+)       
                 /              \         
                 \               \         
                  \              /       
                   *---->>------*       

который вытягивает новый вход из себя по мере его создания, но всегда на одну и две позиции перед точкой производства, поддерживая два "обратных указателя" в последовательности. Это отражено в определении fibs = 0:1:[ a+b | a <- fibs | b <- tail fibs], с пониманием параллельного списка (:set -XParallelListComp и т.д.).

Поскольку он использует только свои последние два элемента, он эквивалентен

    map fst . iterate (\(a, b) -> (b, a+b)) $ (0,1)

Ответ 8

Я все еще не понимаю. Мне нравится этот ответ: 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 еще не рассчитан. он "ждет" (в левой части знака равенства) для вычисления в правой части (знака равенства).

Ответ 9

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

я пишу другую реализацию, чтобы проиллюстрировать это, и использую ?? в качестве заполнителя значения, который должен быть вычислен при необходимости.

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.

так вот процесс вычисления:

  1. x == 2, поэтому у [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, ??].
  2. x == 3, точно так же, как шаг 1. y - это [last [take 3 fibs], last [take 2 fibs]], то есть [last [1,1,2], last [1,1]], теперь доступно значение, поэтому мы можем просто взять его и продолжить. Наконец, мы получили четвертое значение 3.
  3. что это, просто повторите описанные выше шаги, и вы можете получить все значения, даже если оно бесконечно

Разве это не кажется знакомым? так же, как с помощью рекурсивной функции для вычисления выдумок. теперь мы позволяем самому компилятору делать вещи (ссылаясь). мы используем ленивую оценку здесь.

Реализация zip - это просто еще одно представление [значений last fibs] здесь. вам просто нужна небольшая модификация, чтобы понять zib-версию реализации fibs.

  1. Haskell Wiki: ленивая оценка

  2. для символа <-: Понимание списка

Ответ 10

Как бы анализатор знал, что происходит в (a, b) в противном случае?

EDIT: Благодаря ViralShah, я сделаю это немного менее гномическим. "< -" сообщает парсеру назначить список пар с правой стороны "zip fibs (tail fibs)" в левую сторону "(a, b)".