【问题标题】:Type Signature of functions with Lists in haskell在haskell中使用列表类型签名函数
【发布时间】:2013-06-19 15:41:05
【问题描述】:

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

【问题讨论】:

  • 当前标题没问题。

标签: haskell


【解决方案1】:

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

有问题的新编辑更新:

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

【讨论】:

    【解决方案2】:

    在这两个签名中,a 都不是一个列表——它是任何类型,你无法选择哪个(除非它必须是 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

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

    【讨论】:

    • 很好地解释了匹配的内容,但是(我自己很好奇)为什么后者会失败?不是列表Show 并且不应该列表能够匹配Show a,即使我们将列表匹配到a,而不是[a]
    • @trutheality:List 确实实现了 Show 类型类,但是当你说:Show a => a -> String 这意味着该函数将接受任何实现 Show 的类型,最重要的是你只能在 a 上调用 show 类函数仅此而已,您的函数永远不会知道 a 的具体类型。而您正在尝试在 a 上调用列表模式匹配
    • @Ankur 谢谢,这很有意义,需要列表类型的是模式匹配。
    • @Alice:正确的实现是:pm c ="wow"。您可以在参数 c 上调用任何 Show 类型的类函数。您无法像之前尝试的那样进行模式匹配,因为您不知道参数的确切类型,您只知道它实现了 Show 类型类。但是,当您将 Char 指定为类型时,模式匹配就会起作用
    • 感谢@Ankur,如果您可以发布您的 cmets 作为答案,我可以接受
    【解决方案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/

    “而不是“用户指定类型”,我们使用更简短的术语严格 type 来描述一个完全指定的类型,在某些情况下 直接方式,由程序员提供的类型注释。”

    所以严格类型是程序员类型签名指定的任何类型。 所有其他类型都是“摇摆不定”[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 是什么。

    【讨论】:

      【解决方案4】:

      只要您在“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 函数。

      【讨论】:

      • 感谢完美的解释,但对于问题的第一部分,Ankur 的解决方案也更完整。但是,您在这里的解释非常有针对性。 +1
      【解决方案5】:
      tell :: (Show a) =>a->String
      

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

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

      tell [] = "The list is empty"
      

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

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

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

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


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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-13
        • 2021-01-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-01-03
        相关资源
        最近更新 更多