Написання foldl за допомогою foldr


79

У реальному світі Haskell , глава 4. про функціональне програмування :

Написати foldl за допомогою foldr:

-- file: ch04/Fold.hs
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)

Вищезгаданий код мене дуже збентежив, і хтось, хто називається dps, переписав його із значущим ім'ям, щоб зробити його трохи зрозумілішим:

myFoldl stepL zeroL xs = (foldr stepR id xs) zeroL
where stepR lastL accR accInitL = accR (stepL accInitL lastL)

Хтось інший, Джеф Дж, тоді відмінно справився, подавши приклад і покроково показавши основний механізм:

myFoldl (+) 0 [1, 2, 3]
= (foldR step id [1, 2, 3]) 0
= (step 1 (step 2 (step 3 id))) 0
= (step 1 (step 2 (\a3 -> id ((+) a3 3)))) 0
= (step 1 (\a2 -> (\a3 -> id ((+) a3 3)) ((+) a2 2))) 0
= (\a1 -> (\a2 -> (\a3 -> id ((+) a3 3)) ((+) a2 2)) ((+) a1 1)) 0
= (\a1 -> (\a2 -> (\a3 -> (+) a3 3) ((+) a2 2)) ((+) a1 1)) 0
= (\a1 -> (\a2 -> (+) ((+) a2 2) 3) ((+) a1 1)) 0
= (\a1 -> (+) ((+) ((+) a1 1) 2) 3) 0
= (+) ((+) ((+) 0 1) 2) 3
= ((0 + 1) + 2) + 3

Але я все ще не можу цього повністю зрозуміти, ось мої запитання:

  1. Для чого потрібна функція id? Яка роль? Навіщо нам це потрібно тут?
  2. У наведеному вище прикладі функція id є накопичувачем у лямбда-функції?
  3. прототип foldr є foldr :: (a -> b -> b) -> b -> [a] -> b, і перший параметр - це функція, яка потребує двох параметрів, але покрокова функція у реалізації myFoldl використовує 3 параметри, я повністю заплутаний!

2
Для справжніх мазохістів,step = curry $ uncurry (&) <<< (flip f) *** (.)
Вейцзюнь Чжоу,

Відповіді:


99

Деякі пояснення в порядку!

Для чого потрібна функція id? Яка роль? Навіщо нам це потрібно тут?

idє функцією ідентичності , id x = xі використовується як еквівалент нуля при нарощуванні ланцюжка функцій з функцією складу , (.). Ви можете знайти його визначеним у Прелюдії .

У наведеному вище прикладі функція id є накопичувачем у лямбда-функції?

Акумулятор - це функція, яка створюється за допомогою багаторазового застосування функції. Там немає явного лямбда, так як ми називаємо акумулятор, step. Ви можете написати це за допомогою лямбди, якщо хочете:

foldl f a bs = foldr (\b g x -> g (f x b)) id bs a

Або як би написав Грем Геттон :

5.1 foldlОператор

А тепер давайте узагальнимо з sumlприкладу і розглянемо стандартний оператор, foldlякий обробляє елементи списку в порядку зліва направо, використовуючи функцію fдля поєднання значень і значенняv як початкове значення:

foldl :: (β → α → β) → β → ([α] → β)
foldl f v [ ] = v
foldl f v (x : xs) = foldl f (f v x) xs

Використовуючи цей оператор, sumlможна перевизначити його просто suml = foldl (+) 0. Багато інших функцій можна визначити простим способом, використовуючи foldl. Наприклад, стандартну функцію reverseможна перевизначити, використовуючи foldlнаступне:

reverse :: [α] → [α]
reverse = foldl (λxs x → x : xs) [ ]

Це визначення є більш ефективним, ніж наше початкове визначення з використанням fold, оскільки воно дозволяє уникнути використання неефективного оператора додавання (++) для списків.

Просте узагальнення розрахунку в попередньому розділі для функції sumlпоказує, як перевизначити функцію foldlз точки зору fold:

