You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现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

效果验证

  1. 单向coerce合法:safeCoerce (two :: Number PrimesOnly) :: Number AllNumbers可以正常编译
  2. 反向coerce被禁止:尝试safeCoerce (one :: Number AllNumbers) :: Number PrimesOnly会触发类型错误
  3. 高阶类型支持:safeCoerce (Just two :: Maybe (Number PrimesOnly)) :: Maybe (Number AllNumbers)可以正常工作
  4. 模式匹配无警告:fPrime的匹配不会触发非穷举警告,因为Number PrimesOnly只能通过two/three/five构造
fPrime :: Number PrimesOnly -> Int
fPrime = \case
  Two -> 2
  Three -> 3
  Five -> 5

内容的提问来源于stack exchange,提问作者Clinton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 05:54:52