行.这里的问题非常抽象.忍受我.
我有一堆"单位",每个单位都有一定的属性.这些属性在Seq
类中定义,如下所示:
class Seq a x y where
break :: x -> (a, y)
build :: a -> y -> x
Run Code Online (Sandbox Code Playgroud)
从概念上讲,a
类型是重要类型,x
是用于生成的上下文a
,y
是用于生成任何其他Seq
实例的上下文.break
打破Seq
,build
让你重建它.
在个别Seq
情况下,这很好.但是,我也有一个如下所示的数据构造函数:
data a :/: b = a :/: b deriving (Eq, Ord)
infixr :/:
Run Code Online (Sandbox Code Playgroud)
整个操作的目标是能够组合Seq
实例.
例如,如果我有a
,b
并且c
所有Seq
这些a
上下文的实例都被输入b
和b
输入c
,那么我应该自动拥有一个Seq
实例a :/: b :/: c
.通过递归数据类型,设置它的类非常简单:
instance (Seq a x y, Seq b y z) => Seq (a :/: b) x z where
break x = let
(a, y) = break x :: (a, y)
(b, z) = break y :: (b, z)
in (a :/: b, z)
build (a :/: b) z = let
y = build b z :: y
x = build a y :: x
in x
Run Code Online (Sandbox Code Playgroud)
问题是我似乎无法使用它.如果我定义以下三个Seq
实例:
data Foo
instance Seq Foo Integer Bool
data Bar
instance Seq Bar Bool Bar
data Baz
instance Seq Baz Bar ()
Run Code Online (Sandbox Code Playgroud)
(实施细节编辑,详见此处)
和一个良好的break
功能:
myBreak :: Integer -> (Foo :/: Bar :/: Baz)
myBreak = fst . break' where
break' = break :: Integer -> (Foo :/: Bar :/: Baz, ())
Run Code Online (Sandbox Code Playgroud)
然后我甚至无法编译:
No instances for (Seq Foo Integer y, Seq Bar y y1, Seq Baz y1 ())
arising from a use of `break'
Possible fix:
add instance declarations for
(Seq Foo Integer y, Seq Bar y y1, Seq Baz y1 ())
In the expression: break :: Integer -> (Foo :/: (Bar :/: Baz), ())
In an equation for break':
break' = break :: Integer -> (Foo :/: (Bar :/: Baz), ())
In an equation for `myBreak':
myBreak
= fst . break'
where
break' = break :: Integer -> (Foo :/: (Bar :/: Baz), ())
Run Code Online (Sandbox Code Playgroud)
在我看来,myBreak
不能确定Foo→Bar→Baz的上下文有"工作线程".我怎样才能说服编译器这个类型很好?
这是我第一次进入类型编程,所以我无疑打破了一些完善的规则.我很好奇我在这里做错了什么,但我也愿意接受有关如何更好地实现目标的建议.
Phi*_* JF 10
您可能需要另外两个语言扩展才能完成此工作.一般的问题是多参数类型类需要编码开放世界的假设,所以假设有人出现并添加
instance Seq Bar Integer Float
instance Seq Baz Float ()
Run Code Online (Sandbox Code Playgroud)
编译器无法知道要使用哪组实例.这可能会导致未定义的行为.如果编译器只是"弄清楚"没有其他实例可用,那么你一定意味着这些,那么你就会陷入尴尬的境地,因为有人添加了一个实例,因此代码可能会破坏.
解决方案是使用某种类型级别的功能.所以,如果a
是重要的类型,那么也许只有一个组合,x
并且y
与之相关a
.一种方法是使用功能依赖.
class Seq a x y | a -> x, a -> y where
break :: x -> (a, y)
build :: a -> y -> x
Run Code Online (Sandbox Code Playgroud)
功能依赖性非常通用,并且可以使用两种编码双射类型函数
class Seq a x y | a -> x, a -> y, x y -> a where
break :: x -> (a, y)
build :: a -> y -> x
Run Code Online (Sandbox Code Playgroud)
这取决于你想要的语义究竟是什么.或者,您可以使用类型族扩展来使用稍微更优雅的"关联类型同义词"方法.
class Seq a where
type X a
type Y a
break :: X a -> (a, Y a)
build :: a -> Y a -> X a
instance Seq Bar where
type X Bar = Bool
type Y Bar = Bar
Run Code Online (Sandbox Code Playgroud)
这个版本目前被认为是最惯用的,并且比功能性依赖版本具有一些优势(尽管,你不能以这种方式编码双射).为了完整,这相当于
type family X a
type instance X Bar = Bool
type family Y a
type instance Y Bar = Bar
class Seq a where
break :: X a -> (a, Y a)
build :: a -> Y a -> X a
instance Seq Bar
Run Code Online (Sandbox Code Playgroud)
编辑:Haskell约束求解机制与多参数类型类是一种逻辑语言,如prolog.在类型级编程时,您应尽可能使用函数而不是关系.三个原因:
我的建议是了解功能依赖和type
功能.类型函数与语言的其余部分相比更好一些,但有时功能依赖性是工作的最佳工具.