处理可组合的免费 DSL 中的模糊类型
Handling ambiguous types in composable free DSLs
我正在构建几个基于 'free monads' 和 'datatypes a la carte' 的 DSL,使用 free 和 compdata 包(本质上类似于 Combining Free types)。
虽然这适用于一些简单的 DSL,但我坚持使用具有类型参数的 DSL,在不依赖于此类型参数的 constructor/command 的情况下,导致 模糊类型参数 来自 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 风格的东西,但无济于事。
有什么方法可以让这个模式起作用吗?
这是一个比较有名的“单点”开奖限制。
简而言之,如果我们有一个 f
函子,它本身有一个或多个内部类型索引,那么对于包含该函子的开和,类型推断会受到很大影响。
为了说明原因,假设我们有一个包含 DSL ()
和 DSL Int
的开放总和。 GHC 必须为其中之一选择一个实例,但使用 getLastIndex
是不可能的,因为参数或 return 类型中未提及 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
真正做到这一点,因为它不会导出相关的 type-level machinery.
我写了一个 minimal example 来说明上面的实现对你的情况来说是什么样的。我不把它贴在这里,因为它有点长而且没有启发性。请注意,内部索引的选择完全由函子的构造函数和开和决定。这也修复了其他情况的类型推断;例如,对于旧代码,如果 x
是数字文字或任何多态值,我们必须对 Write x f
的每次使用进行类型注释,而在新代码中它会被推断出来。
另外,请注意示例实现仅适用于具有单个内部索引的仿函数!如果我们想要 DSL a b next
,那么我们也必须为这种情况编写新代码,或者改用 DSL '(a, b) next
。
我正在构建几个基于 'free monads' 和 'datatypes a la carte' 的 DSL,使用 free 和 compdata 包(本质上类似于 Combining Free types)。
虽然这适用于一些简单的 DSL,但我坚持使用具有类型参数的 DSL,在不依赖于此类型参数的 constructor/command 的情况下,导致 模糊类型参数 来自 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 风格的东西,但无济于事。
有什么方法可以让这个模式起作用吗?
这是一个比较有名的“单点”开奖限制。
简而言之,如果我们有一个 f
函子,它本身有一个或多个内部类型索引,那么对于包含该函子的开和,类型推断会受到很大影响。
为了说明原因,假设我们有一个包含 DSL ()
和 DSL Int
的开放总和。 GHC 必须为其中之一选择一个实例,但使用 getLastIndex
是不可能的,因为参数或 return 类型中未提及 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
真正做到这一点,因为它不会导出相关的 type-level machinery.
我写了一个 minimal example 来说明上面的实现对你的情况来说是什么样的。我不把它贴在这里,因为它有点长而且没有启发性。请注意,内部索引的选择完全由函子的构造函数和开和决定。这也修复了其他情况的类型推断;例如,对于旧代码,如果 x
是数字文字或任何多态值,我们必须对 Write x f
的每次使用进行类型注释,而在新代码中它会被推断出来。
另外,请注意示例实现仅适用于具有单个内部索引的仿函数!如果我们想要 DSL a b next
,那么我们也必须为这种情况编写新代码,或者改用 DSL '(a, b) next
。