【问题标题】:Constraining the argument of a constraint约束约束的参数
【发布时间】:2017-11-17 14:45:17
【问题描述】:

我有一个第一个类型类,它接受 leaf 的列表列表列表:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, UndecidableInstances #-}
class ListTree leaf t where
  lmap :: (leaf -> leaf) -> t -> t
instance ListTree leaf leaf where lmap f v = f v
instance ListTree leaf t => ListTree leaf [t] where lmap f v = map (lmap f) v

我有第二个类型类,它接受 a 的 2 元组和 3 元组:

class Tups a t where
  tmap :: (a -> a) -> t -> t
instance Tups a (a,a) where tmap f (x,y) = (f x, f y)
instance Tups a (a,a,a) where tmap f (x,y,z) = (f x, f y, f z)

我想将它们结合起来描述以一些leaf 类型的 2 或 3 元组结尾的嵌套列表:

class LTTree leaf t where
  ltmap :: (a -> a) -> t -> t
instance (Tups leaf x, ListTree x t) => LTTree leaf t where ltmap f v = lmap (tmap f) v

但是,最后一段代码给了我几个错误:

Could not deduce (LTTree leaf0 t)
  from the context: LTTree leaf t

In the ambiguity check for ‘ltmap’
  To defer the ambiguity check to use sites, enable AllowAmbiguousTypes

Could not deduce (Tups leaf x0)
  from the context: (Tups leaf x, ListTree x t)

In the ambiguity check for an instance declaration
  To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
  In the instance declaration for ‘LTTree leaf t’

如果我添加AllowAmbiguousTypes,我仍然会收到类似的错误。

不过,我可以通过内联其他两个类型类的代码来很好地定义 LTTree 类:

class LTTree leaf t where
  ltmap :: (leaf -> leaf) -> t -> t
instance LTTree leaf (leaf,leaf) where ltmap f (x,y) = (f x, f y)
instance LTTree leaf (leaf,leaf,leaf) where ltmap f (x,y,z) = (f x, f y, f z)
instance LTTree leaf t => LTTree leaf [t] where ltmap f v = map (ltmap f)

如何将ListTree leaf t 类与Tups a t 类结合起来,以便列表树的叶子是a 的2 或3 元组?我不知道如果有帮助,请考虑添加额外的 GHC 扩展。

如果重要的话,我真正的用例是对列表树建模,其中叶子是行多态记录(使用 CTRex),其中记录中的每个字段都是某个类型类的实例(例如 Show,要打印树)。

【问题讨论】:

  • 我认为你会从使用更直接的嵌套列表表示中获得更多好处:data Nested a = Flat a | Nested (Nested [a])
  • 几个问题。 t 不是在LTTree 中确定leaf 吗? t 不是在Tups 中确定a 吗?我认为您的课程需要fundeps 或类型族来表达这些依赖关系。不过,我什至不确定您是否真的需要这些课程:您要解决什么问题?
  • 这种用类型类表示数据的策略并没有真正给你任何东西,而且获得正确的编码显然不是微不足道的。正如其他评论者所指出的,为什么不将您的数据表示为……作为数据?您可以表示存在量化的记录,例如data ConstrainedRec c where CRec :: Forall r c => Rec r -> ConstrainedRec c(其中 RecForall 由 CTrex 提供)。
  • 你没关系,data 方法可能更简单,也同样有效。我是 Haskell 的新手,所以我并不总是确定什么是适合这项工作的工具。学习如何将编码作为类型类进行编码是很有趣的。

标签: haskell typeclass


【解决方案1】:

你还有一个问题。你的ListTree 课程没用!

> lmap id [5 :: Integer]
error: blah blah
> lmap id (5 :: Integer)
error: blah blah
> lmap (+2) [[5::Integer], [], [2,3]]
error: blah blah

先添加一些黑魔法来解决这个问题:

