FFIの例:多相型をもつC関数

多くのC関数は多相的な関数である.これらを使いこなすことが,SML#でのCとのシームレスな連携の鍵と言える.

その例として,Cの標準ライブラリ関数のfread(3)を見てみよう.マニュアルには,以下のような型の関数と定義されている.

 #include <stdio.h>
 size_t  fread(void  *ptr, size_t size, size_t nmemb, FILE *stream);

この引数の型の意図は以下の通りである.

  • void *ptr 読み込むデータを格納する配列
  • size_t size 読み込みデータの型tのサイズ
  • size_t nmemb 最大要素数.
  • FILE *stream 入力ストリーム.

すなわち,この関数は,第2引数に読み込むデータのサイズを指定することによって,種々の型のデータを読み込むことができる'多相的な関数'である.したがって,MLのような型付き言語では,fread関数は,以下のような多相型をもつ関数として扱えるの望ましい.

fread : 'a array * int * FILE -> int

ここで,各引数の意味は以下の通りである.

  • 'a array 読み込むデータを格納する配列
  • int 最大要素数.
  • stdIn 入力ストリーム.

元々のC関数が要求していた読み込むデータのサイズは,型付き言語ではコンパイラが計算し自動挿入されるのが理想である.

SML#では,その多相型レコード演算のコンパイル理論を基礎とし,コンパイラが,すべての型をコンパイル時に計算し,必用に応じて,必用な関数に自動的にサイズを受け渡すコンパイル方式を実現している.そこで,この機構の一部を,FFIを使うプログラマに解放することにより,freadのような型依存の動作を行う多相関数を型付き関数として安全に使用することができる.

freadは以下のように宣言することによって使用できる.

 type c_file = unit ptr
 val libc = DynamicLink.dlopen "/lib/libc.so.6"
 val c_fread = DynamicLink.dlsym (libc, "fread")
 fun fread (dst, len, file) =
     _ffiapply c_fread (dst : 'a array, _sizeof('a), len : int, file : c_file)
                       : int

fread関数定義の本体の式は

_ffiapply e (arg1,...,argn) : t

の形をしたSML#の特殊構文である.引数argには以下の二種類の式を書くことが出来る.

 exp : t
 _sizeof(t)

前者はC関数に渡す式とその型の宣言であり,後者は,型主導コンパイラが管理する型tのサイズ情報の参照である.この構文は,SML#の型主導のコンパイルの結果のコードをユーザが書くことに相当する.

以上の宣言によりfread関数は以下の型の関数として定義される.

val fread = fn : ['a .'a Array.array * int * unit ptr  -> int]

これ以降,freadは多相関数として使用できる.当然,freadの呼び出しはCのfread関数の呼び出しとなり,引数で指定した配列に直接値が格納される.

freadに加え,fopen, fclose, fwrite等の関数を同様に定義すれば,SML#から効率よいバイナリIOを型安全に使用することができる.