foldl f v xs = fold (λx g → (λa → g (f a x))) id xs v

На відміну від цього, неможливо перевизначити foldтерміни foldl, оскільки foldlце суворо у хвості аргументу списку, але foldце не так. Існує ряд корисних "теорем подвійності", що стосуються foldі foldl, а також деякі вказівки щодо вибору, який оператор найкраще підходить для конкретних програм (Bird, 1998).

Прототипом foldr є foldr :: (a -> b -> b) -> b -> [a] -> b

Haskell програміст хотів би сказати , що тип з foldrпоза (a -> b -> b) -> b -> [a] -> b.

і перший параметр - це функція, яка потребує двох параметрів, але покрокова функція у реалізації myFoldl використовує 3 параметри, я повністю заплутаний

Це заплутано і чарівно! Ми граємо в фокус і замінюємо акумулятор функцією, яка, у свою чергу, застосовується до початкового значення, щоб отримати результат.

Graham Hutton пояснює трюк , щоб включити foldlв foldrв вищевказаної статті. Ми починаємо з запису рекурсивного визначення foldl:

foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f v []       = v
foldl f v (x : xs) = foldl f (f v x) xs

А потім переформатуйте його за допомогою статичного перетворення аргументу на f:

foldl :: (a -> b -> a) -> a -> [b] -> a    
foldl f v xs = g xs v
    where
        g []     v = v
        g (x:xs) v = g xs (f v x)

Давайте перепишемо gтак, щоб плавати vвсередину:

foldl f v xs = g xs v
    where
        g []     = \v -> v
        g (x:xs) = \v -> g xs (f v x)

Що те саме, що мислити gяк функцію одного аргументу, що повертає функцію:

foldl f v xs = g xs v
    where
        g []     = id
        g (x:xs) = \v -> g xs (f v x)

Тепер у нас є gфункція, яка рекурсивно переглядає список, застосовує якусь функцію f. Кінцевим значенням є функція ідентичності, і кожен крок також призводить до функції.

Але ми маємо вже дуже подібну рекурсивну функцію у списках foldr!

2 Оператор складання

foldОператор має свої витоки в теорії рекурсії (Кліні, 1952), в той час як використання в foldякості центрального поняття в датах мови програмування назад до оператора редукції APL (Айверсон, 1962), а пізніше вставки оператору FP (BACKUS , 1978). У Haskell foldоператор списків можна визначити наступним чином:

fold :: (α → β → β) → β → ([α] → β)
fold f v [ ] = v
fold f v (x : xs) = f x (fold f v xs)

Тобто, враховуючи функцію fтипу α → β → βта значення vтипу β, функція fold f vобробляє список типів, [α]щоб надати значення типу β, замінивши конструктор nil []у кінці списку на значення v, а кожен конструктор (:)консистенції у списку на функція f. Таким чином, foldоператор інкапсулює простий шаблон рекурсії для обробки списків, в якому два конструктори списків просто замінюються іншими значеннями та функціями. Ряд звичних функцій у списках має просте визначення fold.

Це виглядає як дуже подібна рекурсивна схема до нашої gфункції. Тепер фокус: використовуючи всю доступну магію під рукою (вона ж Bird, Meertens та Malcolm), ми застосовуємо спеціальне правило, універсальну властивість складання , яке є еквівалентністю між двома визначеннями для функції, gяка обробляє списки, заявленої як:

g [] = v
g (x:xs) = f x (g xs)

якщо і тільки якщо

g = fold f v

Отже, універсальна властивість складок говорить:

    g = foldr k v

де gмає бути еквівалентним двом рівнянням, для деяких kі v:

    g []     = v
    g (x:xs) = k x (g xs)

З наших попередніх конструкцій складок ми знаємо v == id. Однак для другого рівняння нам потрібно обчислити визначення k:

    g (x:xs)         = k x (g xs)        
<=> g (x:xs) v       = k x (g xs) v      -- accumulator of functions
<=> g xs (f v x)     = k x (g xs) v      -- definition of foldl
<=  g' (f v x)       = k x g' v          -- generalize (g xs) to g'
<=> k = \x g' -> (\a -> g' (f v x))      -- expand k. recursion captured in g'

