我刚刚开始学习Haskell并且正在阅读“Learnyouahaskell”一书。我遇到过这个例子
tell :: (Show a) => [a] -> String
tell [] = "The list is empty"
我理解(Show a)
这里是一个类约束,参数的类型,在这种情况下a
必须能够“显示”。
考虑到a
这里是列表而不是列表的元素,为什么我无法声明这样的函数: -
tell :: (Show a) =>a->String
编辑1: - 从下面的答案中我似乎明白,需要为模式匹配指定a
的具体类型。考虑到这一点,下面的正确实现是什么: -
pm :: (Show a) =>a->String
pm 'g'="wow"
它给出了如下错误
Could not deduce (a ~ Char)
from the context (Show a)
bound by the type signature for pm :: Show a => a -> String
at facto.hs:31:7-26
`a' is a rigid type variable bound by
the type signature for pm :: Show a => a -> String at facto.hs:31:7
In the pattern: 'g'
In an equation for `pm': pm 'g' = "wow"
失败,模块加载:无。
我从错误消息中了解到它无法推断a
的具体类型,但是如何使用Show
来声明它。
我知道我可以这样解决上述问题: -
pmn :: Char->String
pmn 'g'="wow"
但我只是想正确理解Show
类型类
答案 0 :(得分:3)
在这两个签名中,a
不是一个列表 - 它根本就是任何类型,你不能选择哪个(除了它必须是Show
的一个实例)。
在
tell₁ :: Show a => [a] -> String
tell₁ [] = "The list is empty"
... -- (remember to match the non-empty list case too!)
您在a
列表中匹配,而不是a
类型的值。
如果你写了
tell₂ :: Show a => a -> String
tell₂ [] = "The list is empty"
...
你会假设类型a
是列表的类型(某些东西)。但它可以是任何类型,例如Bool
。
(但我可能不理解你的问题 - 你还没有真正说出问题所在。当问这样的问题时,你应该通常指明你做了什么,你期望什么,以及发生了什么。这些都没有在这里指定,因此人们只能猜测你的意思。)
答案 1 :(得分:3)
问题不在于Show
。确实,如果我们尝试:
tell2 :: a -> String
tell2 [] = "The list is empty"
我们收到类型检查错误。让我们看看它的内容:
test.hs:5:7:
Couldn't match expected type `a' with actual type `[t0]'
`a' is a rigid type variable bound by
the type signature for tell2 :: a -> String at test.hs:4:10
In the pattern: []
In an equation for `tell2': tell2 [] = "The list is empty"
现在我们问自己,这个所谓的“类型”结构究竟意味着什么?当您撰写tell2 :: a -> String
时,您要说的是,对于完全 a的任何类型,tell2
会给我们String
。 [a]
(或[c]
或[foo]
- 名称无关紧要)不是完全 a
。这似乎是一种随意的区分,据我所知,它是。让我们看看当我们写
tell2 [] = "The list is empty"
> :t tell2
> tell2 :: [t] -> [Char]
众所周知,编写t
和a
之间没有区别,[Char]
只是String
的类型同义词,所以我们编写的类型和类型GHC推断 相同 。
嗯,不太好。当您自己,程序员在源中手动指定函数类型时,类型签名中的类型变量将变为 rigid 。这究竟意味着什么?
来自https://research.microsoft.com/en-us/um/people/simonpj/papers/gadt/:
“而不是”用户指定的类型“,我们使用简短的术语刚性 用于描述完全指定的类型的类型 直接时尚,由程序员提供的类型注释。“
因此,刚性类型是程序员类型签名指定的任何类型。 所有其他类型都是“摇摆不定”[1]
所以,只是因为你把它写出来,类型签名变得不同了。在这种新型语法中,我们有a /= [b]
。对于刚性类型签名,GHC将推断它可以获得的信息量最少。它必须从模式绑定中推断出a ~ [b]
;但是它不能从您提供的类型签名中进行推断。
让我们看看GHC为原始函数提供的错误:
test.hs:2:6:
Could not deduce (a ~ [t0])
from the context (Show a)
bound by the type signature for tell :: Show a => a -> String
at test.hs:1:9-29
`a' is a rigid type variable bound by
我们再次看到rigid type variable
等,但在这种情况下,GHC还声称它无法推断出某些东西。 (顺便说一下 - 类型语法中的a ~ b === a == b
)。类型检查器实际上是在类型中查找使函数有效的约束;它没有找到它,并且很好地告诉你它确实需要什么来使它有效:
{-# LANGUAGE GADTs #-}
tell :: (a ~ [t0], Show a) => a -> String
tell [] = "The list is empty"
如果我们逐字插入GHC的建议,则进行类型检查,因为现在GHC不需要进行任何推断;我们已经准确地告诉了a
是什么。
答案 2 :(得分:3)
List确实实现了Show
类型类,但当你说:Show a => a -> String
这意味着该函数将接受任何实现Show
的类型。最重要的是,你只能调用show class functions on a没有别的,你的函数永远不会知道a
的具体类型。而你试图在a
有关新编辑的更新:
正确的实施方式是:pm c ="wow"
。您可以在参数Show
上调用任何c
类型类函数。你不能像以前那样模式匹配,因为你不知道参数的确切类型,你只知道它实现了Show
类型类。但是当您将Char
特定为类型时,模式匹配将起作用
答案 3 :(得分:1)
只要你在'g'上模式匹配,例如
pm 'g' = "wow"
您的函数不再具有(Show a) => a -> String
类型;相反,它具有'a'的具体类型,即Char,因此它变为Char -> String
这与你给它的显式类型签名直接冲突,它表明你的函数适用于任何类型'a'(只要该类型是Show
的实例)。
在这种情况下你不能模式匹配,因为你在Int,Char等上进行模式匹配。但你可以使用Prelude中的show
函数:
pm x = case show x of
"'g'" -> "My favourite Char"
"1" -> "My favourite Int"
_ -> show x
正如您可能已经猜到的那样,show
有点神奇;)。实际上为每个类型实现了一大堆show
函数,它们是Show
类型类的实例。
答案 4 :(得分:1)
tell :: (Show a) =>a->String
这表示tell
接受可显示的任何类型a
的值。您可以在任何可显示的内容上调用。这意味着在tell
的实现中,你必须能够对任何事物进行操作(可以显示)。
您可能认为这对于该类型签名来说是一个好的实现:
tell [] = "The list is empty"
因为列表确实可以显示,所以第一个参数的有效值也是如此。但在那里我正在检查参数是否为空列表;只有列表类型的值可以与列表模式匹配(例如空列表模式),所以如果我调用tell 1
或tell True
或tell (1, 'c')
这没有意义等等。
在tell
内,该类型a
可以是Show
实例的任何类型。因此,我对该值唯一能做的就是与{em>所有类型有效的事情,这些类型是Show
的实例。这基本上意味着您只能将其传递给具有通用Show a => a
参数的其他类似函数。 1
您的混淆源于这种误解“考虑到这里是一个列表,而不是列表的元素”关于类型签名tell :: (Show a) => [a] -> String
。这里a
实际上是列表的一个元素,而不是列表本身。
该类型签名读取“tell需要一个参数,这是一个可显示类型的列表,并返回一个字符串”。这个版本的tell
知道它接收一个列表,所以它可以用它的参数做一些列表。这是里面列表中的东西,它们是某种未知类型的成员。
1 除了将值传递给另一个Show
函数之外,其中大多数函数也无法对值进行任何操作,但迟早会忽略该值或者传递给Show
类型类中的一个实际函数;这些都有针对每种类型的专门实现,因此每个专用版本都可以知道它正在运行的类型,这是最终可以完成的唯一方式。