PR

Cの値を確実に表現するためのデータ型

 前の例ではc_sinの呼び出しに,Preludeの数値型であるDoubleを使いました。しかし,環境によっては,Cコンパイラの数値型とPreludeの数値型の範囲や精度が異なることがあります。その場合,Preludeの数値型を使って関数を呼び出すのは不適当です。CコンパイラとHaskell処理系のどちらの扱う範囲が広くても,お互いの数値のやり取りの中で桁あふれなどによる情報の切り落としが発生する可能性があります。同様の理由で,Intの代わりに任意倍長整数を扱えるIntegerを使用してCの関数を呼び出すことも,バグの原因になり得ます。

 このためFFIは,Cの型と対応する値を確実に表現できる型をForeign.C.Typesモジュールで提供しています。

Prelude Foreign.C.Types> :browse
newtype CChar = Foreign.C.Types.CChar GHC.Int.Int8
newtype CClock = Foreign.C.Types.CClock GHC.Int.Int32
newtype CDouble = Foreign.C.Types.CDouble Double
data CFile = Foreign.C.Types.CFile
newtype CFloat = Foreign.C.Types.CFloat Float
data CFpos = Foreign.C.Types.CFpos
newtype CInt = Foreign.C.Types.CInt GHC.Int.Int32
newtype CIntMax = Foreign.C.Types.CIntMax GHC.Int.Int64
newtype CIntPtr = Foreign.C.Types.CIntPtr GHC.Int.Int32
data CJmpBuf = Foreign.C.Types.CJmpBuf
newtype CLDouble = Foreign.C.Types.CLDouble Double
newtype CLLong = Foreign.C.Types.CLLong GHC.Int.Int64
newtype CLong = Foreign.C.Types.CLong GHC.Int.Int32
newtype CPtrdiff = Foreign.C.Types.CPtrdiff GHC.Int.Int32
newtype CSChar = Foreign.C.Types.CSChar GHC.Int.Int8
newtype CShort = Foreign.C.Types.CShort GHC.Int.Int16
newtype CSigAtomic = Foreign.C.Types.CSigAtomic GHC.Int.Int32
newtype CSize = Foreign.C.Types.CSize GHC.Word.Word32
newtype CTime = Foreign.C.Types.CTime GHC.Int.Int32
newtype CUChar = Foreign.C.Types.CUChar GHC.Word.Word8
newtype CUInt = Foreign.C.Types.CUInt GHC.Word.Word32
newtype CUIntMax = Foreign.C.Types.CUIntMax GHC.Word.Word64
newtype CUIntPtr = Foreign.C.Types.CUIntPtr GHC.Word.Word32
newtype CULLong = Foreign.C.Types.CULLong GHC.Word.Word64
newtype CULong = Foreign.C.Types.CULong GHC.Word.Word32
newtype CUShort = Foreign.C.Types.CUShort GHC.Word.Word16
newtype CWchar = Foreign.C.Types.CWchar GHC.Word.Word16

 Cの次に付く接頭辞のUはunsigned,Lはlongの省略形です。例えば,CULLongはCの「unsigned long long」に対応します。また,「FILE」のような大文字だけの型名や「sig_atomic_t」のようなスネークケースの型名を,Haskellの一般的な命名規則に合うよう「CFile」や「CSigAtomic」といったキャメルケースに置き換えています。このような命名規則を覚えておけば,これらの型がCのどの型に対応するか考えるのはそう難しくないでしょう(参考リンク)。

 また,数値型の範囲を明示的に指定したint8_tやuint8_tなどに対応する,符号付き整数Int*や符号無し整数Word*という型も存在します(参考リンク1参考リンク2)。Int*はData.Intモジュール,Word*はData.Wordモジュールで定義されています。

