【问题标题】:Haskell type class confusion, inferred types cannot be deducedHaskell 类型类混淆,无法推断出推断类型
【发布时间】:2019-04-11 18:54:57
【问题描述】:

抱歉,我以较小的方式涉足 Haskell,每 2 年一次,然后我无法理解它。

最好只做例子。

> {-# LANGUAGE MultiParamTypeClasses #-}
> {-# LANGUAGE ScopedTypeVariables #-}
> {-# LANGUAGE AllowAmbiguousTypes #-}
> {-# LANGUAGE FlexibleInstances #-}
> {-# LANGUAGE FlexibleContexts #-}


> class Foo a b where
>     foo :: a -> b
>     
> class Bar a b where
>     bar :: a -> b

> data A a = A a
> data B b = B b

> instance Foo (A a) a where
>   foo (A a) = a

> instance Bar (B b) b where
>   bar (B b) = b

所以,我认为我正在做的是创建数据类型,然后根据类中数据类型的成员资格定义两个函数......

所以这行得通....

> f1 x = foo x
> f2 x = bar x

> x1 :: String
> x1 = f1 $ A "1"
> x2 :: String
> x2 = f2 $ B "1"

简单...

担心 #1...如果我删除 x1 和 x2 的类型声明,ghc 会抱怨

• Ambiguous type variable ‘b1’ arising from a use of ‘f1’
      prevents the constraint ‘(Foo (A [Char]) b1)’ from being solved.
      Relevant bindings include x1 :: b1 (bound at catdog.lhs:27:3)
      Probable fix: use a type annotation to specify what ‘b1’ should be.
      These potential instance exist:
        instance Foo (A a) a -- Defined at catdog.lhs:17:12
    • In the expression: f1 $ A "1"
      In an equation for ‘x1’: x1 = f1 $ A "1"
   |
27 | > x1 = f1 $ A "1"    |        ^^^^^^^^^^

这是一个担心,不是很明显吗?......这种事情会再次发生......

如果我写

> f x = bar (foo x)

对我来说这是一件完全合理的事情......ghc同意!

我问它的类型...我明白了

f :: (Bar a1 b, Foo a2 a1) => a2 -> b

我可以买那个。

像一个优秀的程序员一样,我将类型粘贴到

> f :: (Bar a1 b, Foo a2 a1) => a2 -> b
> f x = bar (foo x)

和“砰”的一声……

• Could not deduce (Foo a2 a0) arising from a use of ‘foo’
      from the context: (Bar a1 b, Foo a2 a1)
        bound by the type signature for:
                   f :: forall a1 b a2. (Bar a1 b, Foo a2 a1) => a2 -> b
        at catdog.lhs:32:3-39
      The type variable ‘a0’ is ambiguous
      Relevant bindings include
        x :: a2 (bound at catdog.lhs:33:5)
        f :: a2 -> b (bound at catdog.lhs:33:3)
      These potential instance exist:
        instance Foo (A a) a -- Defined at catdog.lhs:17:12
    • In the first argument of ‘bar’, namely ‘(foo x)’
      In the expression: bar (foo x)
      In an equation for ‘f’: f x = bar (foo x)
   |
33 | > f x = bar (foo x)    |              ^^^^^

所以 ghc 告诉我它在没有类型声明的情况下推断出的类型,它现在不确定!

现在...我的脑海中通常有一个齿轮通过使用 scala 或 f# 或其他一些 OO 样式类型系统向后转动,我必须反过来...我要疯了吗?

【问题讨论】:

  • 我当然不是这方面的专家,但是对于担心#1,我认为正在发生的是,当您省略类型签名时,GHC 看到它需要有一个实例@ 987654328@ 并且无法弄清楚b 是什么。我想在这种情况下它无法看到您的脚本只有一个这样的实例(bString) - 如果您有多个实例,那么它肯定有理由抱怨歧义。跨度>
  • 至于第二个,这离我的舒适区更远了,但我猜 GHC 不知道 a1 是什么,并且无法阅读约束说“我希望有某种类型的a1 约束Bar a1 bFoo a2 a1 同时保持”。不知道你如何设置它(或者如果它可能的话),所以我会把它留给其他更有知识的人。

标签: haskell


【解决方案1】:

让我们再看看你的例子:

> class Foo a b where
>     foo :: a -> b

这里的问题是一个给定的a可以对应多个给定的bs;例如,您可以同时拥有instance Foo Int Charinstance Foo Int Bool。这会导致您所看到的类型推断问题。

> f1 x = foo x
> f2 x = bar x

> x1 :: String
> x1 = f1 $ A "1"
> x2 :: String
> x2 = f2 $ B "1"

例如,您在此处指定了 ab,因此 GHC 知道您指的是 instance (A String) String。当您删除签名时,GHC 不知道要使用什么 b。您尚未定义任何其他实例,并且您不打算定义,但 GHC 不知道。

那么我们该如何解决这个问题呢?我们使用{-# LANGUAGE FunctionalDependencies #-}:

> class Foo a b | a -> b where
>     foo :: a -> b

基本上,这告诉 GHC 对于任何给定的a,只有一个对应的b。这应该可以解决您的问题(尽管我承认我还没有测试过)。有关函数依赖的更多信息,请参阅 this SO answer,它也解决了您的 bar (foo x) 问题。

【讨论】:

  • 很好的答案,我想到了函数依赖(虽然这是我非常不熟练的其他东西),但出于某种原因,我暂时(并且错误地!)认为它们是用于类型声明而不是类。
  • achchchc ....当然...我之前曾涉足 Haskell,做过依赖类型的东西...另一种方式(类型函数?)但是是的...我完全明白了....这个齿轮现在已经啮合
  • 顺便说一句......为什么 GHC 会推断出一种类型,然后它会拒绝?
  • @MrD 我不太明白你在问什么——你能详细说明一下吗?
  • @MrD 问题是,虽然 GHC 知道有一个 Foo a2 a1 实例在周围,并且那个 x :: a2,它不知道你肯定想要 foo x :: a1 而不是,比如说, foo x :: Int 或其他东西,因此它不知道使用 Foo a2 a1 实例,而不是在其全局实例列表中搜索 Foo a2 Int 实例。您可以通过在类型签名f :: forall a1 a2 b. (Bar a1 b, Foo a2 a1) => a2 -> b 中添加显式forall 并向主体添加类型注释f x = bar (foo x :: a1) 来告诉它您想要什么。
猜你喜欢
  • 2019-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多