Конспекты лекций «Haskell как первый язык программирования». Часть2

в 17:43, , рубрики: haskell, функциональное программирование, метки: ,

Привет Habr! Сегодня мы поговорим о полиморфизме функций, операторах и карринге. Напоминаю, что лекции рассчитаны на новичков, а конспект предпологает сжатую форму изложения. Если все еще интересно…

Полиморфизм

Функция полиморфна, если она способна оперировать разными типами данных. Самая простая полиморфная функция, это функция тождественности:

id     :: a -> a
id x   =  x

Наиболее часто полиморфизм используют для работы со списками (оно и понятно):

length :: [a] -> Int
length []        =  0
length (_:l)     =  1 + length l

Областью определения данной функции (вычисляющей, очевидно, длину списка) является список переменных типа a. Такую конструкция в просторечии называют просто: «список ашек». Функция примет на вход любой список. Большинство функций в прелюдии являются полиморфными. Не поленитесь — загляните. А, пока, вот еще пример:

filter :: (a -> Bool) -> [a] -> [a]
filter p []                 = []
filter p (x:xs) | p x       = x : filter p xs
                | otherwise = filter p xs

Функция filter оставляет в списке только элементы удовлетворяющие некоторому условию.

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

error            :: String -> a

В данном случае функция error принимает на вход строку об ошибке и возвращает переменную типа а.
Очень интересно определена полиморфная функция без параметров — undefined:

undefined        :: a
undefined        =  error "Prelude.undefined"

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

Операторы

Функцию которую можно расположить между аргументами называют инфиксной или, просто, оператором. (Слово аргументы употреблено условно, мы помним, что в Haskell нет функций двух аргументов).
Синтаксис Haskell допускает использовать следующие знаки для построения операторов:
«: # $ % * + — =. / < >?! @ ^ | »
Операторы можно составлять как угодно, за исключением следующих комбинаций:
« :: =… — @ | < — -> ~ => »
Кроме того, что имена оперраторов заключаются в круглые скобки, определение функции такое же как и в префиксной нотации. Пример:

(%%) :: Int -> Int -> (Int, Int)
(%%) x y = (div x y, rem x y)

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

По ассоциативности операторы делятся на ассоциативные, ассоциативные справа, ассоциативные слева и неассоциативные.
Некоторый условный оператор "/":

  • ассоциативен справа, если выражение a / b / c вычисляют как a / (b / c)
  • ассоциативен слева, если выражение a / b / c вычисляют как (a / b) / c
  • ассоциативен, если выражение a / b / c можно вычислять в любом порядке
  • неассоциативен, если выражение a / b / c запрещено записывать без скобок

В Haskell:

  • infixr – ассоциативность справа
  • infixl – ассоциативность слева
  • infix – неассоциативный оператор

Итак, как мы напишем оператор с учетом всех приоритета и ассоциативности:

infifr 7 --ассоциативность справа, приоритет 7
(/) :: Bool -> Bool -> Bool
(/) x y = ...

Приоритеты и ассоциативность стандартных операторов из прелюдии:

infixr 9  .
infixl 9  !!
infixr 8  ^, ^^, **
infixl 7  *, /, `quot`, `rem`, `div`, `mod`, :%, %
infixl 6  +, -
infixr 5  :, ++
infix  4  ==, /=, <, <=, >=, >, `elem`, `notElem`
infixr 3  &&
infixr 2  ||
infixl 1  >>, >>=
infixr 1  =<<
infixr 0  $, $!, `seq`

Карринг

Итак, почему же в Haskell нет функции двух и более аргументов? Рассмотрим функцию:

plus :: (Integer, Integer) -> Integer
plus (x, y) = x + y

Функция plus, имеет один аргумент. Этот аргумент — пара чисел.
Обычно же функции в Haskell записывают в виде:

plus :: Integer -> Integer -> Integer
plus x  y  = x + y

И у этой функции тоже один аргумент (а не два, как можно было бы подумать), этот аргумент число типа Integer. Если «скормить» такой функции число, то получим еще одну функцию. В этом несложно убедиться построив, например, такую функцию:

successor :: Integer -> Integer
successor = plus 1

Этот прием называют частичным применением функции или каррингом (curring), по имени американского логика Haskell B. Curry. В прелюдии определены специальные функции curry и uncurry, приводящие функции к карринговой форме и обратно:

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)

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

Автор: serr

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js