Почему сумма x y имеет тип (Num a) => a -> a -> a в Haskell? - PullRequest
10 голосов
/ 18 сентября 2010

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

Допустим, я определяю функцию sum:

let sum x y = x + y

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

:t sum

Я получаю

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

Ответы [ 5 ]

29 голосов
/ 18 сентября 2010

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

\x -> x * x

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

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

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

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

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

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

plus x = \y -> x + y

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

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

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

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

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

plusFourToList = \l -> map(\y -> plus(4,y), l) 
10 голосов
/ 18 сентября 2010

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

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

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

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

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

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

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

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

Концепция карри важна в функциональных языках, таких как 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, то sum пришлось бы получать оба параметра одновременно, потому что теперь sum является «функцией, которая получает tuple (Num, Num) и возвращает Num».

Теперь, второй вопрос, что означает Num a => a -> a? По сути, это сокращение, чтобы сказать, что каждый раз, когда вы видите a в подписи, заменяйте его на Num или на один из его производных классов.

5 голосов
/ 18 сентября 2010

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

Оператор => отделяет «ограничения класса типов» от «тела» типа.Это похоже на оператор where для общих ограничений в C #.Вы можете прочитать это как логическое следствие, например, «если 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..

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

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

4 голосов
/ 18 сентября 2010

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

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

4 голосов
/ 18 сентября 2010

это a -> a -> a, а не (a, a) -> a из-за карри .Интересный факт: карри был (ре) изобретен Хаскеллом Карри!По сути, это означает, что если вы предоставите один аргумент, вы получите другую функцию типа a -> a, частичное применение суммы.

...