multThree :: Int -> (Int -> (Int -> Int))
Перед символом –>
пишется тип параметра функции; после записывается тип значения, которое функция вернёт. Таким образом, наша функция принимает параметр типа Int
и возвращает функцию типа Int -> (Int –> Int)
. Аналогичным образом эта новая функция принимает параметр типа Int
и возвращает функцию типа Int -> Int
. Наконец, функция принимает параметр типа Int
и возвращает значение того же типа Int
.
Рассмотрим пример создания новой функции путём вызова функции с недостаточным числом параметров:
ghci> let multTwoWithNine = multThree 9
ghci> multTwoWithNine 2 3
54
В этом примере выражение multThree 9
возвращает функцию, принимающую два параметра. Мы называем эту функцию multTwoWithNine
. Если при её вызове предоставить оба необходимых параметра, то она перемножит их между собой, а затем умножит произведение на 9
.
Вызывая функции не со всеми параметрами, мы создаём новые функции «на лету». Допустим, нужно создать функцию, которая принимает число и сравнивает его с константой 100
. Можно сделать это так:
compareWithHundred :: Int -> Ordering
compareWithHundred x = compare 100 x
Если мы вызовем функцию с 99
, она вернёт значение GT
. Довольно просто. Обратите внимание, что параметр x
находится с правой стороны в обеих частях определения. Теперь подумаем, что вернёт выражение compare 100
. Этот вызов вернёт функцию, которая принимает параметр и сравнивает его с константой 100
. Ага-а! Не этого ли мы хотели? Можно переписать функцию следующим образом:
compareWithHundred :: Int -> Ordering
compareWithHundred = compare 100
Объявление типа не изменилось, так как выражение compare 100
возвращает функцию. Функция compare
имеет тип (Ord a) => a –> (a –> Ordering)
. Когда мы применим её к 100, то получим функцию, принимающую целое число и возвращающую значение типа Ordering
.
Сечения
Инфиксные функции могут быть частично применены при помощи так называемых сечений. Для построения сечения инфиксной функции достаточно поместить её в круглые скобки и предоставить параметр только с одной стороны. Это создаст функцию, которая принимает один параметр и применяет его к стороне с пропущенным операндом. Вот донельзя простой пример:
divideByTen :: (Floating a) => a –> a
divideByTen = (/10)
Вызов, скажем, divideByTen 200
эквивалентен вызову 200 / 10
, равно как и (/10) 200
:
ghci> divideByTen 200
20.0
ghci> 200 / 10
20.0
ghci> (/10) 200
20.0
А вот функция, которая проверяет, находится ли переданный символ в верхнем регистре:
isUpperAlphanum :: Char –> Bool
isUpperAlphanum = (`elem` ['А'..'Я'])
Единственная особенность при использовании сечений – применение знака «минус». По определению сечений, (–4)
– это функция, которая вычитает четыре из переданного числа. В то же время для удобства (–4) означает «минус четыре». Если вы хотите создать функцию, которая вычитает четыре из своего аргумента, выполняйте частичное применение таким образом: (subtract 4)
.
Печать функций
До сих пор мы давали частично применённым функциям имена, после чего добавляли недостающие параметры, чтобы всё-таки посмотреть на результаты. Однако мы ни разу не попробовали напечатать сами функции. Попробуем? Что произойдёт, если мы попробуем выполнить multThree 3 4
в GHCi вместо привязки к имени с помощью ключевого слова let
либо передачи другой функции?
ghci> multThree 3 4
<interactive>:1:0:
No instance for (Show (a –> a))
arising from a use of `print' at <interactive>:1:0–12
Possible fix: add an instance declaration for (Show (a –> a))
In the expression: print it
In a 'do' expression: print it
GHCi сообщает нам, что выражение порождает функцию типа a –> a
, но он не знает, как вывести её на экран. Функции не имеют экземпляра класса Show
, так что мы не можем получить точное строковое представление функций. Когда мы вводим, скажем, 1 + 1
в терминале GHCi, он сначала вычисляет результат (2
), а затем вызывает функцию show
для 2
, чтобы получить текстовое представление этого числа. Текстовое представление 2
– это строка "2"
, которая и выводится на экран.