ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ConstraintKinds教程:haskell-exercises教你让约束成为一等公民,玩转Dict

ConstraintKinds教程:haskell-exercises教你让约束成为一等公民,玩转Dict ConstraintKinds教程haskell-exercises教你让约束成为一等公民玩转Dict【免费下载链接】haskell-exercisesA little course to learn about some of the more obscure GHC extensions.项目地址: https://gitcode.com/gh_mirrors/has/haskell-exercises如果你已经熟悉了 GADTs、KindSignatures、TypeFamilies却还在困惑约束Constraint到底是什么、能不能像普通值一样被传递和组合那么这篇 ConstraintKinds教程 就是为你准备的。haskell-exercises是一个逐章通关 GHC 晦涩扩展的实战课程而它的第七关正是 ConstraintKinds——一个把类型约束从幕后搬到台前、让它成为一等公民的扩展。读完本文你将彻底理解Dict的魔法并完成从会写约束到会设计约束的跃迁。什么是ConstraintKinds为什么值得花30分钟学习简单说ConstraintKinds 扩展把约束如Eq a、Show a变成了一种可以像类型一样被参数化、存储、传递的东西。它回答了一个看似天真的问题既然Maybe能接收类型参数a为什么不能有一个类型接收约束作为参数这个扩展的官方定义其实非常简短它扩展了可以作为约束使用的集合把类型参数也纳入其中。课程原文用一句话点破了本质——我们只是把可以作为约束使用的东西扩展到包含类型参数。 约束可以出现在类型签名里也可以出现在数据类型的类型参数里 约束可以像数据一样被构造、模式匹配 约束可以组合、折叠甚至跨越异构列表回顾课程路线GADTs 与 KindSignatures 打下的基础haskell-exercises的课程设计是层层递进的。前六关分别讲了GADTs、FlexibleInstances、KindSignatures、DataKinds、RankNTypes、TypeFamilies到第七关才轮到 ConstraintKinds。为什么这么排因为KindSignatures让我们给类型参数指定种类比如TTProxy (x :: Type - Type)GADTs让我们把约束直接嵌入数据构造器ConstraintKinds则更进一步让约束本身成为参数可以被装进类型三者环环相扣这也是为什么课程作者说 ConstraintKinds 的位置很难讲清楚它到底属于哪一章。核心概念速览Constraint、CProxy 与 TCProxy打开07-ConstraintKinds目录下的 ConstraintKinds.hssrc/ConstraintKinds.hs你会看到课程用几个代理类型循序渐进地演示约束参数化代理类型参数的种类含义CProxy (x :: Constraint)具体约束比如CProxy (Eq Int)TCProxy (x :: Type - Constraint)类型到约束比如TCProxy Eq、TCProxy ShowHasConstraint (c :: Type - Constraint)约束作为类型参数构造时必须满足c x这些代码都依赖从Data.Kind导入的Constraint和Type。其中TCProxy Eq的写法尤其值得注意——它意味着Eq本身被当作一个值来引用这正是约束是一等公民的直观体现。深入Dict把约束变成数据结构课程的灵魂是下面这个 5 行代码的Dictdata Dict (c :: Constraint) where Dict :: c Dict cDict把约束c变成了一个数据类型。你可以把它理解成约束的证据只要手上有一个Dict (Eq a)就相当于随身携带了一张a满足Eq的证明。Dict 如何让约束随用随取GADT 的魔法在于对Dict进行模式匹配时它所携带的约束会自动进入作用域。课程给出了一个漂亮的例子eq :: Dict (Eq a) - a - a - Bool eq Dict x y x y这里我们明明没有写Eq a 却能直接使用——因为Dict被匹配的那一刻Eq a的证据就被解锁了。这正是把约束当作数据传递的威力。为什么必须模式匹配才能解锁约束课程特意留了一个坑如果你拿到Dict (Eq a)却不做模式匹配直接写x y编译会失败。原因在于即使Dict只有一个构造器约束证据也必须通过模式匹配才能进入作用域编译器不会替你猜。这个小细节恰恰是理解约束即数据的关键。实战练习玩转约束列表ConstrainedList学习扩展最好的方式是动手。src/Exercises.hs中的练习一要求你把普通列表升级为约束列表data ConstrainedList (c :: Type - Constraint) where -- IMPLEMENT ME思考题给出的提示非常实用Nil分支空列表没有任何元素它能满足任何约束Cons分支每个元素的类型都必须满足约束c完成这个数据类型后练习还要求你用 RankNTypes 写一个foldConstrainedList——折叠函数必须对任何满足约束c的类型都有效这正是上一章 RankNTypes 的用武之地。课程的练习设计刻意让多个扩展协同作战做完会有一种原来如此的通透感。组合多个约束的小技巧练习里还有个高频痛点想同时约束Monoid a和Show a但(Monoid, Show)的种类根本不是Type - Constraint直接写会报种类错误。课程给出的经典解法是定义一个新的类把多个约束变成它的超类class (Monoid a, Show a) Constraints a instance (Monoid a, Show a) Constraints a这样ConstrainedList Constraints就能同时享受两个约束的能力。这个小技巧在真实项目中非常常见值得记进你的 Haskell 工具箱。进阶挑战HList 的foldMap练习二是课程的高潮用类型族 约束折叠异构列表HList。HList 的每个元素类型都不同要折叠它必须让每个元素都实现同一个约束并且把结果统一转换成某个 Monoid。为什么需要 Proxy 指明约束课程给出了调用示例test :: ??? HList xs - String test fold (TCProxy :: TCProxy Show) show这里必须显式传入TCProxy Show来指明我们正在用哪个约束。如果不给这个代理GHC 就无法从show的签名中确定约束的种类信息类型推断会直接卡住——Proxy 在这里扮演了类型级指针的角色把模糊的约束信息钉死。等式约束(~)的妙用练习还引导你探索 GADTs 与 TypeFamilies 带来的等式约束(~)例如f :: a ~ b a - b f ida ~ b告诉 GHC 两个类型是等价的。在 HList 的 foldMap 中你往往需要借助这种约束来弥合异构元素与统一 Monoid之间的类型鸿沟。课程源码里甚至预留了foldMap :: Monoid m (a - m) - [a] - m的经典定义作为对照帮助你思考异构版本需要哪些额外条件。如何开始练习克隆仓库并搭建环境想亲自上手这份课程只需克隆仓库仓库地址为 https://gitcode.com/gh_mirrors/has/haskell-exercises 然后进入对应章节目录$ cd 07-ConstraintKinds $ cabal repl # 或 stack repl进入交互式环境 $ cabal build # 或 stack build检查编译建议顺手安装ghcidcabal install ghcid或stack install ghcid它能在你编辑Exercises.hs时实时反馈编译错误让边改边编译的迭代体验顺畅许多$ ghcid -c stack repl仓库中的每个章节都是一个独立的 Cabal 工程如exercise07.cabal目录结构统一为src/ConstraintKinds.hs讲解与src/Exercises.hs练习对照学习非常方便。小结从使用约束到设计约束ConstraintKinds 虽然只是一个小小的扩展却打开了 Haskell 类型编程的新大门约束不再是写在签名开头的配料而是可以被构建、存储、组合、传递的一等公民。通过Dict、ConstrainedList和 HList 折叠这三个练习你已经掌握了它的全部核心用法。接下来haskell-exercises的第八关 PolyKinds 会在此基础上继续升华——很多概念将变得更加抽象。建议先把本章的Dict亲手写一遍、把每个练习跑通再进入下一关。毕竟让约束随用随取的感觉一旦体验过就再也回不去了。【免费下载链接】haskell-exercisesA little course to learn about some of the more obscure GHC extensions.项目地址: https://gitcode.com/gh_mirrors/has/haskell-exercises创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表