[英]Why sum x y is of type (Num a) => a -> a -> a in Haskell?
我一直在阅读关于Haskell的内容,我很难理解如何用这种语言处理函数定义。
假设我正在定义一个sum
函数:
let sum x y = x + y
如果我查询Haskell的类型
:t sum
我明白了
sum :: (Num a) => a -> a -> a
=>
运算符是什么意思? 它与lambda表达式有什么关系吗? 这就是C#中表示跟随=>
运算符的内容是一个的信号。 a -> a -> a
是什么意思? 通过对我一直在尝试的许多不同函数进行眼睛检查,似乎最初的a -> a
是参数而最终-> a
是sum函数的结果。 如果这是正确的,为什么不能作为(a, a) -> a
,这似乎更直观? 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
类型必须是类型次数,这意味着的实例a
必须是像一个数字。 这会对a
施加约束,以便show
或plus
不接受某些不受支持的输入,例如plus "56" "abc"
。 (字符串不像数字。)
类型类与C#的接口类似,或者更具体地说,类似于泛型中的接口基类型约束 。 有关详细信息,请参阅Haskell中的Explain Type Classes问题。
2. a -> a -> a
表示a -> (a -> a)
。 因此,它实际上是一个返回另一个函数的一元函数。
plus x = \y -> x + y
这使得部分应用(currying)非常容易。 部分应用程序使用很多esp。 使用高阶函数时。 例如,我们可以使用
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)
这是因为
Haskell中的每个函数都接受一个参数并返回单个值
如果一个函数需要取多个值,那么该函数将是一个curried函数,或者它必须采用一个元组 。
如果我们添加括号,函数签名将变为:
sum :: (Num a) => a -> (a -> a)
在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)
在该代码中,(sum 5)是一个采用单个参数的函数,将为列表中的每个项调用此函数(sum 5),例如((sum 5)1)返回6。
如果sum
具有sum :: (Num, Num) -> Num
的签名,那么sum必须同时接收它的两个参数,因为现在sum是一个“接收tuple (Num, Num)
并返回的函数一个数字“。
现在,第二个问题, Num a => a -> a
是什么意思? 它基本上是一种简写,表示每次在签名中看到a
时,用Num或其派生类替换它。
Num a =>
表示“在下面, a
表示类型为Num
的实例的类型”(有点像数字类型的接口)。
=>
运算符将“类型类约束”与类型的“主体”分开。 它有点像C#中泛型约束的where
运算符。 您可以将其视为逻辑含义,例如“如果a
是数字类型,则sum
可以与类型a -> a -> a
一起使用a -> a -> a
”。
a -> a -> a
表示“一个函数,它接受a
并返回一个函数,它接受a
并返回a
”。 为了理解这一点,你需要理解sum xy
解析为(sum x) y
。
换句话说:你首先用参数x
调用sum
。 然后,您将返回类型a -> a
a的新函数a -> a
。 然后使用参数y
调用该函数,现在返回类型a
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]
。
它是a -> a -> a
而不是(a, a) -> a
因为currying 。 有趣的事实:Haskell Curry(重新)发明了Currying! 基本上它意味着如果你提供一个参数,你将得到另一个类型为a -> a
函数a -> a
,sum的部分应用。
Haskell文档对它不太清楚,但是(Num a)=>意味着该函数适用于a是Num或从中派生的所有情况(因此是一个数字)。
另见: http : //www.cse.unsw.edu.au/~en1000/haskell/inbuilt.html
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.