Написание фолдла как фолд путаницы - PullRequest
0 голосов
/ 31 января 2019

Я знаю, что есть другие посты по этому поводу, но мой немного отличается.

У меня есть функция, которая выполняет задачу foldl, используя foldr.Мне дано решение, но я хотел бы помочь понять.


Первое, что я понимаю, это то, что моя функция принимает 2 аргумента, но 3 приведены в приведенном выше тесте.Это означает, что 10 будет принимать участие в лямбда-выражении, которое я предполагаю.

1) Заметит ли 10 место b в b -> b?(тогда b будет начальным значением аккумулятора)

Что я не понимаю, так это то, что делает часть (\x r b -> r (f b x)).

2) Каково значение каждой из переменных?Я очень озадачен этой лямбда-функцией.

3) Что именно делает лямбда-функция и чем она отличается от обычной foldr?

Ответы [ 3 ]

0 голосов
/ 31 января 2019

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

3) Что именно делает лямбда-функция и чем она отличается от обычного фолдера?

Все это только строитлевый сгиб (foldl) из правого сгиба (foldr) и переворачивает два последних аргумента.Он эквивалентен

foldTest f = flip (foldr (flip f))
foldTest f = flip (foldl f)

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

1) Заместитель 10 занимает местоб в б -> б?(тогда b будет исходным значением аккумулятора) Чего я не понимаю, так это то, что делает часть (\ xrb -> r (fbx)).

Да, правильно.10 принимает роль начального аккумулятора левой складки.

2) Каково значение каждой из переменных?Я очень озадачен этой лямбда-функцией.

Чтобы понять, что происходит, я считаю полезным сделать шаг за шагом лямбда-исчисление:

foldTest (-) [1,2,3] 10
foldTest (-) (1:(2:(3:[]))) 10

-- remember the definition of foldTest which is given in a point-free way
foldlTest f xs = foldr (\x r b -> r (f b x)) (\b -> b) xs

-- add the hidden parameter and you get
foldlTest f xs b' = (foldr (\x r b -> r (f b x)) (\b -> b) xs) b'

-- apply that definition with f = (-), xs = (1:(2:(3:[]))), b' = 10
(foldr (\x r b -> r ((-) b x)) (\b -> b) (1:(2:(3:[])))) 10
(foldr (\x r b -> r (b - x)) (\b -> b) (1:(2:(3:[])))) 10

-- the inner lambda function is curried, thus we can write it as
-- \x r (\b -> r (b - x)), which is equivalent but will be handy later on.
(
  foldr (\x r -> (\b -> r (b - x))) (\b -> b) (1:(2:(3:[])))
) 10

-- keep in mind foldr is defined as
foldr f' b'' []      = b''
foldr f' b'' (x:xs') = f' x (foldr f' b'' xs')

-- apply second row of foldr with f' = (\x r -> (\b -> r (b - x))),
-- b'' = (\b -> b), x = 1 and xs' = (2:(3:[]))
(
  (\x r -> (\b -> r (b - x))) 1 (foldr (\x r -> (\b -> r (b - x))) (\b -> b) (2:(3:[])))
) 10

-- apply accumulation lambda for the first time with x = 1,
-- r = foldr (\x r -> (\b -> r (b - x))) (\b -> b) (2:(3:[])) gives
(
  \b -> (foldr (\x r -> (\b -> r (b - x))) (\b -> b) (2:(3:[]))) (b - 1)
) 10

-- now we repeat the process for the inner folds
(
  \b -> (
    foldr (\x r -> (\b -> r (b - x))) (\b -> b) (2:(3:[]))
  ) (b - 1)
) 10
(
  \b -> (
    (\x r -> (\b -> r (b - x))) 2 (foldr (\x r -> (\b -> r (b - x))) (\b -> b) (3:[]))
  ) (b - 1)
) 10
(
  \b -> (
    \b -> (foldr (\x r -> (\b -> r (b - x))) (\b -> b) (3:[])) (b - 2)
  ) (b - 1)
) 10
(
  \b -> (
    \b -> (
      foldr (\x r -> (\b -> r (b - x))) (\b -> b) (3:[])
    ) (b - 2)
  ) (b - 1)
) 10
(
  \b -> (
    \b -> (
      (\x r -> (\b -> r (b - x))) 3 (foldr (\x r -> (\b -> r (b - x))) (\b -> b) [])
    ) (b - 2)
  ) (b - 1)
) 10
(
  \b -> (
    \b -> (
      \b -> (foldr (\x r -> (\b -> r (b - x))) (\b -> b) [])) (b - 3)
    ) (b - 2)
  ) (b - 1)
) 10
(
  \b -> (
    \b -> (
      \b -> (
        foldr (\x r -> (\b -> r (b - x))) (\b -> b) []
      ) (b - 3)
    ) (b - 2)
  ) (b - 1)
) 10

