Из Википедии, бесплатной энциклопедии
Перейти к навигации Перейти к поиску

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

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

Как структурные преобразования [ править ]

Складки можно рассматривать как последовательную замену структурных компонентов структуры данных функциями и значениями. Например, списки строятся на многих функциональных языках из двух примитивов: любой список является либо пустым списком, обычно называемым nil   ( []), либо создается путем добавления элемента перед другим списком, создавая так называемый узел cons.  (   ), полученный в результате применения функции (записывается в Haskell как двоеточие ). Можно просмотреть раз в списках , как замена   на ноль в конце списка со значением определенной, и заменяя друг против Cons(X1,Cons(X2,Cons(...(Cons(Xn,nil)))))cons(:)с определенной функцией. Эти замены можно рассматривать в виде диаграммы:

Right-fold-transformation.png

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

Left-fold-transformation.png

Эти изображения визуально иллюстрируют правый и левый сгиб списка . Они также подчеркивают тот факт, что foldr (:) []это функция идентификации в списках ( неглубокая копия на языке Лиспа ), поскольку замена cons на consи nil на nilне изменит результата. Левая диаграмма складки предполагает простой способ полностью изменить список, foldl (flip (:)) []. Обратите внимание, что параметры cons должны быть перевернуты, потому что добавляемый элемент теперь является правым параметром функции объединения. Еще один простой результат, который легко увидеть с этой точки зрения, - это написать функцию карты высшего порядка в терминахfoldr, составив функцию для воздействия на элементы cons, как:

 карта  е  =  foldr  (( : )  .  е )  []

где точка (.) - оператор, обозначающий композицию функции .

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

В списках [ править ]

Свертывание списка [1,2,3,4,5]с помощью оператора сложения приведет к 15 - сумме элементов списка [1,2,3,4,5]. В грубом приближении эту складку можно представить как замену запятых в списке операцией +, давая 1 + 2 + 3 + 4 + 5.

В приведенном выше примере + - это ассоциативная операция , поэтому конечный результат будет одинаковым независимо от скобок, хотя конкретный способ его вычисления будет другим. В общем случае неассоциативных бинарных функций порядок, в котором объединяются элементы, может влиять на значение окончательного результата. В списках есть два очевидных способа сделать это: либо комбинируя первый элемент с результатом рекурсивного комбинирования остальных (называемый правой складкой ), либо комбинируя результат рекурсивного комбинирования всех элементов, кроме последнего, с последний элемент (называемый левой складкой ). Это соответствует тому, что бинарный оператор в Haskell является либо правоассоциативным, либо левоассоциативным.«S или Пролог » s терминологии. При правой складке сумма будет заключена в круглые скобки как 1 + (2 + (3 + (4 + 5))), а при левой складке она будет заключена в скобки как (((1 + 2) + 3) + 4) + 5.

На практике удобно и естественно иметь начальное значение, которое в случае правого сгиба используется, когда кто-то достигает конца списка, а в случае левого сгиба - это то, что изначально объединяется с первым элементом списка. список. В приведенном выше примере значение 0 ( аддитивная идентичность ) будет выбрано в качестве начального значения 1 + (2 + (3 + (4 + (5 + 0))))для правой и ((((0 + 1) + 2) + 3) + 4) + 5левой складок. Для умножения, первоначальный выбор-не будет работать: 0 * 1 * 2 * 3 * 4 * 5 = 0. Единичный элемент для умножения равен 1. Это даст нам результат 1 * 1 * 2 * 3 * 4 * 5 = 120 = 5!.

Линейные складки против древовидных [ править ]

Использование начального значения необходимо, когда функция комбинирования f   асимметрична по своим типам (например, a → b → b), то есть когда тип ее результата отличается от типа элементов списка. Затем необходимо использовать начальное значение того же типа, что и результат f  , чтобы была возможна линейная цепочка приложений. Будет ли он ориентирован влево или вправо, будет определяться типами, ожидаемыми от его аргументов функцией комбинирования. Если это второй аргумент, который должен быть того же типа, что и результат, тогда f   можно рассматривать как двоичную операцию, которая связывает справа , и наоборот.

