F#の構文基本
準備も完了したので、さっそくF#を使ってみましょう。基本的な構文から紹介します。
letバインド(束縛)
let
はF#において、最も重要なキーワードの1つです。使い方は難しくないと思いますが、例を用いて紹介します。
letと識別子のバインド(束縛)
let は識別子に値を割り当てるもしくは、関連付けます。これをバインド(束縛)と言います。F#の束縛は、C#における「変数の宣言」とは若干異なります。F#における束縛で、いったん定義された識別子は変化することがありません。
let 識別子またはパターン[: 型] = 式(expression) 本文式(body-expression)
では、実際にインタープリタを起動して、下記のコマンドを一つずつ実行してみましょう。
> let a = 1 ;;
▼
val a : int = 1 //aにint型の1がバインドされたことをインタープリターが表示
> let c = 'a' ;;
▼
val c : char = 'a'
式を識別子から離して改行して記述する場合には、インデントを使用する必要があります。改行された本文式(body-expression)は、let式のトップと同じ位置にインデントして記述を開始する必要があります(この設定は変更可能ですが、詳細は次回の連載にて紹介予定です)。本文式(body-expression)とは、その識別子が使用される式のことです。
> let testVal = //通常の式:expression(半角スペース4つ分のインデント) let x, y = (1, 2) //識別子xとyが使用される式(body-expression)はletと同じ位置にインデントします。 3*x + 5*y ;;
▼
val testVal : int = 13
letと関数のバインド(束縛)
letキーワードを用いると、C#の場合と同様のプリミティブ型やオブジェクト型だけでなく、関数型の識別子を定義することもできます。
let 識別子 パラメーターリスト [: 戻り値の型] = 式(expression) 本文式(body-expression)
リスト6は、「testaddint は、int型の値をパラメーターとして渡し、さらに、もう1つのint型の値をパラメーターとして渡すと、int型の戻り値を返す関数である」という定義です。[testaddint 2 5 ;;]は、実際にこのtestaddint
関数に 「2」と「5」の2つの引数を渡していて、結果int型の「7」が返されました。
この例では、識別子の持つ型が明示的に定義されていないにも関わらず、続く式の右辺から型が推定されています。これが型推論と呼ばれる機能で、コンパイラがコードを解析し推定した型を返します。ちなみに、型注釈なしの場合は"+"演算子はデフォルトとしてintを使用します。
// testaddint関数の定義 > let testaddint x y = x + y ;;
▼
val testaddint : int -> int -> int
// testaddint関数に引数を与え呼び出し > testaddint 2 5 ;;
▼
val it : int = 7
下記の関数testaddstrの定義では、1つ目のパラメーターに型をあらかじめ指定します。左側の値として、string型の値を受け付けたときの"+"演算子は、string型の値のみ右側の値として受け付けるため、yもstring型である必要があります。
string型のxがパラメーターとして渡され、さらにstring型のyが渡され、string型の値が戻り値として返される関数staddstrを定義します。1つめのパラメーター"2"はstring型として扱われます。結果、2つめのパラメーター(演算子"+"の右側の値)もstring型である必要があります。
// testaddstr関数の定義 > let testaddstr (x : string) y = x + y ;;
▼
val testaddstr : string -> string -> string
// testaddstr関数に引数を与え呼び出し > testaddstr "2" "5" ;;
▼
val it : string = "25"
ラムダ式
C# 3.0ユーザーにはおなじみかもしれませんが、F#においても必要に応じて作成される名前のない関数としてラムダ式(匿名関数)が存在します。ラムダ式にはfunというキーワードを使用します。
fun パラメーターリスト -> 式
以下は、パラメータiを受け取り、i * i の実行結果を返す匿名の関数(ラムダ式)の例です。List.map は後続のリスト内の各要素に対して関数を適用します。つまり、リスト[1;2;3]の要素を1つずつラムダ式に渡し、i * i を実行し結果をtestlistというリストに入れます。最終的にtestlistには [1;4;9]という要素が入ります。
> let testlist = List.map (fun i -> i * i) [1;2;3] ;;
▼
val testlist : int list = [1; 4; 9]
F#固有のデータ型
F#には、他.NET言語で使用できる従来のデータ型のシームレスなサポートに加え、新たにF#固有の型も追加されています。
Unit(ユニット)
厳密に言うと、F#において関数は必ず値を返します。ただし、返されるのが通常の値ではなく、unit(ユニット)という特定の型である場合もあります。ユニット型の唯一の値は"()"です。他の値が存在しない、もしくは必要ないときにはプレースホルダーとして使用されます。とりあえず、C#のvoidのようなものと覚えておけばOKです。以下は、unit" ()"を用いた引数を受け付けず、値を戻さない関数の例です。
> let a () = () ;;
▼
val a : unit -> unit
組(Tuple:タプル)
配列やリストは同じ型の複数の値をまとめる機能ですが、これに対して「組」は、型が異なる2つ以上の関連付けられた複数の値をまるで一つの値のように扱う機能です。値はコンマで区切られます。カッコをつけるかどうかは任意ですが、つけた方が見やすいでしょう。
(要素1, ……, 要素n)
下記の例は、(11111, true, “Hello world!”)という3つの異なる型の要素からなる1つの組をtupletestに束縛しています。
> let tupletest = (11111, true, "Hello world!") ;;
▼
val tupletest : int * bool * string = (11111, true, "Hello world!")
まとめ
今回はC#との比較というよりもF#の基本的な構文を中心に紹介しましたが、次回は再帰関数や、高階関数の例を用いながら、「関数型言語としてのF#」をテーマにC#とF#の機能を比較し、解説する予定です。