-- Now the first line of foldr's definition comes in to play
(
  \b -> (
    \b -> (
      \b -> (
        \b -> b
      ) (b - 3)
    ) (b - 2)
  ) (b - 1)
) 10

-- applying those lambdas gives us
(
  \b -> (
    \b -> (
      \b -> (
        \b -> b
      ) (b - 3)
    ) (b - 2)
  ) (b - 1)
) 10

-- So we can see that the foldTest function built another function
-- doing what we want:
(\b -> (\b -> (\b -> (\b -> b) (b - 3)) (b - 2)) (b - 1)) 10
(\b -> (\b -> (\b -> b) (b - 3)) (b - 2)) (10 - 1)
(\b -> (\b -> b) (b - 3)) ((10 - 1) - 2)
(\b -> b) (((10 - 1) - 2) - 3)
(((10 - 1) - 2) - 3)
((9 - 2) - 3)
(7 - 3)
4
0 голосов
/ 31 января 2019

По определению foldlTest имеем

foldlTest (-) xs b = foldr g n xs b
    where
    n     b = b
    g x r b = r (b - x)

По определению foldr имеем

foldr  g  n  [x,y,z]     =  g x (foldr  g  n  [y,z])

но также

foldr  g  n  [x,y,z]  b  =  g x (foldr  g  n  [y,z])  b      -- (1)
                                 ---- r -----------
                         =       foldr  g  n  [y,z]  (b-x)

(при использовании «внутри» foldlTest) и т. Д. Путем многократного применения (1),

                         =  g y (foldr  g  n  [z])   (b-x)
                         =       foldr  g  n  [z]   ((b-x)-y)
                         =  g z (foldr  g  n  [] )  ((b-x)-y)
                         =       foldr  g  n  []   (((b-x)-y)-z)
                         =                 n       (((b-x)-y)-z)
                         =                         (((b-x)-y)-z)

Таким образом, выражение, котороеэквивалентно левой складке, построенной правой складкой прямо вверх, потому что g является хвостовой рекурсией.И, таким образом,

foldlTest (-)  [1,2,3]  10
--             [x,y,z]  b
==
(((10 - 1) - 2) - 3)) 
==
foldl  (-)  10  [1,2,3]

, и мы видим, что нет , b в n = (\b -> b) делает не принимает 10, а скорее принимает все выражение , эквивалентное левому сгибу , которое было построено правый сгиб .

Но да, 10 - это начальное значение аккумулятора в выражении, эквивалентном левому сгибу, как и предполагалось, которое было построено правым сгибом.

0 голосов
/ 31 января 2019

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

Во-первых, как всегда вHaskell, неплохо бы взглянуть на типы:

Prelude> :t foldl
foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b

Так как нас интересуют только списки, а не общие Foldable s, давайте специализируемся на:

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

и сравните с функцией, которую вам дали:

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

Поскольку функции Haskell каррированы, это еще один способ сказать, что -> стрелки в сигнатурах типов ассоциативно справа, последняя парав скобках нет необходимости, так что это то же самое, что и:

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

По сравнению с foldl выше, мы видим, что они идентичны, за исключением того факта, что два последних параметра - [a] и b - перевернуты.

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

взятие функции сгиба и списка и создание функции b -> b, которая описывает, как свертывание по списку превращает первоначальный аккумулятор в конечный результат .

Обратите внимание, в частности, что в этой формулировке она действительно является функцией.

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

foldlTest f xs = foldr (\x r b -> r (f b x))
                (\b -> b)
                xs

Я буду первым, кто признает, что это довольно сложно, даже запутанно.Как всегда, давайте рассмотрим типы.

Типы ввода просты.Из приведенного выше обсуждения мы знаем, что f имеет тип b -> a -> b, а xs имеет тип [a].

