为什么总和xy是类型(数字a)=> a
我一直在阅读关于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
一个约束,以便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
的形式写入,我们将不得不引入很多lambda表达式:
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
a的参数并返回一个类型为Num -> Num
的函数。
实际上,这导致了咖啡/部分功能。
在Haskell这样的函数式语言中,currying的概念是必不可少的,因为你会想要做如下的事情:
map (sum 5) [1, 2, 3, 5, 3, 1, 3, 4] -- note: it is usually better to use (+ 5)
在该代码中,(总和5)是一个接受单个参数的函数,该函数(总和5)将针对列表中的每个项目被调用,例如((sum5)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
”。 为了有意义,你需要明白sum xy
解析为(sum x) y
。
换句话说:你首先用参数x
调用sum
。 然后您返回一个新类型a -> a
函数。 然后你用参数y
调用该函数,现在你得到一个类型a
a的函数,其中a
是x
和y
的类型,并且必须是Num
类型类型的实例。
如果你想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]