Ответ 1
Фактически (+ 2 3)
является ошибкой типа. (+) 2 3
или (+ 2) 3
даст вам 5.
Это поведение очень просто и интуитивно, если вы посмотрите на типы. Чтобы избежать осложнений инфиксных операторов, таких как +
, я собираюсь использовать функцию plus
вместо этого. Я также специализируюсь на plus
, чтобы работать только на Int
, чтобы уменьшить линейный шум типа.
Скажем, что у нас есть функция plus
типа Int -> Int -> Int
. Один из способов прочитать это "функция из двух Int
, которая возвращает Int
". Но эти записи немного неуклюжи для этого чтения, не так ли? Тип возврата не выделяется специально нигде. Почему мы будем писать сигнатуры типа функции таким образом? Поскольку ->
является правильным ассоциативным, эквивалентным типом будет Int -> (Int -> Int)
. Это гораздо больше напоминает выражение "функция от Int
до (функция от Int
до Int
)". Но эти два типа на самом деле точно такие же, и последняя интерпретация является ключом к пониманию того, как это поведение работает.
Haskell рассматривает все функции как от одного аргумента до единственного результата. Могут быть вычисления, которые вы имеете в виду, когда результат вычисления зависит от двух или более входов (например, plus
). Haskell говорит, что функция plus
- это функция, которая принимает один вход и производит выход, который является другой функцией. Эта вторая функция принимает один вход и производит вывод, который является числом. Поскольку вторая функция была вычислена сначала (и будет отличаться для разных входов к первой функции), "окончательный" вывод может зависеть от обоих входов, поэтому мы можем реализовать вычисления с несколькими входами с этими функциями, которые принимают только одиночные входы.
Я обещал, что это будет очень легко понять, если вы посмотрите на типы. Здесь некоторые примеры выражений с их типами явно аннотируются:
plus :: Int -> Int -> Int
plus 2 :: Int -> Int
plus 2 3 :: Int
Если что-то является функцией и вы применяете ее к аргументу, чтобы получить тип результата этого приложения, все, что вам нужно сделать, - удалить все до первой стрелки из типа функции. Если это оставляет тип, у которого больше стрелок, у вас все еще есть функция! Когда вы добавляете аргументы вправо выражения, вы удаляете типы параметров слева от его типа. Тип немедленно позволяет понять, каков тип всех промежуточных результатов, и почему plus 2
- это функция, которая может быть применена (тип имеет стрелку), а plus 2 3
- нет (его тип не имеет стрелка).
"Currying" - это процесс превращения функции из двух аргументов в функцию одного аргумента, которая возвращает функцию другого аргумента, возвращающую возвращаемую исходную функцию. Он также используется для обозначения свойства таких языков, как Haskell, которые автоматически выполняют все функции таким образом; люди скажут, что Haskell "является карриным языком" или "имеет карри", или "имеет карриные функции".
Обратите внимание, что это работает особенно элегантно, потому что синтаксис Haskell для функционального приложения - это простое смещение маркера. Вы можете читать plus 2 3
как приложение plus
до 2 аргументов или приложение plus
to 2
, а затем приложение результата к 3
; вы можете мысленно моделировать его в зависимости от того, что вам больше всего подходит, в то время, когда вы делаете.
В языках с приложением C-like функции в списке аргументов в скобках это немного сокращается. plus(2, 3)
сильно отличается от plus(2)(3)
, а в языках с этим синтаксисом две версии plus
связаны, вероятно, с разными типами. Таким образом, языки с таким синтаксисом, как правило, не имеют всех функций, которые всегда будут в курсе, или даже для автоматического просмотра любой функции, которая вам нравится. Но такие языки исторически также, как правило, не имеют функции как значения первого класса, что делает отсутствие выделки спорным.