Що, підставляючи наші розраховані визначення kта vдає визначення foldl як:

foldl :: (a -> b -> a) -> a -> [b] -> a    
foldl f v xs =
    foldr
        (\x g -> (\a -> g (f v x)))
        id
        xs
        v

Рекурсивний gзамінюється комбінатором папок, і акумулятор стає функцією, побудованою через ланцюжок композицій fкожного елемента списку, у зворотному порядку (тому ми складаємо ліворуч, а не праворуч).

Це, безумовно, дещо вдосконалено, тому, щоб глибоко зрозуміти цю трансформацію, універсальну властивість складок , яка робить трансформацію можливою, я рекомендую підручник Хаттона, наведений нижче.


Список літератури


1
Plz виправте помилку в k = \x g' -> (\a -> g' (f v x)) і(\x g -> (\a -> g (f v x)))
Камель

10

Розглянемо тип foldr:

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

Тоді як тип stepщось подібне b -> (a -> a) -> a -> a. Оскільки крок переходить до foldr, ми можемо зробити висновок, що в цьому випадку складка має тип like (b -> (a -> a) -> (a -> a)) -> (a -> a) -> [b] -> (a -> a).

Нехай вас не бентежать різні значення aв різних підписах; це просто змінна типу. Крім того, майте на увазі, що стрілка функції є асоціативною правою, тому a -> b -> cце те саме, що a -> (b -> c).

Так, так, значення накопичувача для foldrє функцією типу a -> a, а початкове значення - id. Це має певний сенс, оскільки idце функція, яка нічого не робить - це та сама причина, з якої ви б почали з нуля як початкового значення при додаванні всіх значень у списку.

Що стосується stepприйому трьох аргументів, спробуйте переписати їх так:

step :: b -> (a -> a) -> (a -> a)
step x g = \a -> g (f a x)

Це полегшує побачити, що відбувається? Він приймає додатковий параметр, оскільки повертає функцію, і два способи її запису еквівалентні. Також зверніть увагу на додатковий параметр по черзі foldr: (foldr step id xs) z. Частина в дужках - це сама складка, яка повертає функцію, до якої потім застосовується z.


6

(швидко перегляньте мої відповіді [1] , [2] , [3] , [4], щоб переконатися, що ви розумієте синтаксис Хаскелла, функції вищого порядку, каррінг, склад функцій, оператор $, оператори інфіксів / префіксів, розділи та лямбди )

Універсальна властивість складки

рази просто кодифікація певних видів рекурсії. А властивість універсальності просто стверджує, що якщо ваша рекурсія відповідає певній формі, вона може бути перетворена у складку згідно з деякими формальними правилами. І навпаки, кожна складка може бути перетворена в рекурсію такого роду. Знову ж таки, деякі рекурсії можна перекласти у складки, які дають абсолютно однакову відповідь, а деякі рекурсії не можуть, і для цього існує точна процедура.

В основному, якщо ваша рекурсивна функція працює зі списками, схожими на ліві , ви можете перетворити її, щоб скласти одну праворуч , підставляючи fі vте, що насправді є.

g []     = v              ⇒
g (x:xs) = f x (g xs)     ⇒     g = foldr f v

Наприклад:

sum []     = 0   {- recursion becomes fold -}
sum (x:xs) = x + sum xs   ⇒     sum = foldr 0 (+)

Тут v = 0і sum (x:xs) = x + sum xsеквівалентно sum (x:xs) = (+) x (sum xs), отже f = (+). Ще 2 приклади

product []     = 1
product (x:xs) = x * product xs  ⇒  product = foldr 1 (*)

length []     = 0
length (x:xs) = 1 + length xs    ⇒  length = foldr (\_ a -> 1 + a) 0

