繁体   English   中英

为什么sum x y是Haskell中的类型(Num a)=> a - > a - > a?

[英]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
  1. =>运算符是什么意思? 它与lambda表达式有什么关系吗? 这就是C#中表示跟随=>运算符的内容是一个的信号。
  2. 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施加约束,以便showplus不接受某些不受支持的输入,例如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的函数,其中axy的类型,并且必须是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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM