是否可以在自定义类型和标准库类型之间建立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是一个错误”,这就是您所得到的。任意不同的数据类型之间没有实例,即使它们看起来相似。


这似乎是令人沮丧的限制,但请考虑这一点:如果CoercibleBooland之间有一个实例Mark,是什么阻止它强制执行NoughttoTrueCrossto False?可能BoolMark在内存中以相同的方式表示,但不能保证它们在语义上足够相似以保证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才会起作用,因为它们具有少量构造函数,因此构造函数索引在运行时存储在指针的标记位中。但是任意类型的 thunkMarkBool不能可靠地强制转换,并且该方法不能推广到具有超过 {4, 8} 个构造函数的类型(在相应的 {32, 64} 位系统上)。

此外,对象的代码生成器和运行时表示都会定期更改,因此即使现在可以工作(我不知道),将来也可能会中断。

我希望我们Coercible在未来得到一个概括,可以容纳更多的强制,而不仅仅是newtype-of- TT,或者甚至更好,这允许我们为数据类型指定稳定的 ABI。据我所知,没有人在 Haskell 中积极开展这项工作,尽管在 Rust 中有一些类似的工作正在进行中以确保安全转化,所以也许有人会将其走私回功能区。

(说到ABI的,你可以使用FFI对于这一点,我已经在我已经写外来代码的情况下这样做的,知道的Storable情况相匹配。alloca适当大小的缓冲区,poke类型的值Bool到它,castPtrPtr BoolPtr MarkpeekMark,就把它和unsafePerformIO整个事情。)


以上是是否可以在自定义类型和标准库类型之间建立Coercible实例?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>