Prelude Data.Int> :browse
data Int = GHC.Base.I# GHC.Prim.Int#
data Int16 = GHC.Int.I16# GHC.Prim.Int#
data Int32 = GHC.Int.I32# GHC.Prim.Int#
data Int64 = GHC.Int.I64# GHC.Prim.Int64#
data Int8 = GHC.Int.I8# GHC.Prim.Int#
Prelude Data.Int> :m Data.Word
Prelude Data.Word> :browse
data Word = GHC.Word.W# GHC.Prim.Word#
data Word16 = GHC.Word.W16# GHC.Prim.Word#
data Word32 = GHC.Word.W32# GHC.Prim.Word#
data Word64 = GHC.Word.W64# GHC.Prim.Word64#
data Word8 = GHC.Word.W8# GHC.Prim.Word#

 Preludeの数値型ではなくこれらのモジュールで定義されている型を利用することで,範囲や精度などの数値をどう表現するかという問題を回避できます。

{-# INCLUDE <math.h> #-}
{-# LANGUAGE ForeignFunctionInterface #-}
module SimpleFFIExample where
import Foreign.C.Types

foreign import ccall "sin" c_sin :: CDouble -> CDouble

*SimpleFFIExample> c_sin 40.0
0.7451131604793488

 新しい型を導入したことで,既存の型を使ったコードとの相互運用性が気になるかもしれません。ですが,その心配は要りません。Preludeで用意されている関数を使って型を変換できるからです。整数間での型変換にはfromIntegral,浮動小数点間の型変換にはrealToFracを利用できます(正確にはこれらの変換関数の役割は少し違いますが)。

Prelude> :t fromIntegral
fromIntegral :: (Num b, Integral a) => a -> b
Prelude> :t realToFrac
realToFrac :: (Fractional b, Real a) => a -> b
Prelude> :m Data.Int Data.Word
Prelude Data.Int Data.Word> :i Integral
class (Real a, Enum a) => Integral a where
~ 略 ~
instance Integral Word64 -- Defined in GHC.Word
instance Integral Word -- Defined in GHC.Word
instance Integral Word8 -- Defined in GHC.Word
instance Integral Word16 -- Defined in GHC.Word
instance Integral Word32 -- Defined in GHC.Word
instance Integral Int64 -- Defined in GHC.Int
instance Integral Int8 -- Defined in GHC.Int
instance Integral Int16 -- Defined in GHC.Int
instance Integral Int32 -- Defined in GHC.Int
instance Integral Integer -- Defined in GHC.Real
instance Integral Int -- Defined in GHC.Real
Prelude Data.Int Data.Word> :i Real
class (Num a, Ord a) => Real a where toRational :: a -> Rational
~ 略 ~
instance Real Double -- Defined in GHC.Float
instance Real Float -- Defined in GHC.Float
Prelude Foreign.C.Types> :i Fractional
class (Num a) => Fractional a where
  (/) :: a -> a -> a
  recip :: a -> a
  fromRational :: Rational -> a
        -- Defined in GHC.Real
instance Fractional CFloat -- Defined in Foreign.C.Types
instance Fractional CDouble -- Defined in Foreign.C.Types
instance Fractional CLDouble -- Defined in Foreign.C.Types
instance Fractional Double -- Defined in GHC.Float
instance Fractional Float -- Defined in GHC.Float

{-# INCLUDE <math.h> #-}
{-# LANGUAGE ForeignFunctionInterface #-}
module SimpleFFIExample where
import Foreign.C.Types

sin' :: Double -> Double
sin' d = realToFrac (c_sin (realToFrac d))

foreign import ccall "sin" c_sin :: CDouble -> CDouble

*SimpleFFIExample> sin' 40.0
0.7451131604793488

 変換する型間で数値の範囲や精度が異なる場合,情報の切り落としが発生する可能性があります。その点に十分注意したうえで型を変換してください。

 なお,GHCでは上記の型に加えて,第7回で触れた「マシン表現そのものである非ボックス化型」を使って外部関数を呼び出すこともできます。

{-# INCLUDE <math.h> #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE UnliftedFFITypes #-}
{-# OPTIONS_GHC -fglasgow-exts #-}
module SimpleFFIExample where
import Foreign.C.Types
import GHC.Exts

sin' :: Double -> Double
sin' d = realToFrac (c_sin (realToFrac d))

foreign import ccall "sin" c_sin :: CDouble -> CDouble
foreign import ccall "sin" c_sin' :: Double# -> Double#

*SimpleFFIExample> :set -fglasgow-exts
*SimpleFFIExample> case 40.0 of D# x -> D# (c_sin' x)
0.7451131604793488