特集:C#開発者のためのF#入門(後編)

F#言語の基礎文法

bleis-tift
2012/05/10
2012/05/11 更新
Page1 Page2

 前回は、F#の概要や、関数型プログラミングの基礎、F#でよく使われるデータ構造のリストとタプルを説明した。今回後編では、F#でプログラムを書くに当たって必要最低限の文法を紹介していく。

主要な文法

if式

 F#で条件による分岐を行うためには、if式を使用する。

if <条件> then <条件がtrueの場合の式> else <条件がfalseの場合の式>
F#のif式

 字面上はほかの言語のif文と同じように見えるが、F#の「if」はほかの言語での条件演算子(例えばC#の「<条件> ? <trueの場合> : <falseの場合>」という式)に近い。F#の「if」は文ではなく式なので、値を持つ。そのため、「then」に続く式と「else」に続く式は同じ型を持つ必要がある。

 以下のコードは、「then」に続く式の型と、「else」に続く式の型が異なるため、エラーとなる。

if true then "hoge" else 42
型が異なるためにエラーとなるif式

 比較演算子は「==」ではなく「=」、「!=」ではなく「<>」を使用し、否定には「!」の代わりに「not」を使用する。次のコードは、比較演算子「=」を使った例だ。

if x = 42 then "hoge" else "piyo"
比較演算子「=」を使ったif式のコード例

 また、「else」は基本的に省略できない(省略できるケースについては後述)。

letキーワード

 letキーワードはすでに説明したが、もう少し詳しく見ていこう。

// 変数定義
let x = 10

// 関数定義
let f x = x + 10

// 関数内関数
let g x =
  let g' x = x * x
  g' (f x)

// 再帰関数の定義
let rec fact x =
  if x = 0 then 1
           else x * fact (x - 1)

// 引数なしの関数の定義
let hello () =
  System.Console.WriteLine("Hello!")
letキーワードを用いたコード例

 関数「g」の中で関数「f」を呼び出しているが、この際、関数「f」は関数「g」よりも前に定義されている必要がある。つまり、関数「g」の後に関数「f」を書くとエラーとなる。これはC#やVB(Visual Basic)を使っている人がはまりやすいポイントなので、注意しておこう。

 関数「fact」のように再帰関数を定義するためには、recキーワードを使って関数を定義する必要がある。この「rec」がないと、まだ定義していない関数「fact」を参照することになり、エラーとなる。以下のプログラムが何を表示するか考えてみてほしい。

let fact x = x;;  // ;;を忘れずに入力すること
let fact x =
  if x = 0 then 1
           else x * fact (x - 1)
System.Console.WriteLine(fact 5);;
このプログラムを実行すると、何が表示されるか?

 実際に実行して、自分の考えがあっていたかどうかを確認しておこう。

 実行すると、5の階乗(5 * 4 * 3 * 2 * 1)である「120」ではなく、「20」が表示されたはずだ。これは、2番目の関数「fact」の定義の中で呼び出されている関数「fact」が、自分自身ではなく、以前に定義した関数「fact」(=引数をそのまま返す関数)だったからだ。

 このように、F#では定義の順番は非常に重要な意味を持つ。

 引数なしの関数も見ておこう。

 例えば次のコードのように引数を付けないと、「hello」は関数にならない。

let hello = System.Console.WriteLine("Hello!")
関数とはならないコード例

 そのため、引数なしの関数は引数の位置に丸カッコの対(=「()」)を記述する。この「()」は、意味がないことを表す値であり、unitという型を持つ。unit型はC#でのvoid型に近いが、void型が値を持たないのに対して、unit型は「()」という値を持つという違いがある(unit型は値を持つため、実は引数がないわけではなく、「hello」は意味のない値「()」を受け取る関数である)。

 また、戻り値がないメソッドでは、C#ではvoid型を戻り値の型として指定するが、F#ではvoid型の代わりにunit型の戻り値が返される。例えば、今まで使ってきたSystem.Console.WriteLineメソッドは、F#から使うとunit型の戻り値を返すように見える。

 unit型は「()」という唯一の値を持つため、F#の関数は(例外で終了しない限り)戻り値を持つようになっている。意味はないが、以下のようなコードも記述できる。