Вправа:

  1. Реалізувати map, filter, reverse, concatі concatMapрекурсивно, так само , як перераховані вище функції на лівому стороні.

  2. Перетворіть ці 5 функцій у папку за формулою вище , тобто підставляючи fта vу формулу згину праворуч .

Складіть через папку

Як написати рекурсивну функцію, яка підсумовує числа зліва направо?

sum [] = 0     -- given `sum [1,2,3]` expands into `(1 + (2 + 3))`
sum (x:xs) = x + sum xs

Перша рекурсивна функція, яка приходить для пошуку, повністю розширюється ще до того, як почати додавати, це не те, що нам потрібно. Одним із підходів є створення рекурсивної функції, яка має акумулятор , яка відразу складає цифри на кожному кроці (читайте про рекурсію хвоста, щоб дізнатися більше про стратегії рекурсії):

suml :: [a] -> a
suml xs = suml' xs 0
  where suml' [] n = n   -- auxiliary function
        suml' (x:xs) n = suml' xs (n+x)

Добре, зупинись! Запустіть цей код у GHCi і переконайтеся, що розумієте, як він працює, а потім обережно та вдумливо продовжуйте. sumlнеможливо перевизначити за допомогою складки, але suml'може бути.

suml' []       = v    -- equivalent: v n = n
suml' (x:xs) n = f x (suml' xs) n

suml' [] n = nз визначення функції, так? І v = suml' []з формули універсальної властивості. Разом це дає v n = n, функцію , яка негайно повертає то , що отримує: v = id. Обчислимо f:

suml' (x:xs) n = f x (suml' xs) n
-- expand suml' definition
suml' xs (n+x) = f x (suml' xs) n
-- replace `suml' xs` with `g`
g (n+x)        = f x g n

Таким чином, suml' = foldr (\x g n -> g (n+x)) idі, таким чином, suml = foldr (\x g n -> g (n+x)) id xs 0.

foldr (\x g n -> g (n + x)) id [1..10] 0 -- return 55

Тепер нам просто потрібно узагальнити, замінити +змінною функцією:

foldl f a xs = foldr (\x g n -> g (n `f` x)) id xs a
foldl (-) 10 [1..5] -- returns -5

Висновок

А тепер прочитайте Підручник Гремама Хаттона з універсальності та виразності складання . Візьміть ручку і папір, спробуйте зрозуміти все, що він пише, поки ви не отримаєте більшу частину складок самостійно. Не потій, якщо чогось не розумієш, ти завжди можеш повернутися пізніше, але теж не зволікай багато.


Я вважаю цю відповідь простішою та зрозумілішою, ніж прийнята. Шкода, що у нього так мало голосів ...
гігабайти

5

Ось мій доказ, який foldlможна виразити в термінах foldr, який я вважаю досить простим, крім назви спагетті, яку stepвводить функція.

Твердження таке, що foldl f z xsеквівалентно

myfoldl f z xs = foldr step_f id xs z
        where step_f x g a = g (f a x)

Перше, що важливо помітити тут, це те, що права частина першого рядка насправді оцінюється як

(foldr step_f id xs) z

оскільки foldrприймає лише три параметри. Це вже натякає на те, що foldrволя обчислює не значення, а функцію, що працює, до якої потім застосовується z. Є два випадки для розслідування, щоб з’ясувати, чи myfoldlє foldl:

  1. Базовий регістр: порожній список

      myfoldl f z []
    = foldr step_f id [] z    (by definition of myfoldl)
    = id z                    (by definition of foldr)
    = z
    
      foldl f z []
    = z                       (by definition of foldl)
    
  2. Непорожній список

      myfoldl f z (x:xs)
    = foldr step_f id (x:xs) z          (by definition of myfoldl)
    = step_f x (foldr step_f id xs) z   (-> apply step_f)
    = (foldr step_f id xs) (f z x)      (-> remove parentheses)
    = foldr step_f id xs (f z x)
    = myfoldl f (f z x) xs              (definition of myfoldl)
    
      foldl f z (x:xs)
    = foldl f (f z x) xs
    

