Почему сумма x y имеет тип (Num a) => a → a → a в Haskell?

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

Скажем, я определяю функцию sum:

let sum x y = x + y

если я запрошу Haskell для его типа

:t sum

Я получаю

sum :: (Num a) => a -> a -> a
  • Что означает оператор =>? Имеет ли это какое-либо отношение к лямбда-выражениям? Это означает, что то, что следует за оператором =>, является одним, в С#.
  • Что означает a -> a -> a? При проверке глаз на множестве различных функций, которые я тестировал, кажется, что начальные a -> a являются аргументами, а окончательный -> a является результатом функции sum. Если это правильно, почему бы не что-то как (a, a) -> a, что кажется более интуитивным?

Ответ 1

0. Haskell => не имеет ничего общего с С# =>. В Haskell анонимная функция создается с помощью

\x -> x * x

Кроме того, не называйте функцию sum, потому что такая функция уже существует в Prelude. Позвольте называть его plus отныне, чтобы избежать путаницы.

1. В любом случае => в Haskell предоставляет контекст для этого типа. Например:

show :: (Show a) => a -> String

Здесь тип Show a => означает a должен быть экземпляром класса type Show, что означает, что a должен быть преобразован в строку. Аналогично, (Num a) => a -> a -> a означает, что тип a должен быть экземпляром класса типа Num, что означает, что a должен быть как число. Это ставит ограничение на a, так что Show или plus не будет принимать некоторые неподдерживаемые данные, например. plus "56" "abc". (Строка не похожа на число.)

Класс типа похож на интерфейс С# или, более конкретно, ограничение базового типа интерфейса в общих файлах. Для получения дополнительной информации см. Вопрос Объясните типы классов в Haskell.

2. a -> a -> a означает a -> (a -> a). Поэтому на самом деле это унарная функция, которая возвращает другую функцию.

plus x = \y -> x + y

Это делает частичное приложение (currying) очень простым. Частичное применение используется много, особенно. при использовании функций более высокого порядка. Например, мы могли бы использовать

map (plus 4) [1,2,3,4]

добавить 4 к каждому элементу списка. Фактически мы могли бы снова использовать частичное приложение для определения:

plusFourToList :: Num a => [a] -> [a]
plusFourToList = map (plus 4)

Если функция по умолчанию записана в форме (a,b,c,...)->z, нам нужно будет ввести много lambdas:

plusFourToList = \l -> map(\y -> plus(4,y), l) 

Ответ 2

Это потому, что

Каждая функция в Haskell принимает единственный параметр и возвращает одно значение

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

Если мы добавим круглые скобки, сигнатура функции станет:

sum :: (Num a) => a -> (a -> a)

В Haskell сигнатура функции: A -> B означает функцию "домен" функции A, а "Codomain" функции - B; или на языке программиста, функция принимает параметр типа A и возвращает значение типа B.

Следовательно, определение функции sum :: Num -> (Num -> Num) означает, что сумма представляет собой "функцию, которая принимает параметр типа A и возвращает функцию типа Num -> Num".

По сути, это приводит к карри/частичной функции.

Концепция currying необходима в функциональных языках, таких как Haskell, потому что вы захотите сделать что-то вроде:

map (sum 5) [1, 2, 3, 5, 3, 1, 3, 4]  -- note: it is usually better to use (+ 5)

В этом коде (сумма 5) есть функция, которая принимает один параметр, эта функция (сумма 5) будет вызываться для каждого элемента в списке, например. ((сумма 5) 1) возвращает 6.

Если sum имела подпись sum :: (Num, Num) -> Num, тогда сумма должна была бы получить оба параметра одновременно, потому что теперь сумма является "функцией, которая получает tuple (Num, Num) и возвращает число".

Теперь, второй вопрос, что означает Num a => a -> a? Это в основном краткое изложение того, что каждый раз, когда вы видите A в сигнатуре, замените его на Num или с одним из его производного класса.

Ответ 3

Num a => означает, что "в следующем случае a должен ссылаться на тип, который является экземпляром typeclass Num" (который похож на интерфейс для типов номеров).

Оператор => отделяет "ограничения типа текста" от "тела" этого типа. Это как оператор where для общих ограничений в С#. Вы можете прочитать его как логическое выражение типа "если a является числовым типом, тогда sum может использоваться с типом a -> a -> a".

a -> a -> a означает "функция, которая принимает a и возвращает функцию, которая принимает a и возвращает a". Для этого вам нужно понять, что sum x y анализирует как (sum x) y.

Другими словами: вы сначала вызываете sum с аргументом x. Затем вы возвращаете новую функцию типа a -> a. Затем вы вызываете эту функцию с аргументом y, и теперь вы возвращаете функцию типа a, где a - это тип x и y и должен быть экземпляром класса Num typeclass.

Если вы хотите, чтобы sum имел тип Num a => (a,a) -> a, вы можете определить его как sum (x,y) = x+y. В этом случае у вас есть функция, которая берет кортеж, содержащий два a и возвращает a (где a снова является экземпляром класса Num).

Однако стиль "карри" (функции, возвращающие функции для имитации нескольких параметров) гораздо чаще используется, чем стиль кортежа, поскольку он позволяет вам легко частично применять функции. Пример map (sum 5) [1,2,3]. Если вы определили sum с кортежем, вам нужно сделать map (\y -> sum 5 y) [1,2,3].

Ответ 4

это a -> a -> a, а не (a, a) -> a из-за currying. Интересный факт: карри был изобретен Haskell Curry! В основном это означает, что если вы укажете один аргумент, вы вернете еще одну функцию типа a -> a, частичное применение суммы.

Ответ 5

Документация Haskell не слишком ясна, но (Num a) = > означает, что функция работает для всех случаев, когда a является Num или происходит от него (поэтому это число).

Также см.: http://www.cse.unsw.edu.au/~en1000/haskell/inbuilt.html