Haskell 约束约束的参数
我有一个第一个typeclass,它接受……ofHaskell 约束约束的参数,haskell,typeclass,Haskell,Typeclass,我有一个第一个typeclass,它接受……ofleaf的列表列表: {-# 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 ListT
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
我有第二个typeclass,它接受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)
我想结合它们来描述以2或3元组结尾的嵌套列表,这些元组属于leaf
类型:
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
,我仍然会得到类似的错误
通过内联其他两个TypeClass的代码,我可以很好地定义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)
如何将列表树叶t
类与Tups a t
类结合起来,使列表树的叶是a
的2或3元组?如果可以的话,我不介意添加额外的GHC扩展
如果重要的话,我真正的用例是为列表树建模,其中叶子是行多态记录(使用CTRex),其中记录中的每个字段都是某个typeclass的实例(例如,
Show
,以打印树)。您还有另一个问题。你的列表树
类没用
> 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)
是一个等式约束;当a
和b
是同一类型时,它保持不变。它需要使用GADTs
或TypeFamilies
)
根据,当检查lmap id[5::Integer]
时,GHC会遇到这两个实例,并发现它们都可以被实例化:1
带有leaf=[Integer]
和tree=[Integer]
,2
带有leaf=Integer
和tree=[Integer]
。要选择一个,它将检查2
的实例化是否对1
有效。也就是说:leaf=Integer
,tree=[Integer]
是否是1
的有效实例化?答案是肯定的,因为带有等式约束的上下文直到稍后才会被检查。然后,它检查可重叠的
/重叠
/重叠
杂注<如果有更好的实例,代码>可重叠的实例将被丢弃。在这种情况下,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
启用ScopedTypeVariables
和TypeApplications
以帮助它:
{-# 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
(或者用:
显式地给出类型,但这很痛苦)
但更好的办法是启用功能依赖性
,并开始四处传播,因为它们代表了类型级计算的理念:typeclass的某些参数子集可以唯一地决定其他参数。这将生成最终版本:
{-# 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)]]]
我认为使用嵌套列表的更直接表示法会让您获得更多好处:
数据嵌套a=Flat a |嵌套(嵌套[a])
几个问题。t
是否确定LTTree
中的leaf
?t
是否在Tups
中确定a
?我认为您的类需要fundeps或类型族来表达这些依赖关系。我甚至不确定您是否真的需要这些类:您想解决什么问题?这种用TypeClass表示数据的策略实际上并没有给您带来任何好处,而正确编码显然是非常重要的。正如其他评论者所指出的,为什么不表示您的数据。。。作为数据?您可以使用例如data ConstrainedRec c来表示存在的量化记录,其中CRec::Forall r c=>Rec r->ConstrainedRec c
(其中Rec
和Forall
由CTrex提供)。您没问题,data
方法可能更简单,也同样有效。我是Haskell的新手,所以我并不总是确定什么是适合这份工作的工具。不过,学习如何作为类型类进行编码是很有趣的。非常感谢这篇非常好的演练!一个问题:为什么需要(leaf~tree)=>ListTree leaf tree
而不是ListTree a
?是不是2
是1
的有效实例?还是其他原因?