В Haskell + - функция, (+ 2) - функция, (+ 2 3) равна 5. Что именно происходит?

  • Как это возможно, что там происходит?

  • Есть ли имя для этого?

  • Какие другие языки имеют такое же поведение?

  • Без сильной системы ввода?

Ответ 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 связаны, вероятно, с разными типами. Таким образом, языки с таким синтаксисом, как правило, не имеют всех функций, которые всегда будут в курсе, или даже для автоматического просмотра любой функции, которая вам нравится. Но такие языки исторически также, как правило, не имеют функции как значения первого класса, что делает отсутствие выделки спорным.

Ответ 2

В Haskell все функции принимают ровно 1 вход и дают ровно 1 вывод. Иногда ввод или вывод функции может быть другой функцией. Ввод или вывод функции также может быть кортежем. Вы можете имитировать функцию с несколькими входами одним из двух способов:

  • Используйте кортеж как входной файл
    (in1, in2) -> out

  • Используйте функцию как вывод *
    in1 -> (in2 -> out)

Аналогично, вы можете имитировать функцию с несколькими выходами одним из двух способов:

  • Используйте кортеж в качестве вывода *
    in -> (out1, out2)

  • Используйте функцию как "второй вход" (a la function-as-output)
    in -> ((out1 -> (out2 -> a)) -> a)

* этот путь обычно поддерживается Haskellers

Функция (+) имитирует взятие двух входов в типичном способе Haskell для создания функции как выхода. (Специализация Int для удобства общения:)
(+) :: Int -> (Int -> Int)

Для удобства -> является право-ассоциативным, поэтому сигнатура типа для (+) также может быть записана (+) :: Int -> Int -> Int


(+) - это функция, которая принимает число и производит другую функцию от числа к числу.

(+) 5 является результатом применения (+) к аргументу 5, поэтому он является функцией от числа до числа.

(5 +) - это еще один способ написать (+) 5

2 + 3 - это еще один способ записи (+) 2 3. Функциональное приложение лево-ассоциативное, поэтому это еще один способ написания (((+) 2) 3). Другими словами: Примените функцию (+) к входу 2. Результатом будет функция. Возьмите эту функцию и примените ее к входу 3. Результатом этого является число.

Следовательно, (+) является функцией, (5 +) является функцией, а (+) 2 3 является числом.

Ответ 3

В Haskell вы можете взять функцию из двух аргументов, применить ее к одному аргументу и получить функцию одного аргумента. На самом деле, строго говоря, + не является функцией двух аргументов, это функция одного аргумента, которая возвращает функцию одного аргумента.

Ответ 4

  • В условиях неспециалиста + является фактической функцией и ожидает получения определенного количества параметров (в данном случае 2 или более) до тех пор, пока оно не вернется. Если вы не дадите ему два или более параметров, то он останется функцией, ожидающей другого параметра.
  • Он называется Currying
  • Множество функциональных языков (Scala, схема и т.д.)
  • Большинство функциональных языков строго типизированы, но это хорошо в конце, потому что это уменьшает ошибки, которые хорошо работают в корпоративных или критических системах.

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