Haskell 类型声明

3
在Haskell中,为什么以下代码可以通过编译:
splice :: String -> String -> String
splice a b = a ++ b
main = print (splice "hi" "ya")

但这个并不行:
splice :: (String a) => a -> a -> a
splice a b = a ++ b
main = print (splice "hi" "ya")

>> Type constructor `String' used as a class

我本以为这两个东西是一样的。有没有办法使用第二种样式,避免重复三次类型名称?

5个回答

13

=> 语法用于类型类(typeclasses)。

当你写 f :: (Something a) => a 时,你并不是在说 a Something,而是在说它是一个属于 Something 类型的类型。

例如,Num 是一个类型类,其中包括诸如 IntFloat 等类型。 但是,Num 并不是一个类型,因此我不能这样说:

f :: Num -> Num
f x = x + 5

但是,我可以说

f :: Int -> Int
f x = x + 5
或者
f :: (Num a) => a -> a
f x = x + 5

1
我明白了,那么关键的区别在于,在我的代码中,String是一种类型而不是类型类。感谢您为我澄清这个区别。我原以为它们可以互换使用。 - Magnus

3
实际上,这是可能的:
Prelude> :set -XTypeFamilies
Prelude> let splice :: (a~String) => a->a->a; splice a b = a++b
Prelude> :t splice
splice :: String -> String -> String

这里使用了等式约束~。但我建议不要使用它,因为这并没有比直接写String -> String -> String更短,并且更难理解,对编译器的解析也更复杂。

你能解释一下“更难于编译器解决”是什么意思吗?这听起来不太对。 - Daniel Wagner
@DanielWagner:说实话,我不知道编译器有多难。我只是感觉这不仅仅是一个本地的 type A = String 同义词。 - leftaroundabout
1
如果您能消除FUD,我会更愿意投票支持这个答案。 - Daniel Wagner

3

有没有办法使用第二种方式,避免重复3次类型名称?

为了简化类型签名,您可以使用类型同义词。例如,您可以编写

type S = String
splice :: S -> S -> S

或类似于某种东西
type BinOp a = a -> a -> a
splice :: BinOp String

然而,对于像 String -> String -> String 这样简单的事情,我建议直接打出来。类型同义词应该用于使类型签名更易读,而不是更难懂。
在这个特定的情况下,您还可以将您的类型签名泛化为
splice :: [a] -> [a] -> [a]

由于它根本不依赖于元素是字符,因此可以实现。


另一个非常有帮助的答案,谢谢!对于我来说,在这个问题上得到了很多有益的答案。 - Magnus

1

嗯... String 是一种类型,而你试图将其用作类。

如果你想要一个多态版本的 splice 函数示例,请尝试:

import Data.Monoid

splice :: Monoid a=> a -> a -> a
splice = mappend

编辑:这里的语法是,出现在=>左侧的大写单词是约束出现在右侧的变量的类型类。右侧所有的大写单词都是类型的名称。


1

实际上,正是那一章引发了我的问题。根据那一章的例子,使用Integral而不是String,我预期上述代码可以编译。 - Magnus
你可以在ghci提示符下尝试这个,以了解这些内容之间的区别。 ghci>:type Integral-这应该会出现错误。 ghci>:info Integral-这将为您提供Integral类的定义。现在尝试使用String。 - Daniel

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