类型类投影作为继承

问题描述 投票:0回答:1

我希望通过在存在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 typeclass
1个回答
0
投票

一般来说,当 Haskell 查找实例并且您编写了

instance (Context) => Head
时,Haskell 会看到
Head
。这是一项长期存在的技术问题,出于多种原因而如此设计。这对你来说意味着:Haskell 模式仅在
Head
部分匹配,然后使用
Context
来确定是否应该继续或发出错误。如果 Head 失败,它不会
回溯并寻找另一个 
Context

instance (t #-> a, Prop t) => Prop a
所以你在这里说的话是

难以置信的强烈。你是说,如果有人 everany

 类型 
fn1
 上调用 
fn2a
,那么这里的这个实例就是典型的查看位置。要么这个规范实例可以工作(如果 
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 的类型解析引擎
不是是一种成熟的逻辑编程语言,因此我们有时必须做出让步。

© www.soinside.com 2019 - 2024. All rights reserved.