好的。这里的问题非常抽象。忍受我。
我有一堆"单位",每个都有某些属性。这些属性在Seq
类中定义,如下所示:
class Seq a x y where
break :: x -> (a, y)
build :: a -> y -> x
从概念上讲,a
类型是重要类型,x
是用于生成a
的上下文,y
是用于生成任何进一步的上下文{ {1}}个实例。 Seq
向下break
,Seq
允许您重建它。
在单个build
个实例上,这样可以正常工作。但是,我也有一个如下所示的数据构造函数:
Seq
整个操作的目标是能够组成data a :/: b = a :/: b deriving (Eq, Ord)
infixr :/:
个实例。
例如,如果我有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
实例:
Seq
(编译实施细节,详见here)
和良好类型的data Foo
instance Seq Foo Integer Bool
data Bar
instance Seq Bar Bool Bar
data Baz
instance Seq Baz Bar ()
函数:
break
然后我甚至无法编译:
myBreak :: Integer -> (Foo :/: Bar :/: Baz)
myBreak = fst . break' where
break' = break :: Integer -> (Foo :/: Bar :/: Baz, ())
在我看来,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), ())
无法确定是否有一个"工作线程"来自Foo→Bar→Baz的背景我怎样才能说服编译器这个类型很好?
这是我第一次进行类型编程之旅,所以我毫无疑问地打破了一些完善的规则。我很好奇我在这里做错了什么,但我也愿意接受有关如何更好地实现目标的建议。
答案 0 :(得分:10)
您可能需要另外两个语言扩展才能完成此工作。一般的问题是多参数类型类需要编码开放世界的假设,所以假设有人出现并添加
instance Seq Bar Integer Float
instance Seq Baz Float ()
编译器无法知道要使用哪组实例。这可能会导致未定义的行为。如果编译器只是“弄清楚”没有其他实例可用,那么你一定意味着这些,那么你就会陷入尴尬的境地,因为有人添加了一个实例,因此代码可能会中断。
解决方案是使用某种类型级别的功能。因此,如果a
是重要类型,则可能只有x
和y
的一个组合与a
一致。一种方法是使用functional dependencies。
class Seq a x y | a -> x, a -> y where
break :: x -> (a, y)
build :: a -> y -> x
功能依赖性非常普遍,可以使用两种编码双射型函数
class Seq a x y | a -> x, a -> y, x y -> a where
break :: x -> (a, y)
build :: a -> y -> x
这取决于你想要的语义究竟是什么。或者,您可以使用type families扩展名使用稍微更优雅的“关联类型同义词”方法。
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
此版本目前被认为是最惯用的版本,并且与功能性依赖版本相比具有一些优势(但是,您不能以这种方式编码双射)。为完整起见,这相当于
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
编辑:使用多参数类型类的Haskell约束求解机制是一种类似prolog的逻辑语言。在类型级编程时,您应该尽可能使用函数而不是关系。原因有三:
我的建议是了解功能依赖性和type
函数。类型函数与语言的其余部分相比更好一些,但有时功能依赖性是工作的最佳工具。