Реализация почтового индекса с использованием Foldr - PullRequest
19 голосов
/ 25 октября 2008

Я в настоящее время нахожусь на главе 4 Реального мира Haskell, и я пытаюсь обернуть мою голову вокруг реализации foldl в терминах foldr .

(Вот их код:)

myFoldl :: (a -> b -> a) -> a -> [b] -> a

myFoldl f z xs = foldr step id xs z
    where step x g a = g (f a x)

Я думал, что попытаюсь реализовать zip, используя ту же технику, но я, похоже, не добиваюсь прогресса. Это вообще возможно?

Ответы [ 7 ]

16 голосов
/ 25 октября 2008
zip2 xs ys = foldr step done xs ys
  where done ys = []
        step x zipsfn []     = []
        step x zipsfn (y:ys) = (x, y) : (zipsfn ys)

Как это работает: (шаг свёртки сделан хз) возвращает функцию YS; поэтому мы идем вниз по списку XS создание вложенной композиции функции, каждая из которых будет применена к соответствующей части ys.

Как придумать это: я начал с общей идеи (из аналогичного примеры видели раньше) написал

zip2 xs ys = foldr step done xs ys

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

Первая строка может быть написана более просто как

zip2 = foldr step done

как показал маттиаст.

10 голосов
/ 09 октября 2014

Ответ уже был дан здесь, но не (иллюстративный) вывод. Так что даже после всех этих лет, возможно, стоит добавить это.

Это на самом деле довольно просто. Во-первых,

foldr f z xs 
   = foldr f z [x1,x2,x3,...,xn] = f x1 <b>(foldr f z [x2,x3,...,xn])</b>
   = ... = f x1 <b>(f x2 (f x3 (... (f xn z) ...)))</b>

следовательно, посредством eta-расширения,

foldr f z xs ys
   = foldr f z [x1,x2,x3,...,xn] ys = f x1 (foldr f z [x2,x3,...,xn]) ys
   = ... = f x1 <b>(f x2 (f x3 (... (f xn z) ...)))</b> ys

Как очевидно здесь, , если f не форсирует во втором аргументе , он начинает работать first на x1 и ys, f x1 r1ys, где r1 =(f x2 (f x3 (... (f xn z) ...)))= foldr f z [x2,x3,...,xn].

Итак, используя

f x1 <b>r1</b> [] = []
f x1 <b>r1</b> (y1:ys1) = (x1,y1) : <b>r1</b> ys1

мы организуем передачу информации слева направо по списку, , звоня r1 с отдыхом из списка ввода ys1, foldr f z [x2,x3,...,xn]ys1 = f x2r2ys1, как следующий шаг. И это все.


Если ys меньше xs (или такой же длины), случай [] для f срабатывает, и обработка останавливается. Но если ys длиннее xs, то дело f [] не сработает, и мы доберемся до финального f xnz(yn:ysn) приложения,

f xn <b>z</b> (yn:ysn) = (xn,yn) : <b>z</b> ysn

Поскольку мы достигли конца xs, обработка zip должна прекратиться:

<b>z</b> _ = []

А это означает, что следует использовать определение z = const []:

zip xs ys = foldr f (const []) xs ys
  where
    f x r []     = []
    f x r (y:ys) = (x,y) : r ys

С точки зрения f, r играет роль продолжения успеха , которое f вызывает, когда обработка должна быть продолжена, после того, как она выпустила пару (x,y).

То есть r - это ", что делается с большим количеством ys, когда есть больше x s" , и z = const [], nil-case в foldr - "что делается с ys, когда больше нет x s" . Или f может остановиться самостоятельно, возвращая [], когда ys исчерпан.


Обратите внимание, как ys используется как своего рода накапливающееся значение, которое передается слева направо по списку xs, от одного вызова f до следующего («аккумулирующий» шаг, здесь, здесь, снимая с него головной элемент).

Естественно это соответствует левому сгибу, где этап накопления «применяет функцию», при этом z = id возвращает окончательное накопленное значение, когда «больше нет x s»:

foldl f a xs =~ foldr (\x r a-> r (f a x)) id xs a

Аналогично, для конечных списков,

foldr f a xs =~ foldl (\r x a-> r (f x a)) id xs a

И поскольку функция объединения решает, продолжать или нет, теперь можно оставить левый сгиб, который может остановиться рано:

foldlWhile t f a xs = foldr cons id xs a
  where 
    cons x r a = if t x then r (f a x) else a

или пропуск левой складки, foldlWhen t ..., с

    cons x r a = if t x then r (f a x) else r a

и т.д.

9 голосов
/ 25 октября 2008

Я нашел способ, очень похожий на ваш:

myzip = foldr step (const []) :: [a] -> [b] -> [(a,b)]
    where step a f (b:bs) = (a,b):(f bs)
          step a f [] = []