Оскільки в 2. перший та останній рядки мають однакову форму в обох випадках, його можна використовувати для складання списку до тих пір xs == [], поки в цьому випадку 1. не гарантує однаковий результат. Так по індукції myfoldl == foldl.


2

Королівської дороги до математики немає, навіть через Хаскелла. Дозволяє

h z = (foldr step id xs) z where   
     step x g =  \a -> g (f a x)

Що за біса h z? Припустимо, що xs = [x0, x1, x2].
Застосуйте визначення foldr:

h z = (step x0 (step x1 (step x2 id))) z 

Застосуйте визначення кроку:

= (\a0 -> (\a1 -> (\a2 -> id (f a2 x2)) (f a1 x1)) (f a0 x0)) z

Замінити в лямбда-функції:

= (\a1 -> (\a2 -> id (f a2 x2)) (f a1 x1)) (f z x0)

= (\a2 -> id (f a2 x2)) (f (f z x0) x1)

= id (f (f (f z x0) x1) x2)

Застосувати визначення ідентифікатора:

= f (f (f z x0) x1) x2

Застосувати визначення foldl:

= foldl f z [x0, x1, x2]

Це Королівська дорога чи що?


2

Перш ніж проголосувати проти, будь ласка, прочитайте наступний параграф

Я публікую відповідь для тих людей, яким такий підхід може бути більше підходить для їхнього мислення. Відповідь, можливо, містить зайву інформацію та думки, але саме вона мені потрібна для вирішення проблеми. Крім того, оскільки це ще одна відповідь на те саме питання, очевидно, що вона суттєво перекривається з іншими відповідями, однак вона розповідає історію того, як я міг зрозуміти цю концепцію.

Справді, я почав записувати ці замітки як особистий запис своїх думок, намагаючись зрозуміти цю тему. Мені знадобився цілий день, щоб я зачепив його суть, якщо я це справді зрозумів.

Мій довгий шлях до розуміння цієї простої вправи

Легка частина: що нам потрібно визначити?

Що відбувається з наступним прикладом виклику

foldl f z [1,2,3,4]

можна візуалізувати за допомогою такої схеми (яка є у Вікіпедії , але я вперше побачив це за іншою відповіддю ):

          _____results in a number
         /
        f          f (f (f (f z 1) 2) 3) 4
       / \
      f   4        f (f (f z 1) 2) 3
     / \
    f   3          f (f z 1) 2
   / \
  f   2            f z 1
 / \
z   1

