【问题标题】:types and type variable in HaskellHaskell 中的类型和类型变量
【发布时间】:2019-07-20 16:14:41
【问题描述】:

从 Haskell 类型系统的表面开始,运行以下代码:

Prelude> e = []
Prelude> ec = tail "a"
Prelude> en = tail [1]
Prelude> :t e
e :: [a]
Prelude> :t ec
ec :: [Char]
Prelude> :t en
en :: Num a => [a]
Prelude> en == e
True
Prelude> ec == e
True

不知何故,尽管 enec 有不同的类型,但它们都在 == e 上测试 True。我说“不知何故”不是因为我感到惊讶(我不是),而是因为我不知道允许这样做的规则/机制的名称是什么。就好像表达式“[] == en”中的类型变量“a”被允许采用“Num”的值进行评估。同样当用“[] == ec”测试时,它可以变成“Char”。

我不确定我的解释是否正确的原因是:

Prelude> (en == e) && (ec == e)
True

,因为直觉上这意味着在同一个表达式中,e “同时”假定 Num 和 Char 的值(至少我习惯于这样解释 && 的语义)。除非 Char 的“假设”仅在 (ec == e) 的评估期间起作用,并且 (en == e) 被独立评估,在单独的......减少? (我在这里猜测一个术语)。

然后是这样的:

Prelude> en == es

<interactive>:80:1: error:
    • No instance for (Num Char) arising from a use of ‘en’
    • In the first argument of ‘(==)’, namely ‘en’
      In the expression: en == es
      In an equation for ‘it’: it = en == es
Prelude> es == en

<interactive>:81:7: error:
    • No instance for (Num Char) arising from a use of ‘en’
    • In the second argument of ‘(==)’, namely ‘en’
      In the expression: es == en
      In an equation for ‘it’: it = es == en

对异常并不感到惊讶,但惊讶于在两个测试中,错误消息都抱怨“使用 'en'”——不管它是第一个操作数还是第二个操作数。

也许需要学习有关 Haskell 类型系统的重要一课。感谢您的宝贵时间!

【问题讨论】:

    标签: haskell types type-systems


    【解决方案1】:

    有时,将多态函数视为采用显式类型参数的函数是很方便的。我们以多态恒等函数为例。

    id :: forall a . a -> a
    id x = x
    

    我们可以这样想这个函数:

    • 首先,该函数将一个名为a 的类型参数作为输入
    • 其次,该函数将先前选择的类型a的值x作为输入
    • 最后,函数返回xa类型)

    这是一个可能的调用:

    id @Bool True
    

    在上面,@Bool 语法将Bool 传递给第一个参数(类型参数a),而True 作为第二个参数传递(x 类型a = Bool)。

    其他一些:

    id @Int 42
    id @String "hello"
    id @(Int, Bool) (3, True)
    

    我们甚至可以部分应用id,只传递类型参数:

    id @Int       :: Int -> Int
    id @String    :: String -> String
    ...
    

    现在,请注意,在大多数情况下,Haskell 允许我们省略类型参数。 IE。我们可以写id "hello",GHC 将尝试推断缺少的类型参数。大致如下:id "hello" 转换为id @t "hello" 用于某些未知类型t,然后根据id 的类型,此调用只能类型检查"hello" :: t,由于"hello" :: String,我们可以推断出t = String

    类型推断在 Haskell 中极为常见。程序员很少指定他们的类型参数,而让 GHC 完成它的工作。

    在你的情况下:

    e :: forall a . [a]
    e = []
    ec :: [Char]
    ec = tail "1"
    en :: [Int]
    en = tail [1]
    

    变量e 绑定到一个多态值。也就是说,它实际上是一个排序函数,它接受一个类型参数 a(也可以省略),并返回一个类型为 [a] 的列表。

    相反,ec 不接受任何类型参数。这是[Char] 类型的简单列表。 en 也是如此。

    然后我们可以使用

    ec == (e @Char)    -- both of type [Char]
    en == (e @Int)     -- both of type [Int]
    

    或者我们可以让类型推理引擎来确定隐式类型参数

    ec == e     -- @Char inferred
    en == e     -- @Int inferred
    

    后者可能会产生误导,因为ec,e,en 似乎必须具有相同的类型。事实上,他们没有,因为正在推断不同的隐式类型参数。

    【讨论】:

      【解决方案2】:

      当我们说e :: [a] 时,它意味着e 是任何类型的元素的列表。哪种类型?任意种类!无论您目前碰巧需要哪种类型。

      如果您来自非 ML 语言,首先查看函数(而不是值)可能会更容易理解。考虑一下:

      f x = [x]
      

      这个函数的类型是f :: a -&gt; [a]。粗略地说,这意味着该函数适用于任何类型a。你给它一个这种类型的值,它会给你一个包含该类型元素的列表。哪种类型?任意种类!无论您碰巧需要哪个。

      当我调用这个函数时,我实际上是选择我现在想要的类型。如果我称它为f 'x',我选择a = Char,如果我称它为f True,我选择a = Bool。所以这里重要的一点是调用函数的人选择了类型参数

      但我不必一劳永逸地选择它。相反,我选择类型参数每次调用函数时。考虑一下:

      pair = (f 'x', f True)
      

      这里我调用了两次f,每次都选择不同的类型参数——第一次选择a = Char,第二次选择a = Bool

      好的,现在进行下一步:当我选择类型参数时,我可以通过多种方式进行。在上面的示例中,我通过传递所需类型的值参数来选择它。但另一种方法是指定我想要的结果类型。考虑一下:

      g x = []
      
      a :: [Int]
      a = g 0
      
      b :: [Char]
      b = g 42
      

      这里,函数g忽略了它的参数,所以它的类型和g的结果没有关系。但是我仍然可以通过受周围上下文的约束来选择该结果的类型。

      现在,精神上的飞跃:没有任何参数(也称为“值”)的函数与有参数的函数没有太大区别。它只有零参数,仅此而已。

      如果一个值具有类型参数(例如您的值e),我可以在每次“调用”该值时选择该类型参数,就像它是一个函数一样容易。因此,在表达式 e == ec &amp;&amp; e == en 中,您只是“调用”值 e 两次,在每次调用时选择不同的类型参数 - 就像我在上面的 pair 示例中所做的那样。


      关于Num 的困惑是完全不同的事情。

      你看,Num 不是一个类型。这是一个类型类。类型类有点像 Java 或 C# 中的接口,除了您可以稍后声明它们,不必与实现它们的类型一起声明。

      所以签名en :: Num a =&gt; [a] 意味着en 是一个包含任何 类型元素的列表,只要该类型实现(“具有”)类型类Num .

      Haskell 中类型推断的工作方式是,编译器将首先确定它所能确定的最具体的类型,然后尝试为这些类型找到所需类型类的实现(“实例”)。

      在您的情况下,编译器看到 en :: [a] 正在与 ec :: [Char] 进行比较,并显示:“哦,我知道:a 必须是 Char!”然后它去查找类实例并注意到a 必须有一个Num 的实例,并且由于aChar,因此Char 必须有一个Num 的实例。但它没有,所以编译器抱怨:“找不到(Num Char)”

      至于“使用en”——嗯,那是因为en 是需要Num 实例的原因。 en 是在其类型签名中具有Num 的那个,因此它的存在是导致Num 要求的原因

      【讨论】:

      • 参数多态性的很好理解的解释!
      猜你喜欢
      • 1970-01-01
      • 2017-04-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-24
      • 2019-09-20
      • 1970-01-01
      相关资源
      最近更新 更多