5 голосов
/ 02 марта 2017

Проблема со всеми этими решениями для zip заключается в том, что они складываются только в одном или другом списке, что может быть проблемой, если оба они являются «хорошими производителями», на языке объединения списков. Что вам действительно нужно, так это решение, которое объединяет оба списка. К счастью, есть статья именно об этом, названная «Сопоставление складок с гиперфункциями» .

Вам нужен вспомогательный тип, гиперфункция, которая в основном является функцией, которая принимает другую гиперфункцию в качестве аргумента.

newtype H a b = H { invoke :: H b a -> b }

Гиперфункции, используемые здесь, в основном действуют как "стек" обычных функций.

push :: (a -> b) -> H a b -> H a b
push f q = H $ \k -> f $ invoke k q

Вам также нужен способ соединить две гиперфункции, конец в конец.

(.#.) :: H b c -> H a b -> H a c
f .#. g = H $ \k -> invoke f $ g .#. k

Это связано с push по закону:

(push f x) .#. (push g y) = push (f . g) (x .#. y)

Это оказывается ассоциативный оператор, и это тождество:

self :: H a a
self = H $ \k -> invoke k self

Вам также нужно что-то, что игнорирует все остальное в «стеке» и возвращает определенное значение:

base :: b -> H a b
base b = H $ const b

И, наконец, вам нужен способ получить значение из гиперфункции:

run :: H a a -> a
run q = invoke q self

run объединяет все push ed функции вместе, от начала до конца, пока не достигнет base или не зациклится бесконечно.

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

zip xs ys = run $ foldr (\x h -> push (first x) h) (base []) xs .#. foldr (\y h -> push (second y) h) (base Nothing) ys where
  first _ Nothing = []
  first x (Just (y, xys)) = (x, y):xys

  second y xys = Just (y, xys)

Причина, по которой сворачивание обоих списков имеет значение, заключается в том, что GHC делает это объединение списков , о котором говорится в модуле GHC.Base , но, вероятно, должно быть гораздо больше хорошо известно. Будучи хорошим производителем списков и используя build с foldr, вы можете избежать большого количества бесполезного производства и немедленного потребления элементов списка, а также подвергнуть дальнейшей оптимизации.

5 голосов
/ 25 октября 2008

Для не родных Хаскеллеров здесь я написал версию этого алгоритма на Схеме, чтобы прояснить, что на самом деле происходит:

> (define (zip lista listb)
    ((foldr (lambda (el func)
           (lambda (a)
             (if (empty? a)
                 empty
                 (cons (cons el (first a)) (func (rest a))))))
         (lambda (a) empty)
         lista) listb))
> (zip '(1 2 3 4) '(5 6 7 8))
(list (cons 1 5) (cons 2 6) (cons 3 7) (cons 4 8))

Результатом foldr является функция, которая при применении к списку возвращает почтовый индекс сложенного списка со списком, переданным функции. Haskell скрывает внутреннюю lambda из-за ленивой оценки.


Чтобы разбить его дальше:

Взять почтовый индекс на входе: '(1 2 3) Функция Foldr вызывается с

el->3, func->(lambda (a) empty)

Это расширяется до:

(lambda (a) (cons (cons el (first a)) (func (rest a))))
(lambda (a) (cons (cons 3 (first a)) ((lambda (a) empty) (rest a))))

Если бы мы вернули это сейчас, у нас была бы функция, которая принимает список из одного элемента и возвращает пару (3 элемента):

> (define f (lambda (a) (cons (cons 3 (first a)) ((lambda (a) empty) (rest a)))))
> (f (list 9))
(list (cons 3 9))

Продолжая, теперь foldr вызывает func с

el->3, func->f ;using f for shorthand
(lambda (a) (cons (cons el (first a)) (func (rest a))))
(lambda (a) (cons (cons 2 (first a)) (f (rest a))))

Это функция, которая теперь берет список с двумя элементами и архивирует их с помощью (list 2 3):

> (define g (lambda (a) (cons (cons 2 (first a)) (f (rest a)))))
> (g (list 9 1))
(list (cons 2 9) (cons 3 1))

Что происходит?

(lambda (a) (cons (cons 2 (first a)) (f (rest a))))

a, в данном случае это (list 9 1)

(cons (cons 2 (first (list 9 1))) (f (rest (list 9 1))))
(cons (cons 2 9) (f (list 1)))

И, как вы помните, f архивирует свой аргумент с помощью 3.

И это продолжается и т.д. ...

2 голосов
/ 12 января 2016

Я попытался понять это элегантное решение сам, поэтому я попытался вывести типы и оценку самостоятельно. Итак, нам нужно написать функцию:

zip xs ys = foldr step done xs ys

Здесь нам нужно вывести step и done, какими бы они ни были. Напомним тип foldr, созданный для списков:

foldr :: (a -> state -> state) -> state -> [a] -> state

Однако наш вызов foldr должен быть реализован как-то так, как показано ниже, потому что мы должны принять не один, а два аргумента списка:

foldr :: (a -> ? -> ?) -> ? -> [a] -> [b] -> [(a,b)]

Поскольку -> является правоассоциативным , это эквивалентно:

foldr :: (a -> ? -> ?) -> ? -> [a] -> ([b] -> [(a,b)])

Наш ([b] -> [(a,b)]) соответствует переменной типа state в исходной сигнатуре типа foldr, поэтому мы должны заменить каждое вхождение state на нее:

foldr :: (a -> ([b] -> [(a,b)]) -> ([b] -> [(a,b)]))
      -> ([b] -> [(a,b)])
      -> [a]
      -> ([b] -> [(a,b)])

Это означает, что аргументы, которые мы передаем foldr, должны иметь следующие типы:

step :: a -> ([b] -> [(a,b)]) -> [b] -> [(a,b)]
done :: [b] -> [(a,b)]
xs :: [a]
ys :: [b]

Напомним, что foldr (+) 0 [1,2,3] расширяется до:

1 + (2 + (3 + 0))

Поэтому, если xs = [1,2,3] и ys = [4,5,6,7], наш foldr вызов расширится до:

1 `step` (2 `step` (3 `step` done)) $ [4,5,6,7]

Это означает, что наша конструкция 1 `step` (2 `step` (3 `step` done)) должна создать рекурсивную функцию, которая будет проходить через [4,5,6,7] и архивировать элементы. (Имейте в виду, что если один из исходных списков длиннее, лишние значения отбрасываются). Итак, наша конструкция должна иметь тип [b] -> [(a,b)].

3 `step` done - это наш базовый случай, где done - начальное значение, например 0 в foldr (+) 0 [1..3]. Мы не хотим ничего архивировать после 3, потому что 3 является окончательным значением xs, поэтому мы должны прекратить рекурсию. Как прекратить рекурсию по списку в базовом случае? Вы возвращаете пустой список []. Но вспомните done тип подписи:

done :: [b] -> [(a,b)]

Поэтому мы не можем вернуть просто [], мы должны вернуть функцию, которая игнорировала бы все, что она получает. Поэтому используйте const:

done = const [] -- this is equivalent to done = \_ -> []

Теперь давайте начнем выяснять, каким должно быть step. Он объединяет значение типа a с функцией типа [b] -> [(a,b)] и возвращает функцию типа [b] -> [(a,b)].

В 3 `step` done мы знаем, что значение результата, которое позже попадет в наш сжатый список, должно быть (3,6) (зная из оригинальных xs и ys). Поэтому 3 `step` done необходимо оценить в:

\(y:ys) -> (3,y) : done ys

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

Теперь, когда мы предположили, как именно step должен оценивать, давайте продолжим оценку. Вот как выглядят все этапы сокращения в нашей оценке foldr:

3 `step` done -- becomes
(\(y:ys) -> (3,y) : done ys)
2 `step` (\(y:ys) -> (3,y) : done ys) -- becomes
(\(y:ys) -> (2,y) : (\(y:ys) -> (3,y) : done ys) ys)
1 `step` (\(y:ys) -> (2,y) : (\(y:ys) -> (3,y) : done ys) ys) -- becomes
(\(y:ys) -> (1,y) : (\(y:ys) -> (2,y) : (\(y:ys) -> (3,y) : done ys) ys) ys)

Оценка дает начало этой реализации шага (обратите внимание, что мы учитываем ys раннее исчерпание элементов путем возврата пустого списка):

step x f = \[] -> []
step x f = \(y:ys) -> (x,y) : f ys

Таким образом, полная функция zip реализована следующим образом:

zip :: [a] -> [b] -> [(a,b)]
zip xs ys = foldr step done xs ys
  where done = const []
        step x f [] = []
        step x f (y:ys) = (x,y) : f ys

PS: Если вас вдохновляет элегантность сгибов, прочитайте Написание фолдла с использованием foldr , а затем Грэма Хаттона * Учебник об универсальности и выразительности сгиба .

0 голосов
/ 30 ноября 2017

Простой подход:

lZip, rZip :: Foldable t => [b] -> t a -> [(a, b)]

-- implement zip using fold?
lZip xs ys = reverse.fst $ foldl f ([],xs) ys
     where f  (zs, (y:ys)) x = ((x,y):zs, ys)

-- Or;
rZip xs ys = fst $ foldr f ([],reverse xs) ys
     where f x (zs, (y:ys))  = ((x,y):zs, ys)
...