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