Когда функция является магма , т.е. симметрична его типов ( a → a → a), и тип результата такой же , как тип элементов списка, скобки могут быть размещены в произвольном порядке , таким образом , создавая дерево вложенных подвыражения, например, ((1 + 2) + (3 + 4)) + 5. Если двоичная операция f   ассоциативна, это значение будет четко определено, то есть одинаковым для любых скобок, хотя рабочие детали того, как она вычисляется, будут другими. Это может существенно повлиять на эффективность, если f не   является строгим .

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

Специальные складки для непустых списков [ править ]

Часто хочется выбрать единичный элемент операции f в качестве начального значения z . Когда исходное значение не кажется подходящим, например, когда кто-то хочет свернуть функцию, которая вычисляет максимум из двух ее параметров, по непустому списку, чтобы получить максимальный элемент списка, существуют варианты foldrи, foldlкоторые используют последний и первый элемент списка соответственно в качестве начального значения. В Haskell и некоторых других языках они называются foldr1and foldl1, причем 1 ссылается на автоматическое предоставление начального элемента и на тот факт, что списки, к которым они применяются, должны иметь хотя бы один элемент.

Эти свертки используют симметричную по типу бинарную операцию: типы ее аргументов и ее результата должны быть одинаковыми. Ричард Берд в своей книге 2010 года предлагает [1] «общую функцию сворачивания для непустых списков», foldrnкоторая преобразует свой последний элемент, применяя к нему дополнительную функцию аргумента, в значение типа результата перед началом самого сворачивания, и таким образом, может использовать двоичную операцию с асимметричным типом, например обычную, foldrдля получения результата, тип которого отличается от типа элементов списка.

Реализация [ править ]

Линейные складки [ править ]

Использование Haskell в качестве примера, foldlи foldrможет быть сформулирована в нескольких уравнений.

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

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

 foldr  ::  ( a  ->  b  ->  b )  ->  b  ->  [ a ]  ->  b  foldr  f  z  []  =  z  foldr  f  z  ( x : xs )  =  f  x  ( foldr  f  z  xs )

Если список пуст, результатом будет начальное значение z. Если нет, примените f к первому элементу и результату свертывания остальных.

Древовидные складки [ править ]

Списки можно сворачивать в виде дерева как для конечных, так и для неопределенно определенных списков:

foldt  f  z  []  =  z foldt  f  z  [ x ]  =  f  x  z foldt  f  z  xs  =  foldt  f  z  ( пары  f  xs ) foldi  f  z  []  =  z foldi  f  z  ( x : xs )  =  f  x  ( foldi  f  z  ( пары  f  xs )) пары  f  ( x : y : t )  =  f  x  y  :  пары  f  t пары  _  t  =  t

В случае foldiфункции, чтобы избежать ее неконтролируемого вычисления в неопределенно определенных списках, функция неf должна всегда запрашивать значение своего второго аргумента, по крайней мере, не все, или не сразу (см. Пример ниже).

Складки для непустых списков [ править ]

foldl1  f  [ x ]  =  x foldl1  f  ( x : y : xs )  =  foldl1  f  ( f  x  y  :  xs )foldr1  f  [ x ]  =  x foldr1  f  ( x : xs )  =  f  x  ( foldr1  f  xs )foldt1  f  [ x ]  =  x foldt1  f  ( x : y : xs )  =  foldt1  f  ( f  x  y  :  пары  f  xs ) foldi1  f  [ x ]  =  x foldi1  f  ( x : xs )  =  f  x  ( foldi1  f  ( пары  f  xs ))

Рекомендации по порядку оценки [ править ]

При наличии ленивого или нестрогого вычисления foldrнемедленно возвращает приложение f в начало списка и рекурсивный случай сворачивания по остальной части списка. Таким образом, если f может произвести некоторую часть своего результата без ссылки на рекурсивный случай в своем «правом», то есть во втором аргументе, а остальная часть результата никогда не требуется, то рекурсия остановится (например, ) . Это позволяет правым сверткам работать с бесконечными списками. Напротив, немедленно вызовет себя с новыми параметрами, пока не достигнет конца списка. Эта хвостовая рекурсияhead == foldr (\a b->a) (error "empty list")foldlможет быть эффективно скомпилирован как цикл, но вообще не может иметь дело с бесконечными списками - он будет рекурсивно повторяться бесконечно в бесконечном цикле .

