4

我正在使用freecompdata包(在精神上类似于Combining Free types )构建几个 DSL,它们应该可以基于“free monads”和“datatypes a la carte”进行组合。

虽然这适用于一些简单的 DSL,但在构造函数/命令不依赖于该类型参数的情况下,我被困在一个具有类型参数的 DSL 上,这会导致 GHC 出现模棱两可的类型参数错误。

为了澄清,这里有一些代码:

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeOperators #-}

module DSL where

import Data.Comp
import Control.Monad.Free

type Index = Int

data DSL a next = Read Index (a -> next)
                | Write a (Index -> next)
                | GetLastIndex (Index -> next)
    deriving (Functor)

read :: (Functor f, DSL a :<: f, MonadFree f m) => Index -> m a
read idx = liftF (inj (Read idx id))

write :: (Functor f, DSL a :<: f, MonadFree f m) => a -> m Index
write a = liftF (inj (Write a id))

-- This works
getLastIndex' :: MonadFree (DSL a) m => m Index
getLastIndex' = liftF (GetLastIndex id)

-- This doesn't:
--
--     Could not deduce (Data.Comp.Ops.Subsume
--                         (compdata-0.10:Data.Comp.SubsumeCommon.ComprEmb
--                            (Data.Comp.Ops.Elem (DSL a0) f))
--                         (DSL a0)
--                         f)
--     from the context (Functor f, DSL a :<: f, MonadFree f m)
--       bound by the type signature for
--                  getLastIndex :: (Functor f, DSL a :<: f, MonadFree f m) => m Index
--       at simple.hs:30:17-66
--     The type variable ‘a0’ is ambiguous
--     In the ambiguity check for the type signature for ‘getLastIndex’:
--       getLastIndex :: forall (m :: * -> *) (f :: * -> *) a.
--                       (Functor f, DSL a :<: f, MonadFree f m) =>
--                       m Index
--     To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
--     In the type signature for ‘getLastIndex’:
--       getLastIndex :: (Functor f, DSL a :<: f, MonadFree f m) => m Index

getLastIndex :: (Functor f, DSL a :<: f, MonadFree f m) => m Index
-- getLastIndex = liftF (inj (GetLastIndex id))
getLastIndex = _

正如 GHC 所暗示的那样,试图让它与启用AllowAmbiguousTypes扩展一起工作并没有让我更进一步。我尝试在类型签名中添加一些forall a风格的东西,但无济于事。

有什么办法可以让这种模式起作用吗?

4

1 回答 1

4

这是开放式金额“点菜”的一个相对众所周知的限制。

简而言之,如果我们有一个f本身具有一个或多个内部类型索引的函子,那么包含该函子的开和的类型推断会受到很大影响。

说明原因,假设我们有一个包含 aDSL ()和 a的开和DSL Int。GHC 必须为其中一个选择一个实例,但使用 是不可能的getLastIndex,因为a参数或返回类型中没有提到参数。GHC 基本上没有关于a上下文的信息。

这可以使用有点笨拙的方式来补救Data.Proxy

import Data.Proxy

getLastIndex ::
  forall a f m.
  (Functor f, DSL a :<: f, MonadFree f m)
  => Proxy a -> m Index
getLastIndex _ = liftF (inj (GetLastIndex id :: DSL a Index))

DSL或者,如果我们要求开和中只有一个,我们可以恢复良好的类型推断和明确性。但是,这涉及:<:为仿函数DSL(具有内部类型索引的函数)重写类型级别的查找代码。我们不能真正做到这compdata一点,因为它不会导出相关的类型级机器。

我写了一个最小的例子来说明上面的实现对于你的情况是什么样的。我不在这里粘贴它,因为它有点长而且没有启发性。请注意,内部索引的选择完全由仿函数的构造函数和开和确定。这也修复了其他情况的类型推断;例如,对于旧代码,我们必须对Write x fifx是数字文字或任何多态值的每次使用进行类型注释,而在新代码中,它会被推断出来。

另外,请注意,示例实现仅适用于具有单个内部索引的函子!如果我们想要一个DSL a b next,那么我们也必须为这种情况编写新代码,或者DSL '(a, b) next改用。

于 2015-05-05T21:12:44.393 回答