DSL 中的常量和表达式都可以有类型 class 吗?
Is it possible to have a type class for both constants and expressions in DSL?
假设,我有一个在 LangL r a
中进行计算的 DSL。我可能想让函数同时使用常量 (0 :: Int
、"lala" :: String
) 和 DSL 表达式 (LangL r a
)。所以,我实现了一个类型class。但是,无论我尝试以何种方式实现它,我都会遇到问题。
这是使用类型族时出现问题的一个最小示例:
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
data LangL r a = LangL a
deriving instance Functor (LangL r)
deriving instance Applicative (LangL r)
class DSLEntity r a where
type ValueOf a
entityValue :: a -> LangL r (ValueOf a)
instance DSLEntity r (LangL r a) where
type ValueOf (LangL r a) = a
entityValue = id
instance DSLEntity r Int where
type ValueOf Int = Int
entityValue = pure
foo :: LangL r Int -> LangL r Int
foo m = entityValue (entityValue m)
GHC 给出以下输出:
• Ambiguous type variable ‘r0’ arising from a use of ‘entityValue’
prevents the constraint ‘(DSLEntity
r (LangL r0 Int))’ from being solved.
Relevant bindings include
m :: LangL r Int (bound at temp.hs:25:5)
foo :: LangL r Int -> LangL r Int
(bound at temp.hs:25:1)
Probable fix: use a type annotation to specify what ‘r0’ should be.
These potential instance exist:
instance DSLEntity r (LangL r a)
-- Defined at temp.hs:16:10
• In the expression: entityValue (entityValue m)
In an equation for ‘foo’: foo m = entityValue (entityValue m)
|
temp.hs:25:22-34: error: …
• Ambiguous type variable ‘r0’ arising from a use of ‘entityValue’
prevents the constraint ‘(DSLEntity
r0 (LangL r Int))’ from being solved.
Relevant bindings include
m :: LangL r Int (bound at temp.hs:25:5)
foo :: LangL r Int -> LangL r Int
(bound at temp.hs:25:1)
Probable fix: use a type annotation to specify what ‘r0’ should be.
These potential instance exist:
instance DSLEntity r (LangL r a)
-- Defined at /temp.hs:16:10
• In the first argument of ‘entityValue’, namely ‘(entityValue m)’
In the expression: entityValue (entityValue m)
In an equation for ‘foo’: foo m = entityValue (entityValue m)
|
问题很清楚了。 LangL r a
的 r
参数和 DSLEntity
的 r
参数之间没有依赖关系。但是,我们不能添加这样的依赖项,因为对于 Int
实例,它实际上不存在。
我很困惑,想知道是否有可能完成我想做的事情。如果不是,为什么?
我认为你只需要在中间类型上给 GHC 一点帮助:
{-# LANGUAGE ScopedTypeVariables #-}
foo :: forall r. LangL r Int -> LangL r Int
foo m = entityValue (entityValue m :: LangL r Int)
您可以使用:
instance (r ~ r') => DSLEntity r' (LangL r a) where
而不是:
instance DSLEntity r (LangL r a) where
这实际上是做什么的有点神秘。
您的原始实例声明说 GHC 只有在可以证明 LangL r a
中的 r
与参数和 [=16= 的结果中的类型相同时才能使用该实例].但是 entityValue :: a -> LangL r (ValueOf a)
,所以任何类型都可以用作输入(并且需要 GHC 去寻找匹配的实例)。特别是,任何 LangL r0 a
都可以作为输入出现,即使是不匹配的 r
。所以在entityValue (entityValue m)
中,第一个可以在任何r0
处使用,而第二个会将其转换回foo
类型中使用的r
。由于 GHC 无法确定您在中间谈论的 which r
,因此您会遇到模糊类型变量阻止它知道哪个 DSLEntity
的问题例如,它应该选择解决约束。
而 instance (r ~ r') => DSLEntity r' (LangL r a)
表示此实例适用于 any 类型 r
和 r'
,但使用它会增加 r
和 r'
是相等的。这听起来和只写 instance DSLEntity r (LangL r a)
一样,但实际上并不是因为 GHC 在选择实例时不考虑约束的规则,只有在事后才考虑。现在 GHC 不需要证明 r
和 r'
相等来选择这个实例,只要 DSLEntity
约束的第二个参数看起来像 LangL _ _
,然后它会知道为了进行类型检查,约束 r ~ r'
必须成立,所以它会继续并假设(如果可能的话;否则会出现类型错误)。
您可以通过查看 entityValue . entityValue
的类型非常清楚地看出差异。使用您的原始实例,您将获得:
λ :t entityValue . entityValue
entityValue . entityValue
:: (DSLEntity r1 (LangL r2 (ValueOf a)), DSLEntity r2 a) =>
a -> LangL r1 (ValueOf a)
有了新的实例,你会得到这个:
λ :t entityValue . entityValue
entityValue . entityValue
:: DSLEntity r a => a -> LangL r (ValueOf a)
假设,我有一个在 LangL r a
中进行计算的 DSL。我可能想让函数同时使用常量 (0 :: Int
、"lala" :: String
) 和 DSL 表达式 (LangL r a
)。所以,我实现了一个类型class。但是,无论我尝试以何种方式实现它,我都会遇到问题。
这是使用类型族时出现问题的一个最小示例:
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
data LangL r a = LangL a
deriving instance Functor (LangL r)
deriving instance Applicative (LangL r)
class DSLEntity r a where
type ValueOf a
entityValue :: a -> LangL r (ValueOf a)
instance DSLEntity r (LangL r a) where
type ValueOf (LangL r a) = a
entityValue = id
instance DSLEntity r Int where
type ValueOf Int = Int
entityValue = pure
foo :: LangL r Int -> LangL r Int
foo m = entityValue (entityValue m)
GHC 给出以下输出:
• Ambiguous type variable ‘r0’ arising from a use of ‘entityValue’
prevents the constraint ‘(DSLEntity
r (LangL r0 Int))’ from being solved.
Relevant bindings include
m :: LangL r Int (bound at temp.hs:25:5)
foo :: LangL r Int -> LangL r Int
(bound at temp.hs:25:1)
Probable fix: use a type annotation to specify what ‘r0’ should be.
These potential instance exist:
instance DSLEntity r (LangL r a)
-- Defined at temp.hs:16:10
• In the expression: entityValue (entityValue m)
In an equation for ‘foo’: foo m = entityValue (entityValue m)
|
temp.hs:25:22-34: error: …
• Ambiguous type variable ‘r0’ arising from a use of ‘entityValue’
prevents the constraint ‘(DSLEntity
r0 (LangL r Int))’ from being solved.
Relevant bindings include
m :: LangL r Int (bound at temp.hs:25:5)
foo :: LangL r Int -> LangL r Int
(bound at temp.hs:25:1)
Probable fix: use a type annotation to specify what ‘r0’ should be.
These potential instance exist:
instance DSLEntity r (LangL r a)
-- Defined at /temp.hs:16:10
• In the first argument of ‘entityValue’, namely ‘(entityValue m)’
In the expression: entityValue (entityValue m)
In an equation for ‘foo’: foo m = entityValue (entityValue m)
|
问题很清楚了。 LangL r a
的 r
参数和 DSLEntity
的 r
参数之间没有依赖关系。但是,我们不能添加这样的依赖项,因为对于 Int
实例,它实际上不存在。
我很困惑,想知道是否有可能完成我想做的事情。如果不是,为什么?
我认为你只需要在中间类型上给 GHC 一点帮助:
{-# LANGUAGE ScopedTypeVariables #-}
foo :: forall r. LangL r Int -> LangL r Int
foo m = entityValue (entityValue m :: LangL r Int)
您可以使用:
instance (r ~ r') => DSLEntity r' (LangL r a) where
而不是:
instance DSLEntity r (LangL r a) where
这实际上是做什么的有点神秘。
您的原始实例声明说 GHC 只有在可以证明 LangL r a
中的 r
与参数和 [=16= 的结果中的类型相同时才能使用该实例].但是 entityValue :: a -> LangL r (ValueOf a)
,所以任何类型都可以用作输入(并且需要 GHC 去寻找匹配的实例)。特别是,任何 LangL r0 a
都可以作为输入出现,即使是不匹配的 r
。所以在entityValue (entityValue m)
中,第一个可以在任何r0
处使用,而第二个会将其转换回foo
类型中使用的r
。由于 GHC 无法确定您在中间谈论的 which r
,因此您会遇到模糊类型变量阻止它知道哪个 DSLEntity
的问题例如,它应该选择解决约束。
而 instance (r ~ r') => DSLEntity r' (LangL r a)
表示此实例适用于 any 类型 r
和 r'
,但使用它会增加 r
和 r'
是相等的。这听起来和只写 instance DSLEntity r (LangL r a)
一样,但实际上并不是因为 GHC 在选择实例时不考虑约束的规则,只有在事后才考虑。现在 GHC 不需要证明 r
和 r'
相等来选择这个实例,只要 DSLEntity
约束的第二个参数看起来像 LangL _ _
,然后它会知道为了进行类型检查,约束 r ~ r'
必须成立,所以它会继续并假设(如果可能的话;否则会出现类型错误)。
您可以通过查看 entityValue . entityValue
的类型非常清楚地看出差异。使用您的原始实例,您将获得:
λ :t entityValue . entityValue
entityValue . entityValue
:: (DSLEntity r1 (LangL r2 (ValueOf a)), DSLEntity r2 a) =>
a -> LangL r1 (ValueOf a)
有了新的实例,你会得到这个:
λ :t entityValue . entityValue
entityValue . entityValue
:: DSLEntity r a => a -> LangL r (ValueOf a)