Достигнув конца списка, выражение фактически строится foldlиз вложенных приложений с углублением fвлево, которое затем представляется вызывающему для оценки. Если бы функция fздесь сначала ссылалась на свой второй аргумент и могла бы произвести некоторую часть своего результата без ссылки на рекурсивный случай (здесь, слева, то есть в своем первом аргументе), то рекурсия остановилась бы. Это означает, что хотя foldrрекурсия справа , она позволяет функции ленивого комбинирования проверять элементы списка слева; и наоборот, а foldlрекурсии слева, он позволяет функции ленивого комбинирования проверять элементы списка справа, если она того пожелает (например, ).last == foldl (\a b->b) (error "empty list")

Обращение списка также является хвостовой рекурсией (это можно реализовать с помощью ). В конечных списках это означает, что можно составить левую и обратную складку для выполнения правой свертки хвостовой рекурсией (см.  ) С модификацией функции, чтобы она меняла порядок своих аргументов (т. Е. ), tail-рекурсивно строит представление выражения, которое будет строить правый. Посторонняя структура промежуточного списка может быть устранена с помощью техники стиля передачи продолжения ; аналогично (  требуется только в таких языках, как Haskell, с перевернутым порядком аргументов для функции объединенияrev = foldl (\ys x -> x : ys) []1+>(2+>(3+>0)) == ((0<+3)<+2)<+1ffoldr f z == foldl (flip f) z . foldl (flip (:)) []foldr f z xs == foldl (\k x-> k . f x) id xs zfoldl f z xs == foldr (\x k-> k . flip f x) id xs zflipfoldlв отличие, например, от схемы, где один и тот же порядок аргументов используется для объединения функций с обоими foldlи foldr).

Другой технический момент заключается в том, что в случае левых складок с использованием ленивого вычисления новый начальный параметр не оценивается до того, как будет выполнен рекурсивный вызов. Это может привести к переполнению стека, когда кто-то достигает конца списка и пытается оценить результирующее потенциально гигантское выражение. По этой причине такие языки часто предоставляют более строгий вариант сворачивания влево, который заставляет вычислять начальный параметр перед выполнением рекурсивного вызова. В Haskell это foldl'функция (обратите внимание на апостроф, произносится как «премьер») вData.Listбиблиотека (нужно помнить, что принуждение значения, созданного с помощью ленивого конструктора данных, само по себе не приведет к автоматическому принудительному принудительному использованию его составляющих). В сочетании с хвостовой рекурсией такие складки приближаются к эффективности циклов, обеспечивая постоянную пространственную операцию, когда ленивая оценка конечного результата невозможна или нежелательна.

Примеры [ править ]

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

λ >  foldr  ( \ x  y  ->  concat  [ "(" , x , "+" , y , ")" ])  "0"  ( показать карту  [ 1 .. 13 ]) "(1+ (2+ (3 + (4+ (5+ (6+ (7+ (8+ (9+ (10+ (11+ (12+ (13 + 0))))))))))))) "  λ >  foldl  ( \ x  y  ->  concat  [ "(" , x , "+" , y , ")" ])  "0"  ( показать карту  [ 1 .. 13 ]) "(((((((( (((((0 + 1) +2) +3) +4) +5) +6) +7) +8) +9) +10) +11) +12) +13) "  λ >  foldt  ( \ x  y  ->  concat  [ "(" , x , "+" , y , ")" ])  "0"  ( показать карту  [ 1 .. 13 ]) "(((((1 + 2 ) + (3 + 4)) + ((5 + 6) + (7 + 8))) + (((9 + 10) + (11 + 12)) + 13)) + 0) "  λ >  foldi  ( \ x  y  ->  concat  [ "(" , x , "+" , y , ")" ])  "0"  ( показать карту  [ 1 .. 13 ]) "(1 + ((2 + 3 ) + (((4 + 5) + (6 + 7)) + ((((8 + 9) + (10 + 11)) + (12 + 13)) + 0)))) " 

