Class 对于新类型

Class for newtype

我有一个新类型

newtype ScopedTable sym = ScopedTable { tab_stack :: [ Map String sym ] }

因为我想为不同的 symbol 类型创建类似 symbol-table 的结构。

在我的第一次尝试中,我写了一个 class 用于在 monad 中以通用方式使用 ScopedTable,在 class 中我们只得到一个 Lens 来关注 ScopedTable:

class STScopedTable st where
        st_table :: Lens' st (ScopedTable sym)

请注意 Lens' 集中在 ScopedTable sym 上,这是 sym 它出现在 class 中的唯一位置。

现在实例化 class

data OpPState = OpPState { _opp_table :: ScopedTable Operator }
makeLenses ''OpPState

instance STScopedTable OpPState where
        st_table = opp_table

存在类型检查错误:

src/Language/Angler/MixfixParser.hs:74:20:
    Couldn't match type ‘sym’ with ‘Operator’
      ‘sym’ is a rigid type variable bound by
            the type signature for
              st_table :: Functor f =>
                          (ScopedTable sym -> f (ScopedTable sym)) -> OpPState -> f OpPState
            at src/Language/Angler/MixfixParser.hs:74:9
    Expected type: (ScopedTable sym -> f (ScopedTable sym))
                   -> OpPState -> f OpPState
      Actual type: (ScopedTable Operator -> f (ScopedTable Operator))
                   -> OpPState -> f OpPState
    Relevant bindings include
      st_table :: (ScopedTable sym -> f (ScopedTable sym))
                  -> OpPState -> f OpPState
        (bound at src/Language/Angler/MixfixParser.hs:74:9)
    In the expression: opp_table
    In an equation for ‘st_table’: st_table = opp_table

所以我尝试了 MultiParamTypeClasses pragma,现在必须将 sym 传递给 class:

class STScopedTable st sym where
        st_table :: Lens' st (ScopedTable sym)

但现在它不允许我正确使用 class 约束:

enterSc :: (STScopedTable s sym, MonadState s m) => m ()
enterSc = use st_table >>= assign st_table . enterScope

它给我错误:

src/Language/Angler/Monad.hs:79:12:
    Could not deduce (STScopedTable s sym0)
    from the context (STScopedTable s sym, MonadState s m)
      bound by the type signature for
                 enterSc :: (STScopedTable s sym, MonadState s m) => m ()
      at src/Language/Angler/Monad.hs:79:12-56
    The type variable ‘sym0’ is ambiguous
    In the ambiguity check for the type signature for ‘enterSc’:
      enterSc :: forall (m :: * -> *) s sym.
                 (STScopedTable s sym, MonadState s m) =>
                 m ()
    To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
    In the type signature for ‘enterSc’:
      enterSc :: (STScopedTable s sym, MonadState s m) => m ()

我启用了 AllowAmbiguousTypes pragma,现在我得到:

src/Language/Angler/Monad.hs:81:15:
    Could not deduce (STScopedTable s sym0)
      arising from a use of ‘st_table’
    from the context (STScopedTable s sym, MonadState s m)
      bound by the type signature for
                 enterSc :: (STScopedTable s sym, MonadState s m) => m ()
      at src/Language/Angler/Monad.hs:80:12-56
    The type variable ‘sym0’ is ambiguous
    In the first argument of ‘use’, namely ‘st_table’
    In the first argument of ‘(>>=)’, namely ‘use st_table’
    In the expression: use st_table >>= assign st_table . enterScope

我可以为每个使用此 class 的 monad 编写 enterSc,但这会破坏进行概括的目的。

如果有人能给我一个关于如何解决第一次尝试的想法,我会更愿意,因为我总是喜欢使用较少的编译指示。但如果你详细说明第二次尝试,我也将不胜感激!

GHC 不知道在 enterSc 中为 sym 选择什么类型,因为它只在约束中使用,没有其他地方使用。

你可以用函数依赖来解决这个问题:

class STScopedTable st sym | st -> sym where
        st_table :: Lens' st (ScopedTable sym)

这告诉 GHC sym 类型由 STScopedTable 的所有实例中的 st 类型唯一确定。由于 MonadState 具有函数依赖性,即 sm 唯一确定,我们也知道(由于传递性)symm 唯一确定(我们最终工作的 monad)。

避免 pragma 不一定是好事。我认为在不启用任何编译指示的情况下不可能消除第一个版本的歧义。尽可能避免使用 IncoherentInstances 等某些编译指示是个好主意,但我不建议制定避免编译指示的一般规则。