我希望通过在存在projection时自动派生类型类,允许类型类在联合类型上轻松“继承”(此投影是我单独定义的另一个类型类)。下面是一些代码,说明了我想要实现的目标:
class t ##-> a where -- the projection
inj :: t -> a
prj :: a -> Maybe t
class Prop t where -- the property
fn1 :: Int -> t
fn2 :: t -> t
instance (t #-> a, Prop t) => Prop a where -- deriving the property from the projection
cst :: Int -> a
cst = inj . fn1 @t
fn2 :: a -> a
fn2 a1 = inj $ fn2 @t $ fromJust (prj a1)
因此,当我定义 sum 类型时,我可以仅定义投影
#->
,而无需在 sum 类型上重新定义类型类 Prop
。
data K = ...
instance Prop K
data SumType = E1 K | ...
instance K #-> SumType where
inj :: K -> SumType
inj = E1
prj :: SumType -> Maybe K
prj (E1 k) = Just k
prj _ = Nothing
但是,当我想在实例定义中重用类型类函数时,我遇到了以下问题。例如,当我尝试为基本类型提供
Prop
类定义时(例如,String
):
instance Prop String where
cst :: Int -> String
cst = show
fn2 :: String -> String
fn2 s = if s == cst 0 then "0" else s -- Overlapping instances for Prop String arising from a use of ‘cst’
编译器不确定是否使用派生类型(来自
#->
派生),还是使用我定义的基本实例(即 Prop String
定义)。然而,对我来说,显然,在 Prop String
定义中,应该使用 cst @String
定义。使用 {-# LANGUAGE TypeApplications #-}
似乎也无法帮助编译器确定所需的实例。
我想知道我们如何说服编译器使用我在这里想要的实例?
一般来说,当 Haskell 查找实例并且您编写了
instance (Context) => Head
时,Haskell 仅 会看到 Head
。这是一项长期存在的技术问题,出于多种原因而如此设计。这对你来说意味着:Haskell 模式仅在 Head
部分匹配,然后使用 Context
来确定是否应该继续或发出错误。如果 Head
失败,它不会回溯并寻找另一个
Context
。
instance (t #-> a, Prop t) => Prop a
所以你在这里说的话是难以置信的强烈。你是说,如果有人 ever 在 any
类型
fn1
上调用
fn2
或 a
,那么这里的这个实例就是典型的查看位置。要么这个规范实例可以工作(如果
t #-> a
和
Prop t
),或者nothing 可以。这显然不是你的意思。 我没有办法得到你想要的东西。这在 Scala 中是可能的,但那是因为 Scala 的隐式解析引擎愿意回溯 Haskell 不做的地方。这是我推荐的 Haskell 惯用法。
第 1 步:声明对
##->
的 功能依赖
。
class t ##-> a | a -> t where -- the projection
inj :: t -> a
prj :: a -> Maybe t
这是必要的,因为给定一个 Prop a
实例,我们需要知道要查找which
t
。如果您不能保证
a
(较大的类型)唯一确定
t
(较小的类型),那么您将无法执行此操作,因为类型推断无法确定您是哪个
t
想要。第 2 步:如果满足您的条件,请提供默认实现。
{-# LANGUAGE DefaultSignatures #-}
class Prop a where
fn1 :: a -> a
default fn1 :: (Prop t, t ##-> a) -> a -> a
fn1 = undefined -- Your implementation if (Prop t, t ##-> a) here.
第3步:当Prop t
和
t ##-> a
都为真时,实现者可以简单地写
instance Prop a
他们的代码将很乐意采用 default fn1
实例。这并不完全是您所要求的,它确实需要实现者编写一行空的
instance
定义。但是,无论好坏,Haskell 的类型解析引擎不是是一种成熟的逻辑编程语言,因此我们有时必须做出让步。