Как стать автором
Обновить

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

Время на прочтение3 мин
Количество просмотров19K
Привет 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 – неассоциативный оператор

Итак, как мы напишем оператор с учетом приоритета и ассоциативности:
infixr 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. Таким образом, появляется возможность применять аргументы по одному. В этом и состоит принцип каррирования (по имени американского логика Haskell B. Curry). Если «скормить» такой функции число, то получим еще одну функцию. В этом несложно убедиться на простом примере:
successor :: Integer -> Integer
successor = plus 1

Этот прием называют частичным применением функции.

В прелюдии определены специальные функции 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)

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

Публикации

Изменить настройки темы

Истории

Ближайшие события