尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

组合扩展的威力:从haskell-exercises学习GADTs+DataKinds+TypeFamilies协同作战

组合扩展的威力:从haskell-exercises学习GADTs+DataKinds+TypeFamilies协同作战 组合扩展的威力从haskell-exercises学习GADTsDataKindsTypeFamilies协同作战【免费下载链接】haskell-exercisesA little course to learn about some of the more obscure GHC extensions.项目地址: https://gitcode.com/gh_mirrors/has/haskell-exercises学习 Haskell 时很多人都会遇到一个瓶颈普通类型系统已经用得很熟但面对GADTs、DataKinds、TypeFamilies这些 GHC 扩展却不知从何下手。haskell-exercises正是一套为这类学习者量身定制的练习课程它用一个接一个的微型项目带你把把错误挡在编译期这件事做到极致。本文将带你拆解这三个核心扩展并展示它们组合起来时的惊人威力。为什么单独学扩展很容易学废很多教程喜欢把扩展一个个单独讲但真实项目里它们几乎总是抱团出现。GADTs 给了你为每个构造器定制类型的自由DataKinds 让数据提升到类型层面TypeFamilies 则让类型本身可以计算。单独看每个都像是魔术组合起来才是真正的工程利器。haskell-exercises 的目录结构就是按扩展逐一组织的从01-GADTs一直到10-FunctionalDependencies每个练习目录都包含两个核心文件讲解版和练习版。例如01-GADTs/src/GADTs.hs是概念讲解而01-GADTs/src/Exercises.hs则是留给你的填空题答案藏在answers分支里。第一块拼图GADTs 让类型参数活起来 普通代数数据类型ADT有个限制所有构造器必须返回同一个类型。而广义代数数据类型GADTs打破了这个规则——每个构造器可以精确指定自己的返回类型还能在构造器里携带约束。看一个经典例子来自01-GADTs/src/GADTs.hsdata ShowList where ShowNil :: ShowList ShowCons :: Show a a - ShowList - ShowList这里ShowCons里藏着一个存在类型a它可以是任意类型只要实现了Show。于是你可以写出ShowCons Tom (ShowCons 25 (ShowCons True ShowNil))这种混合类型的列表——类型不同没关系只要都能被show出来就行。GADTs 另一个惊人能力是让类型检查器帮你排除不可能。同一个文件中MysteryBox和HList练习展示了当模式匹配某个构造器时GHC 能自动知道其他分支不可能发生从而允许你写出不需要 Maybe 的 total function。第二块拼图DataKinds 把数据升维到类型层 如果说 GADTs 是类型参数根据构造器变化那 DataKinds 就是值也能变成类型。打开04-DataKinds/src/DataKinds.hs你会看到一颗自然数的提升data Natural Zero | Successor Natural开启DataKinds后Natural同时成为一个kindZero和Successor变成类型层面的构造器。更妙的是我们可以用它给列表装上长度data Vector (length :: Natural) (a :: Type) where VNil :: Vector Zero a VCons :: a - Vector n a - Vector (Successor n) a于是长度信息被写进了类型head函数不再需要Maybe——因为类型为Vector (Successor n) a的值不可能是空列表。甚至zip函数原本需要四种情况匹配现在类型检查器能证明长度相等的两个向量要么都是空、要么都是非空代码直接砍掉一半分支。第三块拼图TypeFamilies 让类型也会计算 ➕06-TypeFamilies/src/TypeFamilies.hs只用了寥寥几十行就讲清楚了类型族TypeFamilies的本质类型层面的函数。type family Add (x :: Nat) (y :: Nat) :: Nat where Add Z y y Add (S x) y S (Add x y)这套递归定义和值层面的add函数几乎一一对应。类型族还能和单例类型singleton配合让根据输入类型决定输出类型成为可能。比如定义一个类型层面的Not就可以写出not :: SBool input - SBool (Not input)——布尔值翻转后类型也跟着翻转。协同作战1 1 1 3 的经典案例 ⚔️单独的扩展已经很强但真正震撼的是它们的组合。在04-DataKinds的练习里你可以构建一个异构列表 HList它把每个元素的类型都记录在类型层面在06-TypeFamilies的练习里则要求你用类型族写出类型层面的加减法、比较、甚至素数筛。最能体现三者合力的场景是用类型做协议/状态机。DataKinds 练习中有个著名的例子设计一个文件操作Program类型在类型层面记录文件当前是否打开文件未打开时ReadFile、WriteFile根本构造不出来未打开文件时调用CloseFile编译直接报错程序结束时类型保证文件必然已经关闭。这就是让非法状态不可表示make illegal states unrepresentable的威力——过去要靠运行时检查和测试才能发现的 bug现在编译器直接帮你拦下了。类似的还有 GADTs 练习里的类型对齐函数列表TypeAlignedList它保证函数列表里前一个函数的输出类型恰好等于后一个函数的输入类型拼出composeTALs :: TypeAlignedList b c - TypeAlignedList a b - TypeAlignedList a c这样的安全组合。如何开始动手练习️这套课程的使用方式非常简单克隆仓库git clone https://gitcode.com/gh_mirrors/has/haskell-exercises进入任意练习目录例如01-GADTs/用cabal repl或stack repl进入交互环境也可以用ghcid -c stack repl实现保存即检查打开src/Exercises.hs把error Implement me!逐个替换成你的实现卡住时切到answers分支对照答案每个练习目录下的exercise*.cabal文件都已配置好所需的语言扩展你几乎不需要手动{-# LANGUAGE ... #-}专心解题即可。学习路线建议 ️如果你完全零基础建议按官方顺序推进先01-GADTs掌握存在类型与类型导向的模式匹配再03-KindSignatures理解 kind 的概念这是 DataKinds 的地基接着04-DataKinds体验类型层面的编程最后06-TypeFamilies学会类型计算。之后还有07-ConstraintKinds、08-PolyKinds等进阶扩展等你解锁。结语 ✨很多人觉得 Haskell 的进阶扩展华而不实但 haskell-exercises 用大量精心设计的练习证明当 GADTs、DataKinds、TypeFamilies 协同作战时你是在把程序员的直觉写成可编译的类型约束。编译通过的那一刻不仅意味着程序能跑更意味着你正在写的代码在逻辑上就是正确的。从复制仓库到写完第一道题整个过程可能只需要一个下午。但这一下午的收获会彻底改变你写类型的方式。【免费下载链接】haskell-exercisesA little course to learn about some of the more obscure GHC extensions.项目地址: https://gitcode.com/gh_mirrors/has/haskell-exercises创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表