unsafeCoerce

Unsafe.Coerce に定義されている unsafeCoerce の使い道

export されていないデータを操作する

例えば ghc パッケージの DynFlags に定義されている extensions を考える

DynFlags.hs
data DynFlags = DynFlags {
  ...
  , extensions :: [OnOff LangExt.Extension],
  ...
  }

OnOff というデータ構造は export されていないため、通常であれば触ることができない。つまり extensionFlags などの用意されたものを使うしかない。

こういう場合に unsafeCoerce を使って同型のデータ型に強制的に変換できる。

単純なサンプル

module A (onOffList) where

data OnOff
  = On
  | Off
  deriving (Eq, Show)

onOffList :: [OnOff]
onOffList = [On, Off]

ここでモジュールBで OnOff 型から Bool 型に変換する関数 toBool を定義したいとしましょう。当然 B.hs からは OnOff に対してパターンマッチなどはできません。

toBool :: OnOff -> Bool
toBool On  = True
toBool Off = False

そのため、まずはモジュールBで全く同じ OnOff 型を定義します。

B.hs
module B (toBool) where

import A (onOffList)

data OnOff
  = On
  | Off
  deriving (Eq, Show)
  
toBool :: OnOff -> Bool
toBool On  = True
toBool Off = False

これでコンパイルは通りますが、以下のように onOffList に適用しようとすると失敗してしまいます。

ghci
*B> map toBool onOffList 

<interactive>:13:12: error:
     Couldn't match type A.OnOff
                     with OnOff
      NB: OnOff is defined at B.hs:(6,1)-(9,21)
          A.OnOff is defined at A.hs:(3,1)-(6,21)
      Expected type: [OnOff]
        Actual type: [A.OnOff]
     In the second argument of map, namely onOffList
      In the expression: map toBool onOffList
      In an equation for it’: it = map toBool onOffList

ここで unsafeCoerce を使います。

B.hs
module B (toBool, onOffList2BoolList) where

import A (onOffList)
import Unsafe.Coerce (unsafeCoerce)

data OnOff
  = On
  | Off
  deriving (Eq, Show)

onOffList2BoolList :: [OnOff] -> [Bool]
onOffList2BoolList = map toBool . unsafeCoerce

toBool :: OnOff -> Bool
toBool On  = True
toBool Off = False

実行してみましょう。

ghci
*B> onOffList2BoolList onOffList

<interactive>:23:20: error:
     Couldn't match type A.OnOff
                     with OnOff
      NB: OnOff is defined at B.hs:(6,1)-(9,21)
          A.OnOff is defined at A.hs:(3,1)-(6,21)
      Expected type: [OnOff]
        Actual type: [A.OnOff]
     In the first argument of onOffList2BoolList, namely onOffList
      In the expression: onOffList2BoolList onOffList
      In an equation for it’: it = onOffList2BoolList onOffList

エラーになりました。これは onOffList2BoolList :: [OnOff] -> [Bool] で指定している OnOff がモジュールBのものとして扱われているためです。型注釈をコメントアウトしてから実行してみると、ちゃんと動きます。

ghci
*B> onOffList2BoolList onOffList
[True,False]

*B> :t onOffList
onOffList :: [A.OnOff]

ここで不思議なのは、モジュールAは OnOff の型すら export していないため、明示的に onOffList2BoolList :: [A.OnOff] -> [Bool] とするとエラーになってしまいます。型推論に任せると全て上手くいきます。

注意点

unsafeCoerce は型安全を壊してしまうので、どうしても仕方がない場合以外は使わない方が良いでしょう。unsafeCoerce よりも安全な方法として true-name パッケージがあります。

参考リソース

Last updated