如何实现Haskell中Number类型的单向coercion暴露?
实现带单向 Coercion 的受限数值类型
背景:现有两种定义的问题
1. GADT 定义的局限性
先看原始的GADT定义:
type data AllowPrimesOnly = PrimesOnly | AllNumbers data Number (allowPrimesOnly :: AllowPrimesOnly) where One :: Number AllNumbers Two :: Number allowPrimesOnly Three :: Number allowPrimesOnly Four :: Number AllNumbers Five :: Number allowPrimesOnly
基于这个定义可以写出无警告的匹配函数:
f :: Number allowPrimesOnly -> Int f = \case One -> 1 Two -> 2 Three -> 3 Four -> 4 Five -> 5 fPrime :: Number PrimesOnly -> Int fPrime = \case Two -> 2 Three -> 3 Five -> 5
这里f能处理两种Number类型,但无法将Number PrimesOnly强制转换(coerce)为Number AllNumbers——GHC默认给GADT的类型参数赋予nominal角色,不允许跨构造器的coercion。
2. 普通数据类型的问题
如果改成普通代数数据类型:
data Number (allowPrimesOnly :: AllowPrimesOnly) = One | Two | Three | Four | Five
此时单向coerce(Number PrimesOnly → Number AllNumbers)可以正常工作,但fPrime会出现非穷举匹配警告。即使通过COMPLETE编译指示压制警告:
{-# COMPLETE Two, Three, Five :: Number PrimesOnly #-}
又会出现新问题:反向coerce(Number AllNumbers → Number PrimesOnly)也能编译通过,这显然不符合需求——我们不希望把包含One/Four的Number AllNumbers转换成只允许素数的Number PrimesOnly。
需求目标
需要实现:
- 仅允许单向coerce:
Number PrimesOnly→Number AllNumbers,反向转换必须被禁止 - 支持高阶类型场景(比如
Maybe (Number PrimesOnly)可以coerce到Maybe (Number AllNumbers)) - 无需手动编写大量特定的coerce函数
解决方案:角色注解 + 类型族约束 + 受控构造函数
步骤1:开启必要扩展
{-# LANGUAGE DataKinds, RoleAnnotations, TypeFamilies, TypeOperators, UndecidableInstances #-}
步骤2:定义核心类型与约束
type data AllowPrimesOnly = PrimesOnly | AllNumbers -- 给Number的类型参数指定representational角色,允许底层表示一致时的coerce type role Number representational newtype Number (p :: AllowPrimesOnly) = Number Int deriving (Eq, Ord, Show) -- 类型族,检查coerce方向是否合法 type family CheckCoerce (from :: AllowPrimesOnly) (to :: AllowPrimesOnly) :: Constraint where CheckCoerce PrimesOnly AllNumbers = () CheckCoerce AllNumbers PrimesOnly = TypeError ('Text "禁止将Number AllNumbers转换为Number PrimesOnly") CheckCoerce a a = () -- 安全的coerce函数,带上方向约束 safeCoerce :: CheckCoerce from to => Number from -> Number to safeCoerce = coerce
步骤3:定义受控构造函数
通过构造函数限制不同Number类型的合法值:
one :: Number AllNumbers one = Number 1 two :: Number p two = Number 2 three :: Number p three = Number 3 four :: Number AllNumbers four = Number 4 five :: Number p five = Number 5
效果验证
- 单向coerce合法:
safeCoerce (two :: Number PrimesOnly) :: Number AllNumbers可以正常编译 - 反向coerce被禁止:尝试
safeCoerce (one :: Number AllNumbers) :: Number PrimesOnly会触发类型错误 - 高阶类型支持:
safeCoerce (Just two :: Maybe (Number PrimesOnly)) :: Maybe (Number AllNumbers)可以正常工作 - 模式匹配无警告:
fPrime的匹配不会触发非穷举警告,因为Number PrimesOnly只能通过two/three/five构造
fPrime :: Number PrimesOnly -> Int fPrime = \case Two -> 2 Three -> 3 Five -> 5
内容的提问来源于stack exchange,提问作者Clinton
相关产品推荐
相关产品推荐