ОК, а как насчет этой лямбды?Давайте рассмотрим это поэтапно:

  • Требуется 3 аргумента, x, r и b, в этом порядке.
  • Результат вызова функции равен r (f b x).Это уже многое говорит нам, если мы сядем и подумаем об этом!
  • Для одного мы знаем, что f имеет тип b -> a -> b, поэтому из f b x мы знаем, что b имеет тип b и x имеют тип a.
  • Что касается r, мы видим, что это должна быть функция, потому что она применяется к f b x.И мы знаем, что последний имеет тип b (из сигнатуры типа f).Так что r имеет тип b -> c, для некоторого типа c.
  • Поэтому наша сложная лямбда-функция имеет тип a -> (b -> c) -> b -> c, где c - это какой-то тип, который мы еще не определили.
  • Теперь наступает ключевой момент.Эта лямбда представляется как первый аргумент (функция сгиба) функции foldr.Следовательно, он должен иметь тип d -> e -> e, для некоторых типов d и e.
  • Помните, что функции каррируются, и, хотя кажется, что сигнатура лямбды принимает 3 аргумента, мы можем уменьшить ее до2, переписав как a -> (b -> c) -> (b -> c).Это точное соответствие для сигнатуры, которую мы знаем, foldr ищет, с d, равным a и e, равным b -> c.

И это мы специализируемся foldr подпись, так что он принимает этот тип функции, мы находим, что это:

foldr :: (a -> (b -> c) -> (b -> c)) -> (b -> c) -> [a] -> (b -> c)`

Мы до сих пор не знаем, что такое c, но нам не нужно долго удивляться.Выше приведена подпись для фолда, который просматривает список a с и выдает функцию от b до c.Следующий аргумент foldr, который имеет тип b -> c, задается (реализацией, которую мы пытаемся расшифровать) как \b -> b.Конечно, это просто функция тождества, и, что самое главное, это функция от типа к себе.Таким образом, тип b -> c на самом деле должен быть b -> b, или, другими словами, c всегда был таким же, как b!

Так что лямбда должна иметь следующий тип:

a -> (b -> b) -> (b -> b)

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

Указанная выше сигнатура типа сама по себе не дает нам большого представления о том, как ее реализовать, учитывая, что a и b могут быть чем угодно.Тем не менее, у нас есть наша функция f, которая связывает их - напомним, что она принимает b и a и производит b.Учитывая, что (отменив карри снова), приведенная выше функция требует от нас, учитывая a, b -> b и b, чтобы создать еще один b, я могу видеть только два нетривиальных способачтобы сделать это:

  • примените функцию к b, затем примените f к a и результату.
  • примените f к a и b, затем примените функцию b -> b к результату

Второе из этих двух - именно то, что делает та лямбда, о которой вы спрашиваете, как мы надеемся, теперь очевидно из рассмотренияЭто.(Первый вариант был бы написан \x r b -> f (r b) x. На самом деле я не уверен, какой общий эффект это даст, хотя я не особо задумывался об этом.)

Я прошел много времени,хотя мне кажется, что это больше, чем на самом деле, потому что я старался быть очень кропотливым.Напомним, что функция foldlTest, учитывая список a s и функцию f :: b -> a -> b, создает функцию b -> b, которая строится, начиная с функции тождества и проходя справа налево.вдоль списка, изменяя текущую функцию r :: b -> b на ту, которая отправляет b в r (f b x) - где x :: a - элемент списка, в котором мы находимся в данный момент.

Это довольно алгоритмическое описаниеиз того, что foldlTest делает.Давайте попробуем посмотреть, что он делает с реальным списком - не конкретным, но, скажем, трехэлементным списком [a1, a2, a3].Мы начинаем с функции идентификации \b -> b и последовательно преобразуем ее в:

  • b -> f b a3 (напомним, что r начинается как функция идентификации)
  • b -> f (f b a2) a3 (это просто подстановка предыдущей функции как r в \b -> r (f b x), где a2 теперь играет роль x)
  • b -> f (f (f b a1) a2) a3

Надеюсь, выТеперь я вижу, что это выглядит очень похоже на свертывание списка слева с той же функцией f.И под «выглядит очень похоже» я имею в виду, что он идентичен!(Если вы не видели или не пробовали это раньше, попробуйте записать последовательные этапы foldl f b [a1, a2, a3], и вы увидите идентичный шаблон.)

Итак, еще раз извиняюсь, что это было немного бессмысленно,но я надеюсь, что это дало вам достаточно информации, чтобы ответить на вопросы, которые вы задавали.И не волнуйтесь, если это немного повредит вашему мозгу - это тоже мое!:)

...