是否可以在自定义类型和标准库类型之间建立Coercible实例?
举一个简单的例子,假设我想要一个类型来表示井字游戏标记:
data Mark = Nought | Cross
这与 Bool
Prelude> :info Bool
data Bool = False | True -- Defined in ‘GHC.Types’
但是Coercible Bool Mark
它们之间没有,即使我导入GHC.Types
(我最初认为 GHC 可能需要Bool
定义可见的位置),获得此实例的唯一方法似乎是通过newtype
.
也许我可以这样来定义newtype Mark = Mark Bool
和界定Nought
,并Cross
具有双向模式,我希望有东西比这更简单。
回答
不幸的是,你运气不好。正如文档Data.Coerce
解释的那样,“可以假设存在以下三种实例:”
-
自实例,如
instance Coercible a a
, -
用于在表示或幻像类型参数不同的数据类型的两个版本之间进行强制的实例,如
instance Coercible a a' => Coercible (Maybe a) (Maybe a')
和 -
新类型之间的实例。
此外,“尝试手动声明 的实例Coercible
是一个错误”,这就是您所得到的。任意不同的数据类型之间没有实例,即使它们看起来相似。
这似乎是令人沮丧的限制,但请考虑这一点:如果Coercible
在Bool
and之间有一个实例Mark
,是什么阻止它强制执行Nought
toTrue
和Cross
to False
?可能Bool
和Mark
在内存中以相同的方式表示,但不能保证它们在语义上足够相似以保证Coercible
实例。
您使用 newtype 和模式同义词的解决方案是一种很好的、安全的解决问题的方法,即使它有点烦人。
另一种选择是考虑使用Generic
. 例如,genericCoerce
从另一个问题中查看的想法
回答
这是不可能的,模式同义词现在是一个很好的解决方案。我经常使用这样的代码来为恰好与现有原始类型同构的类型派生有用的实例。
module Mark
( Mark(Nought, Cross)
) where
newtype Mark = Mark Bool
deriving stock (…)
deriving newtype (…)
deriving (…) via Any
…
pattern Nought = Mark False
pattern Cross = Mark True
不相关 ADT 之间的强制也不在允许的不安全强制列表中。最后我知道,在 GHC 的实践中,只有当相关值被完全评估时,Mark
和之间的强制Bool
才会起作用,因为它们具有少量构造函数,因此构造函数索引在运行时存储在指针的标记位中。但是任意类型的 thunkMark
或Bool
不能可靠地强制转换,并且该方法不能推广到具有超过 {4, 8} 个构造函数的类型(在相应的 {32, 64} 位系统上)。
此外,对象的代码生成器和运行时表示都会定期更改,因此即使现在可以工作(我不知道),将来也可能会中断。
我希望我们Coercible
在未来得到一个概括,可以容纳更多的强制,而不仅仅是newtype
-of- T
?T
,或者甚至更好,这允许我们为数据类型指定稳定的 ABI。据我所知,没有人在 Haskell 中积极开展这项工作,尽管在 Rust 中有一些类似的工作正在进行中以确保安全转化,所以也许有人会将其走私回功能区。
(说到ABI的,你可以使用FFI对于这一点,我已经在我已经写外来代码的情况下这样做的,知道的Storable
情况相匹配。alloca
适当大小的缓冲区,poke
类型的值Bool
到它,castPtr
在Ptr Bool
成Ptr Mark
,peek
的Mark
,就把它和unsafePerformIO
整个事情。)