DT日記

家を離れた自宅警備員の日記

F#のletはかっこいいのだ

この記事はF# Advent Calendar 201120日め、のはずでした。クリスマスに間に合はせるはずが…あれ……?

いろいろねたは考へてたんですが、どれも綺麗な落ちがつかないので初心者らしいねたでお茶を濁すことにしました。

いままで漠然と考へてたことをがんばって整理してみたのですが、基本的には初心者が初心者向けに書いた感じのドヤ顔記事です><

letは何かを束縛する。スマートに!

let a = 10 ;;
let b = 20 ;;

printfn "%d" (a + b);;

F#のletは、他の言語(たとへば、C, Java, Ruby, Python...)の「変数宣言」や「代入」に相当するものだと説明されることがあり、実際に同じようにも使用されます。

F#などの函数プログラミング言語では、これを「束縛」と呼びます。

代入の場合は「a10を代入する」と言ひますが、束縛では「10aを束縛する」と言ひます。また、F#では束縛とは別に代入もあり、別物です。

let函数に束縛することもできます。

let f a b = a + b ;;

printfn "%d" (f 1 2);;

これはみんなが大好きなJavaScriptで書くと以下のコードとほぼ同じです。

var f = function(a, b){ return a + b; };

console.log(f(1,2));

「F#のletは変数も函数も定義することができる」と説明されることがありますが、これは正確ではないと思ひます。なぜなら、F#での函数は値でもあるからです。

let f a b = a + b ;;
let g = fun a -> fun b -> a + b ;;

printfn "%d" (f 1 2);;

このfgは、意味としては同じものです。また、fgに束縛されてゐる「値」のことを函数オブジェクトまたはラムダ式などと呼ぶみたいです。F#のfun式、つまり函数オブジェクトは、必ず一つの引数をとって、一つの値を返します。

これを統合して考へてみると…

letで二引数函数を定義できてたように見えてたのは、実は一引数函数二段重ねの糖衣構文に過ぎなかったんだよ! ΩΩΩ<ナ、ナンダッテー!?

引数を二つ以上とる函数定義が、実は二段重ねになった函数への束縛に過ぎない、といふことはfsi函数を定義してみて、その函数に引数を一つだけ渡してみると確認することができます。

letによる函数定義は函数オブジェクトへの束縛ですが、実はJavaScriptでのvar foo = function() { ... };形式の函数定義は、正確には函数定義ではなく函数オブジェクトを生成して代入してゐるので、F#の函数の束縛とよく似てゐます。


まとめると、

  • letは値を変数名に束縛する
  • F#の函数とは函数オブジェクトといふ「値」で、引数を一つとって一つ返す
  • let=の間に空白区切りで仮引数の変数名を書くと、引数に仮引数の変数名が束縛された函数が返される
  • letで複数の値をとるように見える函数は、一引数の函数入れ子の糖衣構文(syntax sugar)である

となります。

実はF#を知ったばかりのときはF#の変数とは引数を取らない函数のことではないかと思ってゐました。ひとつの見方としては綺麗に見えてアリなのですが、実際の動きを理窟で考へてみると、事実はどうでせう…?

蛇足ですがついでに、let函数への束縛に過ぎないといふことは、函数オブジェクトはそのままで値に適用することができます。

(fun x -> fun y -> x + y) 33 66

letはスコープを作る。エレガントに!

let f = 
    let a = 1
    let b = 2
    a + b

printfn "%d" f

ローカルな変数abを足し合はせる函数fを定義します。

これと見た目上(だいたい)等価なコードを、みんなも大好きなJavaScriptで書くとこんな感じになりますね。

var f = function(){
    var a = 1;
    var b = 2;
    return a + b;
};

console.log(f());

JavaScriptの方がちょっと文字数は多いですが、だいたい似たやうな雰囲気があります。

ところでF#はOCamlといふ言語の血を色濃く受け継いで居り、ソースコード#light "off"と書くことで軽量構文(lightweight syntax)を無効にし、ML互換モードである冗語構文(verbose syntax)のみで書くことができるようになります。では上のF#のコードを冗語構文で書き換へてみませう。

#light "off"
let f = 
    let a = 1 in 
        let b = 2 in
            a + b ;;

printf "%d\n" x

すると、今度はinといふコードがくっつきました。

これもやっぱり、みんな大好きJavaScriptで、意味的にだいたい等価にしてみると、こんなのになります。

var f = function(x){
    return function(){
        var a = 1;
        return function(){
            var b = 2;
            return function(){
                return a + b;
            }();
        }();
    }();
}

console.log(f());

このコードを見てキャー、クロージャカッコイー!なんて反応をしてくださるのは一部の方だけですね。長ったらしすぎてふつうに気持ち悪いです。自分で書いてて思ひましたが、気持ち悪いです*1

JavaScriptfunctionリテラルはスコープを保持してゐて、varはローカル変数の定義です。

これと上ふたつのF#(≒OCaml)のソースコードは簡潔で、言語としての洗練されっぷりの半端なさがわかります><

ちなみに、「平均的なOCamlプログラマ」は

let f = 
    let a = 1 in
    let b = 2 in
    a + b ;;

printf "%d\n" f

と、インデントを深くせずに書くらしい*2ので、F#の軽量構文のインデントとほとんど同様になるようです。

さいごに

F#の軽量構文は書きやすさを重視して設計されてゐますが、函数型プログラミングの学習には「省略」といふ形で概念のわかりにくさに繋がってゐる一面もあります。letの柔軟な糖衣構文はとてもエレガントでスマートなものですが、その「隠された」意味を探ってみることで函数型言語の理解への一助になるのではないでせうか><

この記事を書き終ってッターンしたあとで、Advent Calendarの27日めにF#初心者による in キーワドの考察:Gushwell's C# Dev Notesといふ記事があることに気がついて心臓が止まるかと思ったのですが、さらに進んだinキーワードの作用について紹介されてゐて、たいへん参考になります。

参考文献

//www.amazon.co.jp/gp/product/4774145165/ref=as_li_ss_tl?ie=UTF8&tag=wx310kflashca-22&linkCode=as2&camp=247&creative=7399&creativeASIN=4774145165">実践 F# 関数型プログラミング入門:F#の入門書で、とてもわかりやすくまとまってゐます。私もこれで学びました!
//d.hatena.ne.jp/hamatsu1974/20090616/1245142607">少年オッカムル - 序 - - 暗号、数学、時々プログラミング:さっき軽量構文について調べてるときに見つけました。まだほとんど読んでませんが、F#/OCamlの勉強にすごく良ささうです!

誰のために書いたの?

ドヤ顔して書いた割にはエキスパートには当たり前すぎる内容で、本当の初学者向けには情報が不足してゐます。初心者向け記述の不足は手間の省略と同時に意図したもので、F#に興味を持った初心者が複数の疑問点について自分で調べ進めて、納得する楽しみを味はってもらへると良いなじゃないかなあと思ふのです。

そんなちょっとした楽しみを覚えていただけた方は、せっかくなのでRuby vs Python! 〜def vs def〜もどうぞ。defletってちょっとだけ似てますけど、たまたまでした。

*1:その気持ち悪さが大好きです!

*2:出典はどこだっけ…