Реализовать zip с помощью foldr
в настоящее время я нахожусь на главе 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 ответов
zip2 xs ys = foldr step done xs ys
where done ys = []
step x zipsfn [] = []
step x zipsfn (y:ys) = (x, y) : (zipsfn ys)
как это работает: (шаг foldr done xs) возвращает функцию, которая потребляет йс; поэтому спускаемся хз списка наращивание вложенного состав функции, которые будут применяться к соответствующей части ys.
как это придумать: я начал с общей идеи (из аналогичного примеры видел раньше), написал
zip2 xs ys = foldr step done xs ys
затем заполнил каждую из следующих строк по очереди с тем, что он должен был быть, чтобы типы и значения выходили правильно. Это было легче рассмотрим сначала простейшие случаи, а потом более сложные.
первая строка может быть написана более просто как
zip2 = foldr step done
как показал маттиаст.
ответ уже был дан здесь, но не (наглядный) деривации. Так что даже после всех этих лет, возможно, стоит добавить.
это на самом деле довольно просто. Во-первых,
foldr f z xs = foldr f z [x1,x2,x3,...,xn] = f x1 (foldr f z [x2,x3,...,xn]) = ... = f x1 (f x2 (f x3 (... (f xn z) ...)))
следовательно, 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 (f x2 (f x3 (... (f xn z) ...))) ys
как видно здесь,если f
не является принудительным в своем 2-м аргументе, он начинает работать первый on x1
и ys
, f x1
r1
ys
где r1 =
(f x2 (f x3 (... (f xn z) ...)))
= foldr f z [x2,x3,...,xn]
.
Итак, используя
f x1 r1 [] = [] f x1 r1 (y1:ys1) = (x1,y1) : r1 ys1
организуем прохождение информации слева направо по списку, by вызов r1
с остальное в список ys1
, foldr f z [x2,x3,...,xn]
ys1 = f x2
r2
ys1
, как следующий шаг. Вот и все.
, когда ys
меньше, чем xs
(или такая же длина), то []
чехол для f
пожары и обработка останавливается. Но если ... --12--> больше, чем xs
затем f
' s []
дело не будет стрелять, и мы доберемся до финала f xn
z
(yn:ysn)
приложения
f xn z (yn:ysn) = (xn,yn) : z ysn
так как мы достигли конца xs
на zip
обработка должна прекратиться:
z _ = []
и это означает, что определение 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
и " что делается с more ys
когда есть более x
s" и z = const []
на nil
-дело в foldr
, is "то, что делается с 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
etc.
Я нашел способ, используя довольно похожий метод на ваш:
myzip = foldr step (const []) :: [a] -> [b] -> [(a,b)]
where step a f (b:bs) = (a,b):(f bs)
step a f [] = []
для неродных Haskellers здесь, я написал версию схемы этого алгоритма, чтобы сделать его более ясным, что на самом деле происходит:
> (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
приводит к функции, которая при применении к списку возвращает zip списка, сложенного со списком, заданным функции. Хаскелл скрывает внутреннее lambda
из-за ленивости.
чтобы разбить его дальше:
take zip on input: '(1 2 3) Эти выражения производится Функ получает вызов с
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
в этом случае, is (list 9 1)
(cons (cons 2 (first (list 9 1))) (f (rest (list 9 1))))
(cons (cons 2 9) (f (list 1)))
и, как вы помните, f
молнии на аргументе 3
.
и это продолжается и т. д...
проблема со всеми этими решениями 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 делает под названием список fusion, о котором говорится в GHC.Базовый модуль, но, вероятно, должно быть гораздо более известным. Быть хорошим производителем списка и использовать build
С foldr
может предотвратить много бесполезного производства и немедленного потребления элементов списка и может подвергнуть дальнейшей оптимизации.
я сам пытался понять это элегантное решение, поэтому я сам попытался вывести типы и оценку. Итак, нам нужно написать функцию:
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)]
, потому что ->
is право-ассоциативной, это эквивалентно:
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))
construct должен создать рекурсивную функцию, которая будет проходить через [4,5,6,7]
и застегнуть элементы. (Имейте в виду, что если один из исходных списков длиннее, избыточные значения выбрасываются). IOW, наша конструкция должна иметь тип [b] -> [(a,b)]
.
3 `step` done
наш базовый случай, где done
начальный значение, как 0
на foldr (+) 0 [1..3]
. Мы не хотим ничего zip после 3, потому что 3 является конечным значением xs
, поэтому мы должны прекратить рекурсию. Как вы завершаете рекурсию над списком в базовом случае? Вы возвращаете пустой список []
. Но вспомните!--15--> подпись типа:
done :: [b] -> [(a,b)]
поэтому мы не можем вернуться просто []
, мы должны вернуть функцию, которая будет игнорировать все, что она получает. Поэтому используйте const
:
done = const [] -- this is equivalent to done = \_ -> []
теперь давайте начнем выяснять, что step
должно быть. Он сочетает в себе значение типа a
функции типа [b] -> [(a,b)]
и возвращает функцию типа [b] -> [(a,b)]
.
на 3 `step` done
, мы знаем, что значение результата, которое позже перейдет в наш список zipped, должно быть (3,6)
(зная от оригинала xs
и ys
). Поэтому 3 `step` done
должны оценить в:
\(y:ys) -> (3,y) : done ys
помните, мы должны вернуть функция, внутри которой мы как-то застегиваем элементы, приведенный выше код имеет смысл и typechecks.
теперь, когда мы предположили, как именно 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
P. S.: Если вы вдохновлены изяществом складок, читайте:!--94-->написание foldl с помощью foldr а потом Грэма Хаттона учебник по универсальности и выразительности раза.
простой подход:
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)