(Як допоміжна примітка, при використанні foldlкожної програми fне виконується, і вирази обманюються так, як я писав їх вище; в принципі, їх можна обчислити, коли ви рухаєтесь знизу вгору, і саме це і foldl'робить.)

Вправа по суті кидає нам виклик використовувати foldrзамість того foldl, щоб відповідним чином змінити функцію кроку (тому ми використовуємо sзамість f) та початковий накопичувач (тому ми використовуємо ?замість z); список залишається незмінним, інакше про що ми говоримо?

Заклик до foldrмає виглядати так:

foldr s ? [1,2,3,4]

і відповідна схема така:

    _____what does the last call return?
   /
  s
 / \
1   s
   / \
  2   s
     / \
    3   s
       / \
      4   ? <--- what is the initial accumulator?

Результат дзвінка -

s 1 (s 2 (s 3 (s 4 ?)))

Що таке sі ?? А які їх типи? Схоже, sце функція з двома аргументами, дуже схожа f, але не будемо поспішати робити висновки. Крім того, ?залишмо на мить осторонь і давайте зауважимо, що це zмає вступити в дію, як тільки 1вступить у гру; однак, як це може zзіграти у виклику функції може бути два аргументи s, а саме у виклику s 1 (…)? Ми можемо розв’язати цю частину загадки, вибравши один, sякий приймає 3 аргументи, а не 2, про які ми згадали раніше, так що найвіддаленіший виклик s 1 (…)призведе до функції, яка приймає один аргумент, якому ми можемо передати z!

Це означає, що нам потрібен вихідний дзвінок, який розширюється до

f (f (f (f z 1) 2) 3) 4

бути рівнозначним

s 1 (s 2 (s 3 (s 4 ?))) z

або, іншими словами, ми хочемо частково застосовану функцію

s 1 (s 2 (s 3 (s 4 ?)))

бути еквівалентною наступній лямбда-функції

(\z -> f (f (f (f z 1) 2) 3) 4)

Знову ж таки, "єдиними" частинами, які нам потрібні, є sі ?.

Поворотний пункт: розпізнавання складу функції

Давайте перемалюємо попередню діаграму і напишемо праворуч, що ми хочемо, щоб кожен виклик sбув еквівалентним:

  s          s 1 (…) == (\z -> f (f (f (f z 1) 2) 3) 4)
 / \
1   s        s 2 (…) == (\z -> f (f (f    z    2) 3) 4)
   / \
  2   s      s 3 (…) == (\z -> f (f       z       3) 4)
     / \
    3   s    s 4  ?  == (\z -> f          z          4)
       / \
      4   ? <--- what is the initial accumulator?

Я сподіваюся, що з структури діаграми зрозуміло, що (…)на кожному рядку знаходиться правий бік рядка під ним; краще, це функція, повернута з попереднього (нижче) викликуs .

Також повинно бути ясно, що виклик sаргументами xі yє (повним) застосуванням yчасткового застосування fєдиного аргументу x. Оскільки часткове застосування fto xможна записати як лямбда (\z -> f z x), то повне застосування yдо нього призводить до лямбди (\z -> y (f z x)), яку в цьому випадку я б переписав як y . (\z -> f z x); перекладаючи слова у вираз, sми отримуємо

s x y = y . (\z -> f z x)

(Це те саме, що s x y z = y (f z x) , що і те саме, що і книга, якщо ви перейменуєте змінні.)

Останній біт: яке початкове "значення" ?акумулятора? Наведену вище діаграму можна переписати, розширивши вкладені виклики, щоб зробити їх ланцюжками композиції:

  s          s 1 (…) == (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2) . (\z -> f z 1)
 / \
1   s        s 2 (…) == (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2)
   / \
  2   s      s 3 (…) == (\z -> f z 4) . (\z -> f z 3)
     / \
    3   s    s 4  ?  == (\z -> f z 4)
       / \
      4   ? <--- what is the initial accumulator?

Ми тут бачимо, що sпросто "нагромаджує" послідовні часткові додатки f, але yв s x y = y . (\z -> f z x)припускає, що при інтерпретації s 4 ?(і, в свою чергу, всіх інших) не вистачає провідної функції, яка повинна бути складена з крайньою лівою лямбда.

Це просто наша ?функція: час дати їй причину свого існування, окрім того, щоб зайняти місце у заклику до foldr. Яким ми можемо його вибрати, щоб не змінювати отримані функції? Відповідь: id, на одиничний елемент по відношенню до оператора композиції (.).

  s          s 1 (…) == id . (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2) . (\z -> f z 1)
 / \
1   s        s 2 (…) == id . (\z -> f z 4) . (\z -> f z 3) . (\z -> f z 2)
   / \
  2   s      s 3 (…) == id . (\z -> f z 4) . (\z -> f z 3)
     / \
    3   s    s 4 id  == id . (\z -> f z 4)
       / \
      4   id

Отже, шукана функція є

myFoldl f z xs = foldr (\x g a -> g (f a x)) id xs z

1

Це може допомогти, я спробував розширити по-іншому.

myFoldl (+) 0 [1,2,3] = 
foldr step id [1,2,3] 0 = 
foldr step (\a -> id (a+3)) [1,2] 0 = 
foldr step (\b -> (\a -> id (a+3)) (b+2)) [1] 0 = 
foldr step (\b -> id ((b+2)+3)) [1] 0 = 
foldr step (\c -> (\b -> id ((b+2)+3)) (c+1)) [] 0 = 
foldr step (\c -> id (((c+1)+2)+3)) [] 0 = 
(\c -> id (((c+1)+2)+3)) 0 = ...

1
foldr step zero (x:xs) = step x (foldr step zero xs)
foldr _ zero []        = zero

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

myFold (+) 0 [1, 2, 3] =
  foldr step id [1, 2, 3] 0
  -- Expanding foldr function
  step 1 (foldr step id [2, 3]) 0
  step 1 (step 2 (foldr step id [3])) 0
  step 1 (step 2 (step 3 (foldr step id []))) 0
  -- Expanding step function if it is possible
  step 1 (step 2 (step 3 id)) 0
  step 2 (step 3 id) (0 + 1)
  step 3 id ((0 + 1) + 2)
  id (((0 + 1) + 2) + 3)

Ну, принаймні, це мені допомогло. Навіть це не зовсім правильно.


фактична послідовність foldr step id [1, 2, 3] 0-> step 1 (foldr step id [2, 3]) 0-> (foldr step id [2, 3]) (0 + 1)-> step 2 (foldr step id [3]) (0 + 1)-> (foldr step id [3]) ((0 + 1) + 2)-> step 3 (foldr step id []) ((0 + 1) + 2)-> (foldr step id []) (((0 + 1) + 2) + 3)-> id (((0 + 1) + 2) + 3).
Уілл Несс

0

Ця відповідь робить наведене нижче визначення легко зрозумілим за три етапи.

-- file: ch04/Fold.hs
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)

