Шаг 108.
Основы языка Haskell. Функционалы (функции высшего порядка). Представление функций с помощью карринга

    На этом шаге мы рассмотрим функционалы curry и uncurry.

    Представление функций многих аргументов в виде последовательности функций одного аргумента предложил М.Шейнфинкель (1924): если функция y=f(x1,x2,...,xn) имеет тип

   A1 → (A2 → (... → (An → B)...)),
то для вычисления значения f(x1,x2,...,xn) необходимо последовательно провести вычисление следующего вида:
   (...(f(a1) a2)...) an,
результатом которого будет объект типа B.
Определение.
  • (1) Каррингом (по имени Х.Карри, чьё имя носит язык Haskell) называется приём замены структурированных выражений последовательностью нескольких простых выражений.
  • (2) ([1, с.107])  Каррингом называется явление, при котором для функции нескольких аргументов появляется возможность зафиксировать несколько первых из них: сам процесс фиксации аргументов и подготовка функции к возможности их фиксации.
  • (3) Каррингом (каррированием) называется процесс (и приём) представления функции от нескольких аргументов в виде функции от одного аргумента (другими словами, процесс построения каррированной функции).
Определение.
Каррированными функциями называются функции y=f(x1,x2,...,xn), синтаксически представимые в виде
   (...(f(x1) x2)...) xn.

    В качестве синонима слова "карринг" используются термины  частичное применение функции и  частичная параметризация.

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

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

    Библиотека Prelude содержит два функционала

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

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

    Отметим преимущества частично применённых функций:

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

    Поясним последнее утверждение.


Пример. Рассмотрим функцию twice, которая применяет к аргументу одну и ту же функцию дважды:
   twice:: (Double -> Double) -> (Double -> Double)
   twice f x = f (f x)

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

   f(fx)=Bffx=((.) f f) x,
а функцию twice переписать следующим образом:
   twice':: (Double -> Double) -> (Double -> Double)
   twice' f = (.) f f

    Получено представление функции в карринговой форме, которое характеризуется отсутствием аргументов:

   twice':: (Double -> Double) -> (Double -> Double)
   twice' f = (.) f f

    Определение twice в некарринговой форме выглядит так:

   twice'':: (Double -> Double) -> (Double -> Double)
   twice'' (f,x) = f (f x)

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

    Для тестирования можно использовать функцию

   test = (twice sin 1,(twice' sin 1,twice'' (sin,1)))

    Итак, в случае необходимости всегда можно преобразовать функцию к виду частично применённой с помощью функции curry, которая берёт "некарринговую" функцию и преобразует её к частично применённой форме.

    Отметим, что функция curry сама представляет собой частично применённую функцию (она получает три аргумента, один за другим).

    Функция uncurry осуществляет обратное преобразование: она превращает функцию с двумя параметрами в её некарринговую форму.

    В заключение приведем несколько демонстрационных примеров.

   -- Демонстрация некаррированной функции  "умножение"
   -- (вызов функции аналогичен принятому в математике)
   ---------------------------------------------------- 
   mUnCur:: (Int,Int) -> Int
   mUnCur (a,b) = a*b
   -- **********************************************
   -- Демонстрация каррированной функции "умножение"
   -- (вызов функции аналогичен принятому в функцио-
   --  нальном программировании)
   ----------------------------- 
   mulCur:: Int -> Int -> Int
   mulCur a b = a*b
   -- **************************************************
   -- Демонстрация удобного изменения порядка аргументов
   -- в каррированной функции
   -------------------------------------- 
   flip':: (t -> u -> v) -> (u -> t -> v)
   flip' f b a = f a b
   -- ***********************************
   -- Демонстрация преобразования функции
   -- в каррированную форму
   -------------------------------------- 
   curry':: ((t,u) -> v) -> (t -> u -> v)
   curry' g a b = g (a,b)
   -- ***********************************
   -- Демонстрация преобразования функции
   -- в некаррированную форму
   ---------------------------------------- 
   uncurry':: (t -> u -> v) -> ((t,u) -> v)
   uncurry' f (a,b) = f a b
   -- ****************************************
   -- Моделирование функции для декаррирования
   -------------------------------------------
   uncurry'' f p = f (fst p) (snd p)
   -- ******************************
   -- Неудачные тестовые примеры:
   ------------------------------------
   test1 =   mUnCur (3,4)          == 12
          && curry'   mUnCur 4 5   == 20
          && uncurry' mulCur (4,5) == 20
   test2 = uncurry'' (-) (2,5) == uncurry (-) (2,5)
Файл с примерами можно взять здесь.
(1)Кирпичёв Е.Р. Элементы функциональных языков // Практика функционального программирования, 2009, 3, с.83-197.

    На следующем шаге мы приведем некоторые утилиты для работы со списками.




Предыдущий шаг Содержание Следующий шаг