使用haskell中的列表键入函数签名

时间:2013-06-19 03:47:03

标签: haskell

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

5 个答案:

答案 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]

众所周知,编写ta之间没有区别,[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 1tell Truetell (1, 'c')这没有意义等等。

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

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

该类型签名读取“tell需要一个参数,这是一个可显示类型的列表,并返回一个字符串”。这个版本的tell知道它接收一个列表,所以它可以用它的参数做一些列表。这是里面列表中的东西,它们是某种未知类型的成员。


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