オフィスアワーがそろそろ始まるよ!()

関数ポインタ

関数はメモリ上に配置される命令データであるため、アドレスを取得して、ポインタから利用することができます。

次の例では、add関数のポインタをfunc変数に格納して、func(1, 2)により間接的にadd関数を呼び出しています。

examples/ch02-primitives/src/function_pointers.zen:4:12

fn add(a: u32, b: u32) u32 {
    return a + b;
}

test "use a function pointer" {
    const func = add;
    ok(func(1, 2) == 3);
    ok(@typeOf(func) == fn(u32, u32) u32);
}

関数ポインタ型

関数ポインタの型は、fn(パラメータ型, ... ) 戻り値型です。関数のパラメータ数や型、戻り値型の組み合わせを、関数シグネチャと呼びます。関数シグネチャが同一の関数は、同じ関数ポインタ型に格納できます。

examples/ch02-primitives/src/function_pointers.zen:4:24

fn add(a: u32, b: u32) u32 {
    return a + b;
}

fn sub(a: u32, b: u32) u32 {
    return a - b;
}

test "function pointer type" {
    var func = add;
    ok(func(1, 2) == 3);

    func = sub;
    ok(func(2, 1) == 1);
}

関数シグネチャが異なると、同じ関数ポインタ変数に格納することができません。

fn signedAdd(a: i32, b: i32) i32 {
    return a + b;
}

test "different function pointer type" {
    var func = add;
    func = signedAdd;
}

上のコードは、次のコンパイルエラーになります。

error[E02046]: expected 'fn(u32, u32) u32', found 'fn(i32, i32) i32'
    func = signedAdd;
           ~

関数ポインタの初期化

コンパイラが推論可能な場合、関数ポインタ変数の型は省略可能です。

examples/ch02-primitives/src/function_pointers.zen:26:30

test "initialize function pointer" {
    // 下の2行は同じです
    const explicited: fn(u32, u32) u32 = add;
    const inferred = add;
}

関数ポインタが何の関数も参照していないことを示すには、通常のポインタ型同様、オプション型を使用します。最初にnullで初期化する場合、コンパイラに格納するポインタ型を教えてあげる必要があります。

examples/ch02-primitives/src/function_pointers.zen:32:41

test "null function pointer" {
    var func: ?fn(u32, u32) u32 = null;
    ok(func == null);

    func = add;
    if (func) | f | {
        ok(@typeOf(f) == fn(u32, u32) u32);
        ok(f(1, 2) == 3);
    }
}

関数ポインタの用途

関数の引数として

関数ポインタを関数の引数として渡すことができます。

examples/ch02-primitives/src/function_pointers.zen:43:50

fn calculate(a: u32, b: u32, func: fn(u32, u32) u32) u32 {
    return func(a, b);
}

test "function pointer as an argument" {
    ok(calculate(1, 2, add) == 3);
    ok(calculate(2, 1, sub) == 1);
}

上の例では、calculate関数の第3引数funcは関数ポインタです。関数の引数として関数ポインタを渡すことで、関数呼び出し時に処理内容を変更したり、コールバック関数として利用することができます。

コンパイル時の関数選択として

コンパイル時計算可能な値により、呼び出す関数を切り替えることができます。例えば、1つのコードで、プログラムを実行するOSごとに異なるメッセージを出力する場合には、次のようにします。

examples/ch02-primitives/src/function_pointers.zen:52:79

fn printLinux() void {
    std.debug.warn("I'm running on Linux!\n");
}

fn printWindows() void {
    std.debug.warn("I'm running on Windows!\n");
}

fn printMacosx() void {
    std.debug.warn("I'm running on MacOSX!\n");
}

fn printDefault() void {
    std.debug.warn("I'm running on Unknown OS!\n");
}

const builtin = @import("builtin");
const printOS = switch(builtin.os) {
    .linux => printLinux,
    .windows => printWindows,
    .macosx => printMacosx,
    else => printDefault,
};

test "compile time function pointer selection" {
    ok(@typeOf(printOS) == fn() void);
    printOS();
}

関数ポインタ変数printOSは、コンパイル時の実行環境情報に合わせて、printLinux, printWindows, printMacosx, printDefaultのいずれかの関数ポインタが格納されます。利用側は、関数ポインタが参照している関数を意識せずに、printOSを使用するだけです。

例えば、Linuxでプログラムを実行した場合、次のメッセージが出力されます。

I'm running on Linux!

関数の戻り値として

関数の戻り値として、関数ポインタを返すことができます。

examples/ch02-primitives/src/function_pointers.zen:81:88

fn getAddFunctionPointer() fn(u32, u32) u32 {
    return add;
}

test "function pointer as a return value" {
    var func = getAddFunctionPointer();
    ok(func(1, 2) == 3);
}

構造体のフィールドとして

構造体に関数ポインタのフィールドを定義することができます。

examples/ch02-primitives/src/function_pointers.zen:90:100

const Calculator = struct {
    calculate: fn(u32, u32) u32,
};

test "function pointer as a struct field" {
    const adder = Calculator{ .calculate = add };
    const subtractor = Calculator{ .calculate = sub };

    ok(adder.calculate(1, 2) == 3);
    ok(subtractor.calculate(2, 1) == 1);
}

関数ポインタ型のエイリアス

関数ポインタの型は長くなりがちですし、型名から意図を読み取ることが困難です。そこで、エイリアスを作成することで、型の可読性を向上することができます。

examples/ch02-primitives/src/function_pointers.zen:102:109

const BinOpU32 = fn(u32, u32) u32;

test "type name alias" {
    const func: BinOpU32 = add;
    ok(add(1, 2) == 3);
    ok(@typeOf(add) == BinOpU32);
    ok(@typeOf(add) == fn(u32, u32) u32);
}

この例では、BinOpU32fn(u32, u32) u32のエイリアスで、両者は全く同じものとして扱うことが可能です。

☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2019 connectFree Corporation. All rights reserved.
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.