Главная » Haskell » Каррирование и частичное применение

0

Общий  вид  типа  функции  в  виде  (Arg1Type ->  (… ->  (ArgNType -> ResType)…))  наводит  на   мысль  о   том,   что   каждая   функция   может рассматриваться  в   качестве   функции   одного  аргумента,  при   этом   результатом  исполнения такой   функции  будет  тоже   функция.  Действительно,  если  мысленно   обозначить в  представленном выражении  типа  часть (… ->  (ArgNType ->  ResType)…) как ResType*, то типом исходной  функции будет выражение (Arg1Type ->  ResType*), а это в силу определения типа функции есть тип функции одного аргумента.

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

Суть частичного применения можно пояснить следующим образом.  Пусть имеется некоторая функция f, которая принимает на вход n аргументов:

f x1 x2 … xn = …

И пусть имеется некоторая функция g, которая определена следующим образом:

g = f 0

В теле определения функции g имеет место частичное применение функции f с фактическим значением первого аргумента равным 0. Что происходит в этом случае? Транслятор языка Haskell выполняет создание функции g на основании тела функции f, подставляя в нём на места вхождения формального параметра x1 поданное  на вход  фактическое значение 0. Результатом будет являться

функция с (n ? 1) аргументами, которая и будет являться определением функ-

ции g.

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

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

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

В  стандартном модуле Prelude   определена  пара функций  для  применения  некаррированных аргументов к каррированным  функциям  и  наоборот (для функций двух аргументов). Под  некаррированным аргументом здесь, само собой, понимается кортеж значений. Эти функции: curry и uncurry  соответственно (детально описываются на стр. 129 и стр. 166):

curry :: ((a, b)  ->  c) ->  (a  ->  b ->  c) curry f x  y  =  f  (x, y)

uncurry :: (a  ->  b ->  c) ->  ((a, b)  ->  c) uncurry f p = f  (fst p)  (snd  p)

При их частичном применении (когда  на вход функциям  подаётся только другая функция) результатом будет функция, которая выполняет те же самые действия, что и исходная функция, но имеет противоположный признак каррированности (и это, кстати, видно по записи типов функции: в них скобки расставлены не совсем обычным образом). Например, операция (*), как и любая иная, является каррированной. Но применение uncurry  (*) делает её некаррирован ной, а потому на вход это применение начинает ожидать пару значений:

> (uncurry  (*)) (10,  2)

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

Источник: Душкин Р. В., Справочник по языку Haskell. М.: ДМК Пресс, 2008. 544 с., ил.

По теме:

  • Комментарии