{-# LANGUAGE FunctionalDependencies, GADTs #-}
class ListTree leaf tree where lmap :: (leaf -> leaf) -> (tree -> tree)
instance {-# OVERLAPPABLE #-} (leaf ~ tree) => ListTree leaf tree where -- 1
  lmap = id
instance ListTree leaf tree => ListTree leaf [tree] where -- 2
  lmap = map . lmap

(a ~ b) 是一个等式约束;当ab 是同一类型时它成立。它需要使用GADTsTypeFamilies。)

根据the rules of instance resolution,在检查lmap id [5 :: Integer]时,GHC会遇到这两个实例,发现它们都可以被实例化:1leaf = [Integer]tree = [Integer]2leaf = Integer和@ 987654336@。要选择一个,它检查2 的实例化是否对1 有效。即:leaf = Integertree = [Integer]1 的有效实例化吗?答案是肯定的,因为直到稍后才会检查具有等式约束的上下文。然后,它检查OVERLAPPABLE/OVERLAPPING/OVERLAPS pragmas。如果周围有更好的实例,OVERLAPPABLE 实例将被丢弃。在这种情况下,1 被丢弃,只剩下2。它被使用了,所以lmap id [5 :: Integer] == [5]。其他示例也可以。

LTTree,你有一个错字。应该是:

class LTTree leaf tree where ltmap :: (leaf -> leaf) -> tree -> tree

使用leaf,而不是a。您还有另一个问题:推理器非常生气,因为您让它完成所有这些工作:

> instance (Tups leaf x, ListTree x t) => LTTree leaf t where ltmap f v = lmap (tmap f) v
error: blah blah

启用ScopedTypeVariablesTypeApplications 来帮助它:

{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}
instance (Tups leaf x, ListTree x t) => LTTree leaf t where ltmap f v = lmap @x @t (tmap @leaf @x f) v

(或者只是用:: 明确地给出类型,但这很痛苦)

但更好的想法是启用FunctionalDependencies 并开始喷洒它们,因为它们代表了类型级计算的理念:类型类的参数的某些子集可以唯一地确定其他参数。这将产生最终版本:

{-# LANGUAGE FlexibleInstances
           , FunctionalDependencies
           , GADTs
           , UndecidableInstances #-}
class ListTree leaf tree | tree -> leaf where lmap :: (leaf -> leaf) -> tree -> tree
instance {-# OVERLAPPABLE #-} (leaf ~ tree) => ListTree leaf tree where lmap = id
instance ListTree leaf tree => ListTree leaf [tree] where lmap = map . lmap
-- The tree determines the leaf

class Tups leaf tree | tree -> leaf where tmap :: (leaf -> leaf) -> tree -> tree
-- Change instances to help type inference along:
instance (a ~ b) => Tups a (a, b) where tmap f (x, y) = (f x, f y)
instance (a ~ b, b ~ c) => Tups a (a, b, c) where tmap f (x, y, z) = (f x, f y, f z)
-- tmap (+2) (5 :: Integer, 3, 2) now works because the type info from 5 spreads out
-- via the equality constraints

class LTTree leaf tree | tree -> leaf where ltmap :: (leaf -> leaf) -> tree -> tree
instance (Tups leaf mid, ListTree mid tree) => LTTree leaf tree where ltmap = lmap . tmap
-- mid can be deduced from tree via ListTree's fundep
-- leaf can be deduced from mid via Tups' fundep
-- leaf can be deduced from tree

它有效!

> ltmap (+(2 :: Integer)) [[[(5, 2)]], [], [[(2, 8), (4, 5)]]]
[[[(7,4)]],[],[[(4,10),(6,7)]]]

【讨论】:

  • 非常感谢这个非常好的演练!一个问题:为什么需要(leaf ~ tree) => ListTree leaf tree 而不是ListTree a a ?是不是21 的有效实例?还是其他原因?
猜你喜欢
  • 1970-01-01
  • 2021-09-04
  • 2019-10-11
  • 1970-01-01
  • 1970-01-01
  • 2021-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多