F#における関数とレコード

次に、関数を定義する例を見てみよう。記述するコードは次のようになる。

let hello name = "Hello," + name;;
printfn "%A" (hello "F#");;

関数を定義する場合も、letというキーワードを使用する。構文としては「let 関数名 引数1 引数2… = 式;;」というものになる。

また、returnというキーワードは使用せず、式の値がそのまま戻り値となる。このコードをC#で書くとすれば、次のようになるだろう(C#ではトップレベルはクラスでなければならないため、完全に同じ意味にはならないが、イメージはつかんでいただけると思う)。

string hello(string name)
{
    return "Hello," + name;
}

void Run()
{
    Console.WriteLine(hello("F#"));
}

では次に、レコードを扱う方法を見てみよう。

レコードというのは「複数の変数をグループ化したもの」であり、C言語でいうところの構造体のようなものである。レコードを使用するには、typeというキーワードを使用する。

たとえば、string型のNameという値と、int型のAgeという値を持つPersonという名前のレコードを定義する場合は、次のようなコードになる。

type Person = {Name:string;Age:int};;

そして、レコードからオブジェクトを生成したり、生成したオブジェクトのプロパティにアクセスする場合は、次のようなコードになる。

let p = {Name="INRIA";Age=30};;
printfn "%A" p.Name;;

ここまで見てきた感じでは、書式が少し異なるだけで、C#やVBとは大きな違いはないように思われる。

非常に強力な型推論

それでは、「Personレコードを引数に取り、その中身を表示する関数Show」を定義して、呼び出してみよう。この例を通して、F#の面白さの一端をかいま見ることができるだろう。

先ほどのPersonレコードを定義するコードを含めて、関数Showを定義し、呼び出す場合のコードは次のようになる。

type Person = {Name:string;Age:int};;
let Show p = printfn "Name:%A Age:%A" p.Name p.Age;;
Show {Name="INRIA";Age=20};;

このコードを実行してみると、問題なく結果が表示される。

さて、ここで面白いことが起きているが、皆さんはお気づきだろうか。Showメソッドを定義する段階では、引数pの型は一切明示していないのだ。ただ、「p.Name」や「p.Age」といった処理の内容を見ることで「引数pはNameとAgeというプロパティを持っている何かである」ということは分かるだけである。

F#のコンパイラは、こういった情報から、変数pはPersonレコードのオブジェクトである、ということを「推論」してくれているのだ。これがF#の持つ型推論の仕組みである。

少し見た感じだと、動的型付けに見えるかもしれない。そこで、コードの末尾に次の2行を追加してコンパイルしてみよう。

type Person2 = {Name:string;Phone:string};;
Show {Name="INRIA";Phone="03-xxxx-xxxx"};;

すると、「The type Person does not contain a field Phone.」というエラーでコンパイルが失敗する。

「The type Person does not contain a field Phone.」というエラーが発生

実行時エラーではなく、コンパイル段階でエラーが出ているということに注目していただきたい。このエラーメッセージを見れば、F#のコンパイラが「Show関数の引数pがPerson型である」と正しく認識していることが分かるだろう。C#でも、3.0からvarというキーワードを使うことで、簡単な型推論ができるようにはなってはいるが、F#の型推論はそれよりも遙かに強力なのである。