3

我刚刚开始学习 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正确理解类型类

4

5 回答 5

3

List 确实实现了Show类型类,但是当您说:Show a => a -> String这意味着该函数将接受任何实现的类型,Show并且最重要的是,您只能在其他方面调用 show 类函数,您的函数永远不会知道a. 而您正在尝试调用列表模式匹配a

更新有问题的新编辑:

正确的实现是:pm c ="wow". 您可以在 parameter 上调用任何Show类型的类函数c。您无法像之前尝试的那样进行模式匹配,因为您不知道参数的确切类型,您只知道它实现了Show类型类。但是,当您Char指定为类型时,模式匹配将起作用

于 2013-06-19T07:00:23.807 回答
3

在这两个签名中,ais 都不是一个列表——它是任何类型,你不能选择哪个(除了它必须是 的实例Show)。

tell₁ :: Show a => [a] -> String
tell₁ [] = "The list is empty"
... -- (remember to match the non-empty list case too!)

您匹配的是as 列表,而不是类型a本身的值。

如果你写

tell₂ :: Show a => a -> String
tell₂ [] = "The list is empty"
...

您会假设类型a是列表的类型(某物)。但它可以是任何类型,例如Bool.

(但我可能不明白你的问题——你还没有真正说出问题所在。当问这样的问题时,你通常应该说明你做了什么,你期望什么,发生了什么。这些都不是此处确实指定了,因此人们只能猜测您的意思。)

于 2013-06-19T03:54:05.263 回答
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]

众所周知,写tand 并没有区别a[Char]只是类型的同义词String,所以我们写的类型和 GHC 推断的类型是相同的。

嗯,不完全是。当您自己,程序员,在源代码中手动指定函数的类型时,类型签名中的类型变量会变得严格。这到底是什么意思?

来自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

于 2013-06-19T04:27:16.573 回答
2

只要您在“g”上进行模式匹配,例如

pm 'g' = "wow"

您的函数不再具有(Show a) => a -> String; 相反,它有一个“a”的具体类型,即 Char,所以它变成Char -> String

这与您给它的显式类型签名直接冲突,后者表明您的函数适用于任何类型“a”(只要该类型是 的实例Show)。

在这种情况下,您无法进行模式匹配,因为您正在对 Int、Char 等进行模式匹配。但是您可以使用showPrelude 中的函数:

pm x = case show x of
             "'g'" -> "My favourite Char"
             "1"   -> "My favourite Int" 
             _     -> show x

正如您可能已经猜到的那样,show有点神奇;)。实际上,每种类型都实现了一大堆show函数,它们是类型类的一个实例Show

于 2013-06-19T06:55:08.097 回答
1
tell :: (Show a) =>a->String

这表示tell接受任何可显示类型的值a。你可以在任何可展示的地方调用它。这意味着在 的实现中tell,您必须能够对任何东西进行操作(这是可显示的)。

您可能认为这对于该类型签名来说是一个不错的实现:

tell [] = "The list is empty"

因为列表确实是可显示的,所以第一个参数的有效值也是如此。但是我正在检查参数是否为空列表;tell 1只有列表类型的值可以与列表模式(例如空列表模式)匹配,所以如果我调用ortell Truetell (1, 'c')等,这没有意义。

在内部tell,该类型a可以是任何类型的Show. 所以我唯一能用这个值做的事情是对所有类型的实例都有效的事情Show。这基本上意味着您只能将其传递给具有通用Show a => a参数的其他类似函数。1

您的困惑源于这种关于类型签名的误解“考虑到这里是一个列表而不是列表的元素” tell :: (Show a) => [a] -> String。这里a实际上是列表的一个元素,而不是列表本身。

该类型签名为“tell 采用单个参数,它是一些可显示类型的列表,并返回一个字符串”。这个版本tell知道它接收一个列表,所以它可以用它的参数做一些列表的事情。列表中的事物是某种未知类型的成员。


1除了将值传递给另一个函数之外,大多数这些函数也无法对值做任何事情Show,但迟早该值将被忽略或传递给类型类中的实际函数之一Show;这些对每种类型都有专门的实现,因此每个专门的版本都可以知道它正在运行的类型,这是最终可以完成任何事情的唯一方法。

于 2013-06-19T07:41:05.507 回答