Haskell 如何使用受约束的元素创建异构列表(又称HLists)?
我一直在尝试使用类型族来抽象UI工具包。当我试图利用HLists时,我完全失败了(http://homepages.cwi.nl/~ralf/HList/)来改进API 我的API最初看起来像这样:Haskell 如何使用受约束的元素创建异构列表(又称HLists)?,haskell,Haskell,我一直在尝试使用类型族来抽象UI工具包。当我试图利用HLists时,我完全失败了(http://homepages.cwi.nl/~ralf/HList/)来改进API 我的API最初看起来像这样: {-# LANGUAGE TypeFamilies #-} class UITK tk where data UI tk :: * -> * stringEntry :: (UITK tk) => UI tk String intEntry :: (UITK tk) =&
{-# LANGUAGE TypeFamilies #-}
class UITK tk where
data UI tk :: * -> *
stringEntry :: (UITK tk) => UI tk String
intEntry :: (UITK tk) => UI tk Int
tuple2UI :: (UI tk a,UI tk b) -> (UI tk (a,b))
tuple3UI :: (UI tk a,UI tk b,UI tk c) -> (UI tk (a,b,c))
tuple4UI :: (UI tk a,UI tk b,UI tk c,UI tk d) -> (UI tk (a,b,c,d))
ui :: (UITK tk) => (UI tk (String,Int))
ui = tuple2UI (stringEntry,intEntry)
这是可行的,但是UI组合器在元组上工作,因此我需要为每个元组大小使用不同的函数。我想我可以使用HLists之类的东西,但要么不可能,要么希望如此,我只是缺少必要的fu类型
以下是我的尝试:
{-# LANGUAGE TypeFamilies,FlexibleInstances,MultiParamTypeClasses #-}
-- A heterogeneous list type
data HNil = HNil deriving (Eq,Show,Read)
data HCons e l = HCons e l deriving (Eq,Show,Read)
-- A list of UI fields, of arbitrary type, but constrained on their
-- tk parameter. The StructV associated type captures the return
-- type of the combined UI
class (UITK tk) => FieldList tk l
where type StructV tk l
instance (UITK tk) => FieldList tk HNil
where type StructV tk HNil = HNil
instance (UITK tk, FieldList tk l) => FieldList tk (HCons (UI tk a) l)
where type StructV tk (HCons (UI tk a) l) = (HCons a (StructV tk l))
fcons :: (UITK tk, FieldList tk l) => UI tk a -> l -> HCons (UI tk a) l
fcons = HCons
-- Now the abstract ui toolkit definition
class UITK tk where
data UI tk :: * -> *
stringEntry :: (UITK tk) => UI tk String
intEntry :: (UITK tk) => UI tk Int
structUI :: (FieldList tk l) => l -> (UI tk (StructV tk l))
-- this doesn't work :-(
ui :: (UITK tk) => (UI tk (HCons String (HCons Int HNil)))
ui = structUI (fcons stringEntry
(fcons intEntry
HNil ))
最后的定义给了我几个错误,第一个错误是:
Z.hs:38:6:
Could not deduce (FieldList
tk (HCons (UI tk0 String) (HCons (UI tk1 Int) HNil)))
arising from a use of `structUI'
from the context (UITK tk)
bound by the type signature for
ui :: UITK tk => UI tk (HCons String (HCons Int HNil))
at Z.hs:(38,1)-(40,21)
Possible fix:
add (FieldList
tk
(HCons
(UI tk0 String) (HCons (UI tk1 Int) HNil))) to the context of
the type signature for
ui :: UITK tk => UI tk (HCons String (HCons Int HNil))
or add an instance declaration for
(FieldList tk (HCons (UI tk0 String) (HCons (UI tk1 Int) HNil)))
In the expression:
structUI (fcons stringEntry (fcons intEntry HNil))
In an equation for `ui':
ui = structUI (fcons stringEntry (fcons intEntry HNil))
如果不完全理解这一点,我想我至少可以看到其中一个问题。我没有成功地通知编译器3个tk类型参数都是上面提到的同一类型(即它指的是tk、tk0、tk1)。我不明白这一点-我的fcons构造函数旨在保持构建的HList的UI tk参数一致
这是我第一次体验类型族和多参数类型类,所以我可能遗漏了一些基本的东西
是否可以使用受约束的元素构造异构列表?我哪里出错了?类型错误来自此逻辑链:“ui”的最外层为“structui”,而“structui::(FieldList tk l)=>“需要”(FieldList tk l),其中“tk”和“l”必须与您为“ui”编写的类型签名相匹配 在类型变量“tk”中,每样东西都是多态的 类型检查器为structui/fcons的参数提供了一个不同的tk0,并且仅仅因为您有一个具有匹配tk的实例,并不意味着我不会出现并创建一个具有不同tk的FieldList实例。因此,类型检查器被卡住 以下是我如何为类型检查器修复此问题:
-- Use this instance instead of the one you wrote
instance (UITK tk, FieldList tk l, tk ~ tk') => FieldList tk (HCons (UI tk' a) l)
where type StructV tk (HCons (UI tk' a) l) = (HCons a (StructV tk l))
-- Now this works :)
ui :: (UITK tk) => (UI tk (HCons String (HCons Int HNil)))
ui = structUI (fcons stringEntry
(fcons intEntry
HNil ))
替换实例匹配tk和tk'的所有可能组合,然后要求它们相同。没有人能不重叠地编写另一个这样的实例
回应timbod的评论:
考虑这个代码,请注意(TeNUM 97):查尔是“A”< /P>
class TwoParam a b where
combine :: a -> b -> (a,b)
combine = (,)
instance TwoParam c c
t1 :: (TwoParam Char b) => Char -> b -> (Char,b)
t1 = combine
main = print (t1 'a' (toEnum 97))
此操作失败,并显示以下消息:
为什么??类型检查器推断(toEnum 98)有一些枚举类型,这可能是Char,但它不会推断它必须是Char。即使(TwoParam Char b)的唯一可用实例需要将b与Char匹配,类型检查器也不会将(toEnum 97)与Char匹配。编译器在这里是正确的,因为我可以稍后编写另一个实例:
-- instance TwoParam Char Integer
对于第二个(重叠)实例,应该选择哪个实例不再明显。解决方法是使用上述“技巧”:
-- instance (c ~ d) => TwoParam c d
类型检查器在选择实例时只查看“TwoParam c d”,这与所有内容都匹配。然后它尝试满足约束
Char~typeOf(来自Enum 98)
这会成功的。使用技巧“main”打印('a','a')您可以添加
mapUI::(a->b)->UI-tk-a->UI-tk-b
;然后,您只需要zipUI::UI-tk-a->UI-tk-b->UI-tk(a,b)
,就可以使用mapUI
展平嵌套的元组。当然,这相当于要求UI-tk
成为Applicative
的一个实例(假设可以提供pure
),这是我的建议。以上是我真正API的简化-我已经有了mapUI。我也使用过类似嵌套元组的方法,但在实践中效果不太好,因为构建具体UI的代码需要一次看到所有子对象,而不是一次看到一对子对象。。。ie构建复合UI需要是一个n元函数的单一应用,而不是二元函数的重复应用。这很公平。我仍然建议在实例头中需要Functor(UI-tk)
,而不是定义自己的mapUI
函数。实际上,只要看一下我的代码,我就意识到mapUI需要是双向的,并处理错误。所以我的签名是:mapUI::(a->ErrVal b)->(b->a)->UI-tk-a->UI-tk-b。它不可能是函子。谢谢你的建议。啊,我明白了。这让我想起,;如果你还没有看的话,你可能想看一看。用一个过度多态的实例头延迟统一可能是我最喜欢的类型级技巧。它非常有用。我同意这感觉像是骗局。最终,在类型/实例声明中编程只是为了指导类型推断引擎中的统一。虽然在这种情况下,问题更多的是实例选择工作方式的局限性。但是一般来说,很多类型级别的东西都没有简单的一级表示,所以你必须这样做才能间接得到你想要的。谢谢!我不知道这样的“平等约束”。我仍然有点不清楚为什么我必须将这两个参数声明为不同的名称(tk,tk'),然后将它们等同于“tk~tk”。如果我统一使用一个名字,为什么不起作用?
-- instance (c ~ d) => TwoParam c d