【发布时间】: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),我还摔倒了,因为我在实例中使用的outer比inner有更多的类型变量(其中一个是幻像),这意味着我无法在实例声明中合法地实例化关联类型 - 在
into和ScopedTypeVariables中使用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!