说服编译器存在有效的实例链

时间:2012-02-20 04:38:51

标签: haskell types

好的。这里的问题非常抽象。忍受我。

我有一堆"单位",每个都有某些属性。这些属性在Seq类中定义,如下所示:

class Seq a x y where
    break :: x -> (a, y)
    build :: a -> y -> x

从概念上讲,a类型是重要类型,x是用于生成a的上下文,y是用于生成任何进一步的上下文{ {1}}个实例。 Seq向下breakSeq允许您重建它。

在单个build个实例上,这样可以正常工作。但是,我也有一个如下所示的数据构造函数:

Seq

整个操作的目标是能够组成data a :/: b = a :/: b deriving (Eq, Ord) infixr :/: 个实例。

例如,如果我有Seqab c的所有实例,那么Seq的上下文会被输入ab的订阅源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的背景我怎样才能说服编译器这个类型很好?

这是我第一次进行类型编程之旅,所以我毫无疑问地打破了一些完善的规则。我很好奇我在这里做错了什么,但我也愿意接受有关如何更好地实现目标的建议。

1 个答案:

答案 0 :(得分:10)

您可能需要另外两个语言扩展才能完成此工作。一般的问题是多参数类型类需要编码开放世界的假设,所以假设有人出现并添加

instance Seq Bar Integer Float

instance Seq Baz Float ()

编译器无法知道要使用哪组实例。这可能会导致未定义的行为。如果编译器只是“弄清楚”没有其他实例可用,那么你一定意味着这些,那么你就会陷入尴尬的境地,因为有人添加了一个实例,因此代码可能会中断。

解决方案是使用某种类型级别的功能。因此,如果a是重要类型,则可能只有xy的一个组合与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的逻辑语言。在类型级编程时,您应该尽可能使用函数而不是关系。原因有三:

  1. 你可以从一个函数到一个约束,但反之不然
  2. Haskell不是序幕。大多数Haskell程序员发现函数更容易推理。如果您不了解Prolog(或其他逻辑语言),那么坚持功能是非常重要的,因为关系和函数编程之间的差异几乎与功能和命令式编程之间的差异一样大。
  3. 尽管有相似之处,Haskell的约束求解器不是Prolog(直觉上所有子句都在头部之后立即切换,如果您不知道这意味着什么,请不要担心)。关系类型编程比使用完全回溯的语言编码要困难得多。
  4. 我的建议是了解功能依赖性和type函数。类型函数与语言的其余部分相比更好一些,但有时功能依赖性是工作的最佳工具。