我试图提出一个可以显示典型用例的示例。由于使用阶乘作为函数式语言示例的传统由来已久,因此我决定不打破它。
下面的这两个文件(factorial.h 和 factorial.c)使用表格来帮助计算整数的阶乘。他们首先建立并用阶乘填充一个表;那么这个表用来求阶乘;然后在不再需要时将其释放。我们还将消息打印到标准输出,以便能够知道我们的表何时初始化和释放。
阶乘.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);
阶乘.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,然后返回它。
因为我们知道我们的函数没有重要的副作用,所以最后两行只是把我们刚刚创建的东西变成了一个纯函数。让我们测试一下:
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.
我希望这很有用。当然,这是一种完全人为的计算阶乘的方法,但这就是上帝创造阶乘的目的。