Главная » Haskell » Типы функций

0

Необходимо отметить, что понимание функций в функциональном программировании достаточно серьёзно  отличается от их восприятия в императивных языках. Дело в том, что в рамках функциональной парадигмы функция является программной сущностью, которая  обладает типом, является объектом, над которым можно производить действия: во-первых,  передавать в другие функции в качестве фактического значения; а во-вторых, возвращать в качестве вычисленного значения. Такое положение вещей является прямым следствием из принятой модели типизации языка Haskell (статическая типизация Хиндли-Милнера), в рамках которой у функций имеются типы.

2.4.1.       Функции  как  программные сущности с типом

Каждая функция в языке Haskell имеет определённый тип. Такой тип функций уже несколько раз встречался в тексте этого справочника в описаниях сигнатур функций, которые  обычно  стоят  перед определением функции. Строка, в которой имеются символы (::), и есть сигнатура, то есть определение (и ограничение) типа функции.

Для   формализации типов  функций   используется   единственная операция: (->).  Она связывает типы  аргументов  (формальных параметров) и тип возвращаемого результата. Тип функции определяется на основании следующих простых правил:

1)                        Константная функция без формальных параметров имеет тип, равный типу возвращаемого результата. Это вполне логично, ибо такая функция по своей сути является просто константой.

2)                        Функция   с  одним  аргументом  имеет тип  (ArgType  ->  ResType),  где ArgType —  тип единственного аргумента, а  ResType  — тип возвращаемого результата.

3)                        Функция с n аргументами имеет тип (Arg1Type ->  (… ->  (ArgNType -> ResType)…)). Эта запись показывает, что операция (->) правоассоциативна, а поэтому лишние скобки можно опустить для удобства представления и восприятия: Arg1Type ->  … ->  ArgNType ->  ResType.

В  качестве  примера сигнатур  функций  можно привести такие  сигнатуры из стандартного модуля Prelude  для некоторых интересных функций и бинарных операций:

(.) :: (b  ->  c) ->  (a  ->  b)  ->  (a  ->  c) flip :: (a  ->  b ->  c)  ->  b ->  a ->  c

($) :: (a  ->  b)  ->  a ->  b

Операция (.) используется для композиции функций в полном соответствии с математическим  определением термина «композиция» (подробно описывается на стр. 171). Функция flip получает на вход функцию двух аргументов и два значения, а возвращает значение переданной на вход первым параметром функции на переданных же аргументах, но в обратном порядке (описывается на стр. 133). Операция  ($) является синонимом операции применения функции к своим аргументам, но имеет самый низкий приоритет, а потому может  использоваться для сшивания последовательных применений разных функций для того, чтобы использовать поменьше скобок (детально описывается также на стр. 171).

Как  видно, в приведённых типах функций очень важное значение имеет заключение определённых частей типов в круглые скобки. Уже было сказано, что в силу правой ассоциативности операции (->)  лишние скобки можно опустить. Но если имеется необходимость расставить скобки иным образом, это необходимо указывать явно. В каких же случаях необходимо такое явное указание? В случаях, если формальными аргументами функций являются другие функции, ибо операция (->)  используется только для создания функциональных типов. Этот вопрос самым детальным образом прорабатывается ниже в разделе 2.5.3..

Здесь остаётся отметить, что сигнатура типа функции является ничем иным, как ограничением на тип. Символ (::) может использоваться именно как ограничитель типов, при помощи которого можно ограничить тип функции (или любой иной программной  сущности). Это иногда необходимо делать в целях оптимизации, а  можно и ограничивать типы для решения определённой задачи. Всё это связано с тем, что механизм вывода типов, как это доказано теоретически, выводит наиболее общий тип, стремясь сделать его полиморфным.

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

power x  0 = 1

power x  y  = x  * power x  (y 1)

Если записать это определение без сигнатуры, то транслятор языка Haskell автоматически  выведет тип этой функции в виде:

power :: (Num a, Num  b)  => a ->  b ->  a

Это значит, что типы обоих аргументов (a и b соответственно) могут не совпадать, но при этом оба типа должны быть экземплярами класса Num, то есть таких величин, над которыми можно производить арифметические операции (подробно о классах типов и их экземплярах написано в главе 3.).

Однако  если имеется необходимость  ограничить  использование  функции только типом Int (ограниченные  целые числа) для обоих аргументов, то можно явно записать:

power :: Int  ->  Int ->  Int power x  0 = 1

power x  y  = x  * power x  (y 1)

В этом случае транслятор языка Haskell будет в своей работе использовать только этот тип функции power, что позволит, в свою очередь, довольно  существенно её оптимизировать.

Источник: Душкин Р. В., Справочник по языку Haskell. М.: ДМК Пресс, 2008. 544 с., ил.

По теме:

  • Комментарии