为什么sum和xy在Haskell中的类型(Num a)=> a - > a - > a?

dev*_*ium 10 haskell functional-programming type-inference typeclass

我一直在阅读关于Haskell的内容,我很难理解如何用这种语言处理函数定义.

假设我正在定义一个sum函数:

let sum x y = x + y
Run Code Online (Sandbox Code Playgroud)

如果我查询Haskell的类型

:t sum
Run Code Online (Sandbox Code Playgroud)

我明白了

sum :: (Num a) => a -> a -> a
Run Code Online (Sandbox Code Playgroud)
  1. 这对=>运营商意味着什么?它与lambda表达式有什么关系吗?这就是=>用C#表示运算符后面的内容是一个的信号.
  2. 什么a -> a -> a意思?通过对我一直在尝试的许多不同函数进行眼睛检查,看起来最初a -> a是参数,而final -> a是sum函数的结果.如果这是正确的,为什么不这样(a, a) -> a,这看起来更直观?

ken*_*ytm 29

0哈斯克尔=>无关用C#的=>.在Haskell中,创建了一个匿名函数

\x -> x * x
Run Code Online (Sandbox Code Playgroud)

另外,不要命名该函数,sum因为Prelude中已经存在这样的函数.我们plus从现在开始称它为避免混淆.

1.无论如何,=>在Haskell提供了一个上下文的类型.例如:

show :: (Show a) => a -> String
Run Code Online (Sandbox Code Playgroud)

这里,Show a =>means a类型必须是类类 的实例Show,这意味着a必须可以转换为字符串.同样,(Num a) => a -> a -> a表示a类型必须是类Num的实例,这意味着a必须像数字一样.这会产生约束,a以便接受showplus不接受某些不受支持的输入,例如plus "56" "abc".(字符串不像数字.)

类型类与C#的接口类似,或者更具体地说,类似于泛型中接口基类型约束.有关详细信息,请参阅Haskell中的Explain Type Classes问题.

2. a -> a -> a手段a -> (a -> a).因此,它实际上是一个返回另一个函数的一元函数.

plus x = \y -> x + y
Run Code Online (Sandbox Code Playgroud)

这使得部分应用(currying)非常容易.部分应用程序使用很多esp.使用高阶函数时.例如,我们可以使用

map (plus 4) [1,2,3,4]
Run Code Online (Sandbox Code Playgroud)

添加4到列表的每个元素.实际上我们可以再次使用部分应用来定义:

plusFourToList :: Num a => [a] -> [a]
plusFourToList = map (plus 4)
Run Code Online (Sandbox Code Playgroud)

如果函数(a,b,c,...)->z默认写在表单中,我们必须引入很多lambdas:

plusFourToList = \l -> map(\y -> plus(4,y), l) 
Run Code Online (Sandbox Code Playgroud)

  • 很好的答案,除了当我听到有人说类型类就像接口类时,我总是畏缩.他们有一个类似的角色,但从这个类比推理将导致你严重误入歧途.特别是在Haskell中,类型类本身不是类型,因此您不能将"[Num]"写为数字列表. (2认同)
  • 虽然写作(Num a)=> [a]并不是真的那么不同.我相信,只要您明确说明界面类比只是一个简单的帮助您开始,它是可以的. (2认同)

Lie*_*yan 10

这是因为

Haskell中的每个函数都接受一个参数并返回单个值

如果一个函数需要取多个值,那么该函数将是一个curried函数,或者它必须采用一个元组.

如果我们添加括号,函数签名将变为:

sum :: (Num a) => a -> (a -> a)
Run Code Online (Sandbox Code Playgroud)

在Haskell中,函数签名:A -> B表示函数的"域"和函数A的"Codomain"的函数B; 或者以程序员的语言,该函数接受一个类型的参数A并返回一个类型的值B.

因此,函数定义sum :: Num -> (Num -> Num)意味着sum是"取一个类型参数a并返回类型函数的函数Num -> Num".

实际上,这导致了currying/partial功能.

currying的概念在像Haskell这样的函数式语言中是必不可少的,因为你会想要做以下事情:

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

在该代码中,(sum 5)是一个采用单个参数的函数,将为列表中的每个项调用此函数(sum 5),例如((sum 5)1)返回6.

如果sum有签名sum :: (Num, Num) -> Num,则sum必须同时接收它的两个参数,因为现在sum是"接收a tuple (Num, Num)并返回Num的函数".

现在,第二个问题,Num a => a -> a意味着什么?它基本上是一个简写,表示每次a在签名中看到,用Num或其派生类替换它.

  • 你有点滥用这个符号:`Num`是类型类,而不是类型. (2认同)

sep*_*p2k 5

Num a =>表示"在下面,a应引用作为类型类实例的类型Num"(它有点像数字类型的接口).

=>操作者从所述类型的"体"分开"类型类约束".它有点像whereC#中泛型约束的运算符.您可以将其视为逻辑含义,例如"if a是数字类型,然后sum可以与类型一起使用a -> a -> a".

a -> a -> a表示"一个函数,它接受a并返回一个函数,该函数接受a并返回a".为了理解这一点,你需要理解sum x y解析为(sum x) y.

换句话说:你先sum用参数调用x.然后,您将返回一个新的类型函数a -> a.然后,您拨打的说法是功能y,现在你回来类型的函数a,其中a是类型xy和必须的一个实例Num的类型类.

如果您想sum拥有类型Num a => (a,a) -> a,可以将其定义为sum (x,y) = x+y.在这种情况下,你有一个函数,它接受一个包含两个as 的元组并返回一个a(其中a又是一个Num类型类的实例).

然而,"咖喱风格"(函数返回函数来模拟多个参数)比元组样式更常用,因为它允许您轻松地部分应用函数.例子map (sum 5) [1,2,3].如果你已经定义sum了一个元组,你必须这样做map (\y -> sum 5 y) [1,2,3].