如何模式匹配其实例是递归数据类型的类型类?

时间:2017-04-21 15:41:38

标签: haskell pattern-matching typeclass recursive-datastructures

这个问题是对How to pattern match against a typeclass value?的跟进。

我正在启动一阶逻辑的宠物项目,并决定将Haskell用于此目的。我的第一个障碍是定义一阶逻辑的公式'因此数据类型:

data Formula v  = Belong v v                      -- x in y
                | Bot                             -- False
                | Imply (Formula v) (Formula v)   -- p -> q
                | Forall v (Formula v)            -- forall x, p

但是,我不愿意根据实现细节编写代码,而是宁愿抽象出细节,以防我改变主意,或者如果我想重用其他实现的功能,那么类型类:< / p>

class FirstOrder m where
  belong    :: v -> v -> m v
  bot       :: m v
  imply     :: m v -> m v -> m v
  forall    :: v -> m v -> m v
  asFormula :: m v -> Formula v

我添加了最后一个函数asFormula以便使用ViewPatterns(如上面的链接所示),以便能够对此类型类的值进行模式匹配。

现在假设我想定义一个简单的函数:

subst :: (FirstOrder m) => (v -> w) -> m v -> m w

根据给定的映射f::v -> w替换公式中的变量(感觉像fmap),因此公式belong x y映射到belong (f x) (f y)等,然后使用{ {1}}:

ViewPatterns

到目前为止一直很好......

但是,在尝试撰写subst f (asFormula -> Belong x y) = belong (f x) (f y) 时:

subst f p->q = (subst f p) -> (subst f q)

我得到一个类型错误,它会让人联想到它是完全合理的:模式匹配将subst f (asFormula -> Imply p q) = imply (subst f p) (subst f q) p绑定到q类型的元素而不是所需的类型{ {1}}。

现在我可以看到问题了,甚至可以通过在类型类中添加Formula v函数来转换回类型m v来考虑解决问题的方法,但我认为这很疯狂从性能的角度来看(和fromFormula一样疯狂),为了保持代码的通用性,需要支付巨大的代价。

所以我现在在尝试通过自由代数(递归数据类型)上的结构递归来定义一个简单的函数,但是我希望抽象出实现细节(并编写类代码而不是数据类型)我似乎是一个不可能的位置。

有没有出路,或者我应该忘记抽象并使用递归数据类型?

1 个答案:

答案 0 :(得分:2)

这看起来像是一种过度概括,但无论如何我们都要忽略它。

您可以使用显式F-(共)代数和不动点来解决这个问题。

data FormulaF v k
   = Belong v v                      -- x in y
   | Bot                             -- False
   | Imply (k v) (k v)               -- p -> q
   | Forall v (k v)                  -- forall x, p

newtype Formula v = Formula (FormulaF v Formula)      
   -- fixed point. You might not need it, but it's nice to have.
   -- 

然后,你可以做

class FirstOrder m where
  belong    :: v -> v -> m v
  bot       :: m v
  imply     :: m v -> m v -> m v
  forall    :: v -> m v -> m v
  asFormula :: m v -> FormulaF v m

subst :: (FirstOrder m) => (v -> w) -> m v -> m w
subst f (asFormula -> Belong x y) = belong (f x) (f y)
subst f (asFormula -> Imply p q)  = imply (subst f p) (subst f q)

现在可以正常工作,因为asFormula不会递归地将整个m v转换为完整的公式,但它会转换为FormulaF v m,它看起来像一个表面公式(第一个构造函数)你模式匹配,比如Imply),但内部仍然看起来像m v

如果你真的想要采用这种过于普遍的方法,也许你应该看看recursion-schemes,F-algebras和F-coalgebras,以及它们相关的cata- / ana- / hylo- / para- / whatever态射。

最后,我建议避免尝试在FP中调整OOP设计模式。有时你可以用这种方式来制作东西,但它通常可以是单一的。例如,在Haskell中,我们习惯于具有固定数量的构造函数的类型(但是对它们进行操作的一组开放函数),就像在OOP接口中具有固定数量的方法(但是一组开放的子类) 。人们可以尝试概括这一点,但它很复杂,应该只在需要时才能完成。