F#での引数なしの関数の扱いについて

2017/05/16

なんとなく、F#での引数なしの関数の書き方がよくわからなくなってきたから、整理してみた。

1.単なる関数の場合

以下のプログラムを考える。

module  FTest
open System


// printfの戻り値であるUnit値を持つ定数になる
let func1 =     // unit
    printf "func1\n"


// unit値を引数にとる関数値になる
let func2 () =  // unit -> unit
    printf "func2\n"


[<EntryPoint>]
let main args =
    printf "main start\n"

    func1       // 意味なし
    func1       // 意味なし
    func2 ()    // ここで実行される
    func2 ()    // ここで実行される

    printf "main end\n"
    0


実行結果は以下となる。



上記では、func1自体は関数ではなくunit値の定数になっている。関数本体の実行はmainが呼ばれる前に行われてしまっている。

func2はunit値を引数に取る関数値である。C言語に例えるのなら関数ポインタの値であり、俺が思うところの引数なしの関数として振る舞ってくれる。

だから、func2については「func2 ()」を記述したところで、その関数の中身である「printf "func2\n"」が実行される。一方、func1はmain関数の中では実行されていない。

2.クラスのメソッドの場合
混乱するのが以下の場合だ。

module  FTest
open System


type CTest() =
    member this.func1 =
        printf "CTest.func1\n"


    member this.func2 () =
        printf "CTest.func2\n"


[<EntryPoint>]
let main args =
    printf "main start\n"
    let c = new CTest()

    c.func1       // ここで実行される
    c.func1       // ここで実行される
    c.func2 ()    // ここで実行される
    c.func2 ()    // ここで実行される

    printf "main end\n"
    0


これを実行すると下記となる。



func1の動きがさっきと違う。さっきと似た様なもんだろうと思うのだが、今度はmain関数の中で実行されている。何なのか?

まぁ、よくわからんが、たぶん自己識別子であるthisが暗黙の引数として扱われている、ということなのではないだろうか。だから「c.func1」が記述されたところで、初めて実行されると。

ということで、次にこうしてみた。

3.スタティックなメソッドの場合

module  FTest
open System


type CTest() =
    static member func1 =
        printf "CTest.func1\n"

    static member func2 () =
        printf "CTest.func2\n"


[<EntryPoint>]
let main args =
    printf "main start\n"
    let c = new CTest()

    CTest.func1       // ここで実行される
    CTest.func1       // ここで実行される
    CTest.func2 ()    // ここで実行される
    CTest.func2 ()    // ここで実行される

    printf "main end\n"
    0


実行するとこうなった。



予想外な結果である。俺としては、CTest.func1は、タイミングは別としても1回だけしか実行されないのではないかと考えたのだが、そうでもないらしい。

4.let束縛にする

なんかむかついたから、こうしてみた。

module  FTest
open System


type CTest() =
    let func1 = printf "CTest.func1\n"


    static member func2 () =
        printf "CTest.func2\n"


    member this.func3 = func1

[<EntryPoint>]
let main args =
    printf "main start\n"
    let c = new CTest()
    printf "new CTest() owari\n"

    CTest.func2 ()  // ここで実行される
    CTest.func2 ()  // ここで実行される
    c.func3         // 意味なし
    c.func3         // 意味なし

    printf "main end\n"
    0


実行結果はこうなる。



で、ここまでやって、ようやく気がついた。

2番目や3番目のfunc1のように、クラスのメソッドにした場合はなぜにメソッドが呼ばれるまで実行されないのか? それは極単純に「member……」と書くか「let……」と書くかの違いに応じて、扱いが変わっているのではないか? わからんけど。

だから、4番目の例では、newしたタイミングでlet束縛の「printf "CTest.func1\n"」が実行されて、「c.func3」は無視されていると思われる。

5.まとめ

引数なしの「member……」と「let……」の扱いが相違するのはなんとなく直観に反する気もするけど、とりあえず、引数なしの関数にはダミーの引数であるunit値を指定させたほうがいいらしい。

少なくともそうすれば、定数のつもりなのか、引数なしの関数のつもりなのかが明らかになるし、関数が実行されるタイミングを間違えることもなくなるだろう。

0 Comments:

コメントを投稿

Links to this post:

リンクを作成

<< Home