私は典型的な使用例を示すことができる例を考え出そうとしました。関数型言語の例として階乗を使用するという長い伝統があるので、私はそれを壊さないことに決めました。
以下の2つのファイル(factorial.hとfactorial.c)は、整数の階乗の計算に役立つテーブルを使用しています。彼らは最初にテーブルを作成し、階乗で埋めます。次に、このテーブルを使用して階乗を要求します。その後、不要になったときに割り当てが解除されます。また、テーブルが初期化されて解放されたときを知ることができるように、メッセージをstdoutに出力します。
factorial.h:
/* A table of factorials. table[i] is the factorial of i. The
* max field is calculated so that its factorial would not be an
* integer overflow.
*/
typedef struct {
unsigned max;
unsigned *table;
} factorial_table;
int factorial_table_init(factorial_table *t);
int factorial_get(factorial_table *t, int n);
void factorial_table_free(factorial_table *t);
factorial.c:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <factorial.h>
/* Calculates max and allocate table. Returns !0 if
* memory could not be allocated.
*/
int factorial_table_init(factorial_table *t)
{
unsigned i, factorial;
t->max = factorial = 1;
while (INT_MAX / factorial > t->max + 1)
factorial *= ++t->max;
t->table = malloc((t->max + 1)*sizeof(unsigned));
if (!t->table) return !0;
t->table[0] = 1;
for (i = 1; i <= t->max; i++)
t->table[i] = i * t->table[i-1];
fprintf(stdout,"A factorial table was just allocated.\n");
return 0;
}
/* Uses a table to get the factorial of an integer number n. Returns
* (-1) if n is negative and (-2) if n is too big.
*/
int factorial_get(factorial_table *t, int n)
{
if (n < 0) return (-1);
if (n > t->max) return (-2);
return t->table[n];
}
/* Frees the table we used. */
void factorial_table_free(factorial_table *t)
{
free(t->table);
fprintf(stdout,"A factorial table was just freed.\n");
}
さて、Haskellコード。
{-# LANGUAGE CPP, ForeignFunctionInterface, EmptyDataDecls #-}
#include <factorial.h>
#let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__)
module Factorial (factorial) where
import Control.Monad
import Foreign.Ptr
import Foreign.ForeignPtr
import Foreign.C
import Foreign.Storable
import System.IO.Unsafe
import Foreign.Marshal
data Factorial_table
instance Storable Factorial_table where
sizeOf _ = #{size factorial_table}
alignment _ = #{alignment factorial_table}
peek _ = error "Cant peek"
foreign import ccall factorial_table_init :: Ptr Factorial_table -> IO CInt
foreign import ccall factorial_get :: Ptr Factorial_table -> CInt -> IO CInt
foreign import ccall "&factorial_table_free" funptr_factorial_table_free
:: FunPtr (Ptr Factorial_table -> IO ())
factorialIO :: IO (CInt -> IO CInt)
factorialIO = do
tableFgnPtr <- mallocForeignPtr :: IO (ForeignPtr Factorial_table)
withForeignPtr tableFgnPtr $ \ptr -> do
status <- factorial_table_init ptr
when (status /= 0) $ fail "No memory for factorial table"
addForeignPtrFinalizer funptr_factorial_table_free tableFgnPtr
let factorialFunction n = do
r <- withForeignPtr tableFgnPtr $ \ptr -> factorial_get ptr n
when (r == (-1)) $ fail
"Factorial was requested for a negative number"
when (r == (-2)) $ fail
"Factorial was requested for a number that is too big"
return r
return factorialFunction
factorial :: CInt -> CInt
factorial = unsafePerformIO . unsafePerformIO factorialIO
まず、Factorial_tableインスタンスがどのように保存可能であるかに注意してください。また、すべての関数バインディングがIOを返すことにも注意してください。
関連するすべてのコードはfactorialIOにあります。最初にポインタをmallocします(ここで、Storableのサイズと配置情報が使用されます。その呼び出しのタイプを記述しましたが、これは必須ではありません)。次に、ファイナライザーを追加します。ファイナライザーは、ポインターメモリが解放される直前に実行されます。そのポインタを整数の関数(factorialFunction)内にカプセル化し、常にwithForeignPtrを使用して、それを返します。
関数には重要な副作用がないことがわかっているので、最後の2行は、作成したものを純粋関数にします。それをテストしてみましょう:
ghci
Prelude> :m + Factorial
Prelude Factorial> factorial 5
A factorial table was just allocated.
120
Prelude Factorial> factorial 10
3628800
Prelude Factorial> factorial 13
*** Exception: user error (Factorial was requested for a number that is too big)
Prelude Factorial> factorial 12
479001600
Prelude Factorial> :q
Leaving GHCi.
A factorial table was just freed.
これがお役に立てば幸いです。もちろん、それは階乗を計算する完全に人工的な方法ですが、それは神が階乗を作成したものです。