Haskell单构造类型与()的Coercible强制转换问题
单构造类型
Coercible与()强制转换问题解答 原生coerce不支持空构造data类型转()的原因
Coercible的核心规则是仅当两个类型的运行时内存表示完全一致时,才允许零成本强制转换:
newtype Foo = Foo ()定义的类型,运行时和()的表示完全等价,没有额外包装层,因此可以直接使用coerce转换。data Foo = Foo定义的无参数构造枚举类型,GHC为其生成的运行时表示是独立的枚举tag,和()的底层表示并不匹配,哪怕二者逻辑上同构,原生Coercible也不支持直接转换。
泛型Rep方案的报错原因
你尝试的泛型方案编译失败,有两个核心问题:
- 两个类型的泛型结构确实存在表层差异:无参数构造
data T = T的泛型表示是D1 元数据 (C1 构造器元数据 U1),而()的泛型表示是D1 元数据 (C1 构造器元数据 (S1 选择器元数据 U1)),多了一层代表记录字段的S1包装。虽然这些M1开头的元数据类型在运行时会被完全擦除,但直接要求Rep a ()和Rep b ()满足Coercible时,GHC不会自动穿透这些编译期层做结构匹配。 - 泛型使用方式有误:
GHC.Rep a是kind为* -> *的类型构造子,使用时不需要将其应用到()类型上。泛型结构的函子参数对无字段类型的运行时表示没有影响,强行指定()作为参数反而会引入多余的类型约束。
可用的实现方案
方案1:手写泛型同构转换(零运行时开销)
不要直接要求两个类型的Rep可Coercible,而是写一个类型类递归穿透所有编译期元数据层,只匹配核心运行时结构,即可实现自动生成同构映射:
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} import GHC.Generics -- 递归匹配泛型核心结构的转换类 class GIso repA repB where gIso :: repA p -> repB p -- 穿透所有元数据包装层(D1/C1/S1,均为运行时擦除的newtype包装) instance GIso f g => GIso (M1 i meta f) (M1 i' meta' g) where gIso (M1 x) = M1 (gIso x) -- 无字段构造器的核心结构直接对应 instance GIso U1 U1 where gIso U1 = U1 -- 对外暴露的同构转换接口 iso :: forall a b. (Generic a, Generic b, GIso (Rep a) (Rep b)) => a -> b iso = to . gIso . from @a -- 测试代码可正常编译 data Foo = Foo deriving Generic f :: Foo -> () f = iso
所有元数据层在编译阶段都会被优化掉,最终生成的代码和手动写f Foo = ()完全等价,属于零成本转换。
方案2:手动编写同构映射
对于单个类型的场景,最直接的方式是手动写双向映射,没有任何泛型相关的编译开销:
data Foo = Foo fooToUnit :: Foo -> () fooToUnit Foo = () unitToFoo :: () -> Foo unitToFoo () = Foo
编译器同样会将这类简单的模式匹配优化为零成本转换。
内容的提问来源于stack exchange,提问作者Sridhar Ratnakumar
相关产品推荐
相关产品推荐

