【问题标题】:How can I resolve this type class ambiguity?如何解决这种类型的歧义?
【发布时间】:2013-05-28 19:13:56
【问题描述】:

这是我的最小示例:

{-# LANGUAGE MultiParamTypeClasses, RankNTypes #-}

import Control.Lens

class Into outer inner where
    factory :: inner -> outer
    merge :: inner -> inner -> inner

-- Given an inner item, a lens and an outer item, use factory to construct a new
-- outer around the inner if the Maybe outer is Nothing, or else use merge to combine
-- the argument inner with the one viewed through the lens inside the outer
into :: Into outer inner =>
    inner -> Lens' outer inner -> Maybe outer -> Maybe outer
inner `into` lens = Just . maybe (factory inner) (over lens (merge inner))

编译失败,出现以下错误:

GHCi, version 7.6.2: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
[1 of 1] Compiling Main             ( foo.hs, interpreted )

foo.hs:10:62:
    Could not deduce (Into outer0 inner) arising from a use of `merge'
    from the context (Into outer inner)
      bound by the type signature for
                 into :: Into outer inner =>
                         inner -> Lens' outer inner -> Maybe outer -> Maybe outer
      at foo.hs:9:9-84
    The type variable `outer0' is ambiguous
    Possible fix: add a type signature that fixes these type variable(s)
    In the second argument of `over', namely `(merge inner)'
    In the second argument of `maybe', namely
      `(over lens (merge inner))'
    In the second argument of `(.)', namely
      `maybe (factory inner) (over lens (merge inner))'
Failed, modules loaded: none.
Prelude> 

我明白为什么会出现这个错误;对merge 的调用可能使用Into 的不同实例(具有不同的outer 但相同的inner),而不是整个into 函数的约束所选择的实例。但我想不出办法解决它。

我尝试过的事情:

  • 使用函数依赖从inner中隐含outer;这接近工作(抱怨需要UndecidableInstances),但似乎不太正确;理想情况下,我真的希望能够将相同的inner 推入两个不同的outers
  • 使用关联的类型同义词来做同样的事情;除了丑化类型签名(outer => Outer inner),我还摔倒了,因为我在实例中使用的outerinner 有更多的类型变量(其中一个是幻像),这意味着我无法在实例声明中合法地实例化关联类型
  • intoScopedTypeVariables 中使用merge 时添加显式类型签名,以将其绑定到into 的类型签名;但由于 merge 的类型没有引用 outer 它没有帮助

我有什么方法可以明确地为merge 使用与整个into 相同的类型类实例?或者我可以限制类型系统需要这样做的任何其他方式?理想情况下,我想保留这个类,这样我的实例声明仍然很简单:

instance (Hashable v, Eq v) => Into (VarInfo s k v) (HashSet v) where
    -- VarInfo is just a record type with 2 fields, the second being a HashSet v
    factory = VarInfo (return ())
    merge = HashSet.intersection

【问题讨论】:

  • 注意:Lens' 是多态的,因此将其作为参数的函数会获得 rank-2 类型。如果你只是使用over,你可以给它一个更简单的rank-1类型(这也将更加多态,因为它适用于Setters等)。 (即使您正在使用每个镜头操作,您也可以接受ALens',然后使用cloneLens,或类似的东西。)
  • @shachaf 谢谢;我仍然非常关注镜头包装中的所有东西。我知道Lens' 没有它需要的那么通用,但是我 镜头并且我知道Lens' 的含义,而该参数的推断类型对我来说是难以理解的胡言乱语阶段(不确定它的友好同义词是什么,如果有的话)。 rank-1 类型有什么优势?
  • 主要优点是 rank-2 类型的类型推断在 GHC 中效果不佳。您最终需要进行 eta-expand,在 GHC 通常能够推断它们的地方编写显式类型,等等。此外,该类型会传播给您的函数的每个用户——您需要比必要更多的多态性。 (也可能会影响性能,因为您需要传递显式字典,除非内容被内联......不过我还没有检查过。)
  • 顺便说一下,如果您想了解lens 类型等方面的问题,请随时来Freenode 上的#haskell-lens

标签: haskell typeclass


【解决方案1】:

拥有一个不提及所有类变量的类方法很少是一个好主意(除非这些类变量是由函数依赖唯一确定的)。

解决方案是使类层次结构更精确。在这里,您可以为merge 创建第二个类:

class Mergeable a where
    merge :: a -> a -> a

class Mergeable inner => Into outer inner where
    factory :: inner -> outer

您也可以使用更通用的Semigroup 类而不是专用的Mergeable 类,但这取决于您的应用程序的详细信息和merge 的属性。

【讨论】:

  • 干杯,现在这似乎很明显了。我已经考虑过使用 Monoid 进行合并操作,但我的一种情况是集合交集,而交集下的幺半群的零必须是元素类型的完整集合。制作一个与 Monoid 相矛盾的 Semigroup 实例并不是一个好主意。在我的另一个案例中,我认为该操作不是关联的。这个类只是内部的 DRY,没有暴露在我的界面中,所以我不太在意它是否有点专业。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-02
  • 2012-01-15
  • 1970-01-01
相关资源
最近更新 更多