Крок 1. перетворіть склад оцінки функцій на комбінацію функцій

foldl f z [x1 .. xn] = z & f1 & .. & fn = fn . .. . f1 z. в якійfi = \z -> f z xi .

(Використовуючи z & f1 & f2 & .. & fnце означаєfn ( .. (f2 (f1 z)) .. ) .)

Крок 2. висловіть комбінацію функцій у foldrспосіб

foldr (.) id [f1 .. fn] = (.) f1 (foldr (.) id [f2 .. fn]) = f1 . (foldr (.) id [f2 .. fn]). Решту розгорніть, щоб дістати foldr (.) id [f1 .. fn] = f1 . .. . fn.

Помітивши, що послідовність зворотна, ми повинні використовувати зворотну форму (.). Визначте rc f1 f2 = (.) f2 f1 = f2 . f1, тоді foldr rc id [f1 .. fn] = rc f1 (foldr (.) id [f2 .. fn]) = (foldr (.) id [f2 .. fn]) . f1. Решту розгорніть, щоб дістати foldr rc id [f1 .. fn] = fn . .. . f1.

Крок 3. перетворіть складку у списку функцій у складку у списку операндів

Знайдіть, stepщо робить foldr step id [x1 .. xn] = foldr rc id [f1 .. fn]. Це легко знайти step = \x g z -> g (f z x).

У 3 кроки чітке визначення foldlвикористання foldr:

  foldl f z xs
= fn . .. . f1 z
= foldr rc id fs z
= foldr step id xs z

Доведіть правильність:

foldl f z xs = foldr (\x g z -> g (f z x)) id xs z
             = step x1 (foldr step id [x2 .. xn]) z
             = s1 (foldr step id [x2 .. xn]) z
             = s1 (step x2 (foldr step id [x3 .. xn])) z
             = s1 (s2 (foldr step id [x3 .. xn])) z
             = ..
             = s1 (s2 (.. (sn (foldr step id [])) .. )) z
             = s1 (s2 (.. (sn id) .. )) z
             = (s2 (.. (sn id) .. )) (f z x1)
             = s2 (s3 (.. (sn id) .. )) (f z x1)
             = (s3 (.. (sn id) .. )) (f (f z x1) x2)
             = ..
             = sn id (f (.. (f (f z x1) x2) .. ) xn-1)
             = id (f (.. (f (f z x1) x2) .. ) xn)
             = f (.. (f (f z x1) x2) .. ) xn

in which xs = [x1 .. xn], si = step xi = \g z -> g (f z xi)

Якщо ви виявите щось незрозуміле, додайте коментар. :)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.