"->"符号和"=>"符号有什么区别?它们分别代表什么含义?

13
在Haskell中,当我们谈论类型声明时,我看到过->=>两种符号。例如:我可以自己定义类型声明。
addMe :: Int -> Int -> Int
addMe x y = x + y

它能够正常工作。

但是如果我们查看:t sqrt,我们会得到:

sqrt :: Floating a => a -> a

我们什么时候使用=>,什么时候使用->? 在什么情况下使用"fat arrow",在什么情况下使用"thin arrow"?


4
=> 是用于表示类型约束的符号,箭头 (->) 则是函数类型构造器。 - Willem Van Onsem
@WillemVanOnsem,如果我想让我的addMe函数带有类型约束,我该怎么做? - user3088035
另请参见尝试理解Haskell的=>与定义类型 - user11228628
3个回答

22
->用于显式函数。即当f可以写成f x形式的表达式时,签名必须有其中一个箭头。具体来说,x参数)的类型必须出现在->箭头的左侧。
最好一开始不要将=>视为函数箭头。它在逻辑意义上是一个蕴含箭头:如果a是一个具有Floating a 属性的类型,则可以得出sqrt的签名是a -> a
对于您的addMe示例,它是一个具有两个参数的函数,其签名必须始终采用x -> y -> z的形式。可能还可以在前面加上q =>;这不影响函数性质,但可能会对允许使用的特定类型产生影响。通常,如果类型已经固定和具体,则不需要这样的约束。例如,您可以原则上对Int施加约束:
addMe :: Num Int => Int -> Int -> Int
addMe x y = x + y

...但是这并没有实际带来任何成效,因为每个人都知道特定类型的 Int 是数字类 Num 的一个实例。你需要这样的约束条件的情况是当类型不是固定的而是一个类型变量(小写字母),也就是说函数是多态的时候。你不能只写

addMe' :: a -> a -> a
addMe' x y = x + y
因为那个签名会暗示该函数适用于任何类型a,但它不能适用于所有类型(例如,你如何添加两个字符串?好吧,也许不是最好的例子,但你如何两个字符串?)
因此,你需要加上约束条件。
addMe' :: Num a => a -> a -> a
addMe' x y = x + y

这意味着,您不关心确切的类型a是什么,但需要它是一个数值类型。任何人都可以使用自己的类型MyNumType调用该函数,但他们需要确保满足Num MyNumType:然后就会得出 addMe' 的签名为MyNumType -> MyNumType -> MyNumType

要实现这一点,可以使用您知道是数值类型的标准类型,例如addMe' 5.9 3.7 :: Double将起作用,或者为您的自定义类型和Num提供一个实例声明。只有当您确定这是一个好主意时才执行后者;通常,标准的num类型就足够了。


请注意,签名中可能看不到箭头:type IntEndofunc = Int -> Int时,当f :: IntEndofunc; f x = x+x时是可以的。但是,您可以认为typedef本质上只是语法包装器;它仍然是相同的类型,其中确实包含箭头。

恰好这样,逻辑蕴含和函数应用可以看作是同一数学概念的两个方面。此外,GHC实际上将类约束实现为函数参数,称为字典。但所有这些都是在幕后进行的,因此如果有什么东西,它们就是隐式函数。在标准Haskell中,您永远不会看到=>类型的LHS作为某个实际参数的类型来应用该函数。


1
几个月前,我遇到了一个令我惊讶的事实,即使用RankNTypes,匹配并立即重新应用newtype构造函数可能不是无操作。我记不清细节了,但它与类型参数的更改有关,导致在newtype下类约束的类型发生变化,因此包装值必须使用传递的字典进行实际更改。如果您感兴趣,我可以找出来;我相信它是在被拒绝的primitive PR中,并且与遍历数组有关。 - dfeuer

13

"细箭头"用于函数类型(t1 -> t2 是一个函数类型,它接受类型为t1的值并生成类型为t2的值)。

"粗箭头"用于类型约束。它将多态函数的类型约束列表与其余类型分开。因此,给定 Floating a => a -> a,我们有函数类型 a -> a,即可以使用任何类型 a 的参数并生成相同类型的结果的函数类型,添加了约束条件 Floating a,这意味着该函数实际上只能与实现了 Floating 类型类的类型一起使用。


3
或许一个更简单的例子是:show :: Show a => a -> String 可以将任意类型 a 的值转换为字符串,但仅当类型 a 实现了 Show 接口时才能执行转换。 - melpomene
假设我想更改我的addMe函数,使其只接受浮点数,我该怎么做? - user3088035
@John 只有 Float 类型或者任何实现了 Floating 接口的类型吗? - sepp2k
只是两个浮点数,然后相加并返回。就像我的addMe函数已经在做的那样。我只是想尝试为我的函数指定类型。 :) - user3088035
2
@John,我不理解那个回答,所以我会回答两种可能性。如果要添加两个Float,类型应该是addMe :: Float -> Float -> Float。如果要使用任何实现了Floating的类型,那么它将是addMe :: Floating a => a -> a -> a - sepp2k

0

-> 是函数的构造器,=> 用于约束,类似于 Haskell 中称为类型类的“接口”。

以下是一个小例子:

sum :: Int -> Int -> Int
sum x y = x + y

该函数仅允许Int类型,但如果您想要一个巨大的int或小型的int,您可能需要使用Integer,并且如何告诉它同时使用两者?

sum2 :: Integral a => a -> a -> a
sum2 x y = x + y

现在如果你尝试这样做:

sum2 3 1.5

它会给你一个错误

另外,你可能想知道两个数据是否相等,你需要:

equals :: Eq a => a -> a -> Bool
equals x y = x == y

现在如果你这样做:

3 == 4

没问题

但如果你创建:

data T = A | B

equals A B

它将会给你:

error:
    • No instance for (Eq T) arising from a use of ‘equals’
    • In the expression: equals A B
      In an equation for ‘it’: it = equals A B

如果你想让它工作,你只需要做这个:
data T = A | B deriving Eq

equals A B

False

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接