Бесконечное древовидное сворачивание демонстрируется, например, в рекурсивном производстве простых чисел неограниченным решетом Эратосфена в Haskell :

простые числа  =  2  :  _Y  (( 3  : )  .  минус  [ 5 , 7 .. ]  .  Фелди  ( \ ( х : XS )  YS  ->  х  :  объединение  хз  YS )  []  .  карта  ( \ р ->  [ р * р ,  p * p + 2 * p .. ])) _Y  g  =  g ( _Y  g )  - = g. грамм . грамм . грамм . ...

где функция unionработает с упорядоченными списками локально, чтобы эффективно производить их объединение множеств и minusих разность множеств .

Для конечных списков, например, сортировку слиянием (и ее разновидность удаления дубликатов nubsort) можно легко определить, используя древовидное сворачивание как

mergesort  xs  =  foldt  merge  []  [[ x ]  |  x  <-  xs ] nubsort  xs  =  foldt  union  []  [[ x ]  |  x  <-  xs ]

с функцией mergeсохранения дубликатов union.

Функции headи lastмогли быть определены путем складывания как

head  =  foldr  ( \ x  r  ->  x )  ( ошибка  «head: пустой список» ) last  =  foldl  ( \ a  x  ->  x )  ( ошибка  «last: пустой список» )

На разных языках [ править ]

Универсальность [ править ]

Fold - это полиморфная функция. Для любого g, имеющего определение

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

тогда g можно выразить как [10]

 g  =  foldr  f  v

Кроме того, комбинатор с фиксированной точкой может быть реализован через свертку, [11] доказывая, что итерации могут быть сведены к сверткам:

 y  f  =  foldr  ( \ _  ->  f )  undefined  ( повторение  undefined )

См. Также [ править ]

  • Агрегатная функция
  • Итерированная двоичная операция
  • Катаморфизм , обобщение складки
  • Гомоморфизм
  • Карта (функция высшего порядка)
  • Сумма префикса
  • Рекурсивный тип данных
  • Оператор редукции
  • Структурная рекурсия

Ссылки [ править ]

  1. ^ Ричард Берд, «Жемчужины проектирования функциональных алгоритмов», Cambridge University Press 2010, ISBN  978-0-521-51338-8 , стр. 42
  2. ^ "fold - язык программирования Kotlin" . Котлин . Jetbrains . Проверено 29 марта 2019 .
  3. ^ "reduce - язык программирования Kotlin" . Котлин . Jetbrains . Проверено 29 марта 2019 .
  4. ^ "Результат - язык программирования Котлин" . Котлин . Jetbrains . Проверено 29 марта 2019 .
  5. ^ "База" . Джейн Стрит Капитал . Проверено 26 февраля 2019 года .
  6. ^ Для справкиfunctools.reduce:import functools
    Для справкиreduce:from functools import reduce
  7. ^ Одерски, Martin (2008-01-05). «Re: Blog: Мой вердикт языку Scala» . Группа новостейcomp.scala.lang . Проверено 14 октября 2013 года .
  8. ^ Стерлинг, Николас. «Интуитивно понятный оператор Scala /: (foldLeft)» . Проверено 24 июня +2016 .
  9. ^ «Fold API - Стандартная библиотека Scala» . www.scala-lang.org . Проверено 10 апреля 2018 .
  10. ^ Хаттон, Грэм. «Учебник по универсальности и выразительности складки» (PDF) . Журнал функционального программирования (9 (4)): 355–372 . Проверено 26 марта 2009 года .
  11. Папа, Берни. «Получение исправления из правильной складки» (PDF) . Монада. Читатель (6): 5–16 . Проверено 1 мая 2011 года .

Внешние ссылки [ править ]

  • «Функции высшего порядка - отображение, сворачивание и фильтрация»
  • «Блок 6: Функции складывания высшего порядка»
  • "Сложить в Tcl"
  • «Построение гомоморфизма списков из левых и правых складок»
  • "Волшебный фолдр"