itmo_conspects

Лекция 2. Синтаксис языка Haskell

Haskell обладает статической строгой типизацией, то есть компилятор проверяет операции над типами переменных, а также не позволяет не явных преобразований типов

Одними из базовых типов в Haskell являются:

Haskell для работы с числами и булевыми значениями имеет такой же набор операторов, как и большинство других языков:

Операция Обозначение в языке Пример Результат
Сложение + 2 + 3 5
Вычитание - 5 - 2 3
Умножение * 4 * 3 12
Деление / 7 / 2 3.5
Целочисленное деление (применимо только к целым числам) `div` 7 `div` 2 3
Деление по модулю (применимо только к целым числам) `mod` 7 `mod` 2 1
Возведение в степень (показатель степени должен быть неотрицательным) ^ 2 ^ 3 8
Меньше < 2 < 3 True
Больше > 2 > 3 False
Меньше или равно <= 2 <= 2 True
Больше или равно >= 3 >= 2 True
Равно == 2 == 2 True
Не равно /= 2 /= 3 True
Логическое И && True && False False
Логическое ИЛИ \|\| True \|\| False True
Логическое НЕ not not True False
Отрицание числа negate negate 5 -5

Единственным отличием является использование /= в качестве оператора “не равно” вместо привычного !=

Комментарии в коде обозначаются так:

-- однострочный комментарий

{-
    многострочный
    комментарий
-}

В дальнейших примерах будет представлен код и вывод интерпретатора ghci
Строка с ghci> обозначает вводимый код, а код без ghci> обозначает результирующее значение

Вызов функции осуществляется так:

ghci> min 5 8
5

Здесь min - это название функции, а 5 и 8 - аргументы, перечисленные через пробел

Если аргумент содержит в себе пробелы, то аргумент нужно обернуть в скобки. Например:

ghci> min (3) (8)
3
ghci> min 3 (max 8 1)
3
ghci> max 3 max 8 1
-- ошибка GHC-39999, нельзя вызвать `max` как функцию 4 аргументов
ghci> (min 3 max) 8 1
-- ошибка GHC-39999, второй аргумент в функции `min` не может иметь тип функции

Такой способ вызова функции называется префиксный. Другим способом является инфиксный вызов:

ghci> (+) 2 3
5
ghci> 2 + 3
5
ghci> div 2 3
0
ghci> 2 `div` 3
0
ghci> mod 2 3
2
ghci> 2 `mod` 3
2

Здесь (+) 2 3 является префиксной формой, а 2 + 3 - инфиксной. Как можно заметить, операторы в Haskell - это тоже функции. Если функция состоит из специальных символом, то в префиксной форме он должен быть обернутым в скобки ((+) 2 3), а если из букв, то в инфиксной форме в обратные кавычки (2 `mod` 3)

Помимо этого у операторов есть две характеристики: тип ассоциативности и приоритет выполнения

Такую информацию об операторе можно узнать в интерпретаторе с помощью инструкции :info или :i:

ghci> :i +
type Num :: * -> Constraint
class Num a where
  (+) :: a -> a -> a
  ...
        -- Defined in `GHC.Internal.Num'
infixl 6 +

В Haskell можно сделать свой оператор, например:

(|-|) :: Int -> Int -> Int
(|-|) a b = abs (a - b)

В первой строчке определен тип оператора, а именно Int -> Int -> Int. Последний тип в этой цепочке определяет тип возврата, а предыдущие - типы аргументов. Вторая строка определяет реализацию оператора

Важно заметить, что объявление оператор нельзя указать в интерпретаторе

Далее оператор можно будет применить:

ghci> (|-|) 4 5
1
ghci> 4 |-| 5  
1

По умолчанию, такой оператор будет левоассоциативным и иметь приоритет 9

Чтобы изменить тип ассоциативности и приоритет, нужно применить соответствующее ключевое слово (infix, infixl или infixr):

(|-|) :: Int -> Int -> Int
(|-|) a b = abs (a - b)
infixl 6 |-|

Обозначение оператора может состоять из таких символов: !, #, $, %, &, *, +, ., /, <, =, >, ?, @, \, ^, |, -, ~, :. Если оператор начинается с :, то он является конструктором


Функции объявляются таким же образом:

mod :: Int -> Int -> Int
mod a b = a - b * a `div` b

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

Такое определение функции называется явным или точечным (pointful). Однако есть еще бесточечный (pointfree) стиль:

-- Явный
plus5 :: Int -> Int
plus5 a = a + 5

-- Бесточечный
plus5 = (+) 5
-- или так
plus5 = (+5)

Можно также применять в функциях с несколькими аргументами (но такое ухудшает читаемость):

-- Pointful
pentaSum a b c d e = a + b + c + d + e
-- Pointfree
pentaSum = ((((((+) .). (+)) .). (+)) .). (+)

Сервис позволяет переводить функции из точечного стиля в бесточечный


Рассмотрим конструкции языка:


В Haskell можно определить функцию используя соответствие шаблону:

isZero :: Integer -> Bool
isZero 0 = True
isZero _ = False

Такая функция возвращает истину, если аргумент соответствует нулю. Однако порядок важен, такая функция возвращает всегда ложь:

isZero :: Integer -> Bool
isZero _ = False
isZero 0 = True

Другой пример - вычисление значений последовательности a_1 = 1, a_2 = 2, a_k = a_{k - 1} + 3 * a_{k - 2}:

someSequence :: Integer -> Integer
someSequence n = calcHelper n 1 2 where
    calcHelper 1 a1 _ = a1
    calcHelper 2 _ a2 = a2
    calcHelper k x y = calcHelper (k - 1) y (y + 3 * x)

Здесь в блоке where объявляется вспомогательная функция calcHelper


Тип функции можно узнать с помощью инструкции :t или :type:

ghci> :type not
not :: Bool -> Bool

При этом есть особые функции:

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

Помимо этого в Haskell есть перегрузка функций (или Ad-hoc полиморфизм):

ghci> :t (+)
(+) :: Num a => a -> a -> a
ghci> :t (==)
(==) :: Eq a => a -> a -> Bool

Здесь для операторов на тип a наложены ограничения: для оператора + это то, что a должен быть числом, а для == - что a должен быть сравниваемым


Haskell поддерживает композицию функций. В математике композиция функций g ○ f определена так, что (g ○ f)(x) = g(f(x))

В Haskell композиция функций осуществляется через оператор .:

f :: Integer -> Integer
f x = x ^ 2
g :: Integer -> Integer
g x = x + 1

-- (f ○ g)(x) = f(g(x))
example -: Integer -> Integer
example x = (f . g) x

-- или так
example = (f . g)

Так как . - это оператор, значит это функция, и у нее такой тип:

ghci> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c

Как можно заметить . - это функция от 3 аргументов: функции b -> c, функции a -> b и значения a

Реализовать . можно так:

(.) f g x = f (g (x))

Другой оператор $ выглядит таким образом:

($) :: (a -> b) -> a -> b
f $ x = f x

Как можно заметить, оператор $ применяет функцию f с аргументом x. В отличие от обычного применения оператор $ имеет приоритет 0 и правоассоциативен

Например, так можно представить оператор .:

(.) f g x = f $ g x

Или так можно возвести в квадрат несколько раз:

f :: Integer -> Integer
f = (^2)
ghci> f . f $ 2
16
ghci> f . f . f $ 2
256

Или упростить еще сильнее:

ghci> (^2) . (^2) $ 2
16
ghci> (^2) . (^2) . (^2) $ 2
256

Такая форма не засоряет пространство имен, но ухудшает читаемость