let x = System.Console.WriteLine("hello!");;
C#から見ると戻り値のないメソッドだが、F#から見るとunit型の戻り値を返すメソッドとなっていることを確認するコード例

 これにより、F#では戻り値のある関数もない関数も、統一的に扱うことが可能だ。

 if式の説明で、「『else』は基本的に省略できない」と書いたが、「then」に続く式がunit型の場合のみ、「else」を省略することができる。次のコードはその例である。

// 以下のコードは
if x = 42 then System.Console.WriteLine("hoge") else ()
// elseを省略できる
if x = 42 then System.Console.WriteLine("hoge")
if式で「else」を省略できる例

レコード

 F#でユーザー定義の型を作る場合、クラスではなく主にレコード判別共用体を使用する。まずはレコードを見てみよう。

 レコードは、

type <型名> = { <フィールド名>: <型名>; ... }
レコードの定義形式

という形式で定義する。

 レコードはフィールドに名前の付いたタプルと考えることができる。

 例えばタプルの説明で使った「名前と年齢」をレコードとして定義すると、以下のようになる。

type Person = { Name: string; Age: int }
「名前と年齢」をレコードとして定義するコード例

 レコードに対する各種の処理を以下にまとめる。

// レコードの定義
type Person = { Name: string; Age: int }

// レコードのインスタンスを生成
let p = { Name = "Alice"; Age = 24 }

// フィールド「Name」の値を取得して標準出力に出力
System.Console.WriteLine(p.Name)
// フィールド「Age」の値を取得して標準出力に出力
System.Console.WriteLine(p.Age)

// 変数「p」をコピーして別のインスタンスを生成(フィールド「Age」の値は「24」のまま)
let p2 = { p with Name = "Bob" }

// 変数「p」を標準出力に出力
printfn "%A" p
レコードに対する各種の処理

 今まで画面出力にはSystem.Console.WriteLineメソッドを使ってきた。しかし、レコードなどのユーザー定義型は、「%A」という書式指定を使うことで簡単に文字列化できるため、以降は(同様に画面出力できる)printfn関数を使用する。

 さて、先ほどのコード例では、レコードのインスタンスの生成に型名が明記されていなかったが、例えばさらに以下のような型もあった場合はどうだろう(つまり、同じ型の組み合わせのレコードがある場合はどうなるのだろうか)。

type Dog = { Name: string; Age: int }
先ほどのレコード「Person」と全く同じ型のフィールド群を持つレコードの定義

 この場合、F#では「レコードのインスタンスを生成する直前で定義された型」のインスタンスが生成される。つまり、次のコード例のようになる。

type Person = { Name: string; Age: int }
// これはPerson型
let p = { Name = "Alice"; Age = 24 }

type Dog = { Name: string; Age: int }
// これはDog型
let d = { Name = "Max"; Age = 5 }
全く同じ型のフィールド群を持つレコードがある場合のインスタンス生成の挙動

 Dog型を定義した後でPerson型のインスタンスを生成したい場合は、次のように、フィールドに型名を修飾することで可能だ。

let p = { Person.Name = "bbb"; Age = 20 }
フィールドに型名を修飾することでレコードの型を明示する例

 ジェネリック型のレコードを作りたい場合は、型変数(この例では「'a」や「'b」)を型名(この例では「Pair」)の後ろの山カッコ(< >)の中に記述する。

type Pair<'a, 'b> = { Key: 'a; Value: 'b }
ジェネリック型のレコードの定義例

 型変数名の先頭にシングルクォート(')が必要な点のみ注意してほしい。

 続いて次のページでは、判別共用体を説明する。


 INDEX
  特集:C#開発者のためのF#入門(前編)
  F#で初めての関数型プログラミング
    1.F#とは
    2.関数型プログラミングの基礎
    3.リストとタプル
 
  特集:C#開発者のためのF#入門(後編)
  F#言語の基礎文法
  1.主要な文法: if式/letキーワード/レコード
    2.主要な文法: 判別共用体/パターン・マッチ

更新履歴
【2012/05/11】

 以下のような誤り(=記事中の関数factが階乗にならない誤り)がありました。お詫びして訂正させていただきます。

「letキーワードを用いたコード例」における「else fact (x - 1)」というコードの一部
else x * fact (x - 1)

「このプログラムを実行すると、何が表示されるか?」というコードの一部
else x * fact (x - 1)

実行すると、5の階乗(5 * 4 * 3 * 2 * 1)である「120」ではなく、「4」が表示されたはずだ。
実行すると、5の階乗(5 * 4 * 3 * 2 * 1)である「120」ではなく、「20」が表示されたはずだ。




Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間