четверг, 29 января 2015 г.

Немного о каррировании в Haskell

Читая М. Липовача "Изучай Haskell во имя добра!", я некоторое время не понимал, чем частичное применение отличается от каррирования. Потратил некоторое время на разбор данного вопроса и набросал себе "шпаргалку" по обозначенной теме.


В Haskell функции без параметров называются определениями (definition) или именами (name).

func :: String
func = "Haskell"

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

func :: Int -> Double -> Char -> Bool

то Haskell воспринимает её следующим образом:

func :: Int -> (Double -> (Char -> Bool))

Т.е. функция func принимает параметр типа Int и возвращает новую функцию, которая принимает очередной параметр - типа Double и возвращает другую новую функцию, принимающую параметр типа Char и возвращающую значение типа Bool.
Преобразование функции от многих аргументов в функцию, берущую свои аргументы по одному называется каррированием. Haskell автоматически выполняет каррирование всех функций, принимающих более одного параметра. Именно благодаря каррированию становится возможным частичное применение функций, а так же создание сечений. В свою очередь, частичное применение делает возможным существование бесточечной нотации.

Примечание

В Haskell не существует такого понятия, как частичное применение функции. Существует применение функции (без "частично"). Если мы говорим (для удобства восприятия), что функция f :: Int -> Int -> Int имеет два аргумента, (что с технической точки зрения не является корректным), то мы можем так же сказать (снова для удобства восприятия), что f 5 - это частично применённая функция (что так же не будет корректно технически).

Пример

func :: (Num a) => a -> a -> a -> a
func a b c d = a + b + c + d

ghci

Частичное применение:
λ: let n = func 2 3
λ: let m = n 10
λ: let g = m 7
λ: g
22
Сечения:
λ: let a = (/2)
λ: a 10
5.0
λ: let b = (15/)
λ: b 5
3.0
Бесточечная нотация:
odd' :: Integral a => a -> Bool
odd' = odd
ghci:
λ: odd' 5
True
λ: odd' 4
False

Каррирование и декаррирование

В стандартном модуле Data.Tuple определены, помимо прочего, следующие функции:
curry :: ((a, b) -> c) -> a -> b -> c
uncurry :: (a -> b -> c) -> (a, b) -> c
Функция curry преобразовывает некаррированную функцию в каррированную.
Функция uncurry преобразовывает каррированную функцию в некаррированную.

Пример


msg :: Int -> Bool -> String
msg n True = show $ n `div` 2
msg n _ = show $ n * 2

ghci


λ: let n = msg
λ: let m = uncurry n
λ: :t n
n :: Int -> Bool -> String
λ: :t m
m :: (Int, Bool) -> String
λ: n 5 True
"2"
λ: m (5,True)
"2"
λ: let k = curry m
λ: :t k
k :: Int -> Bool -> String
λ: k 5 True
"2"

Комментариев нет: