« [JavaScript] スコープの基礎 | トップページ | 多様化の時代、捉え方によって見え方も違うのだと »

2007/08/05

F# を始めよう

F# なる言語があることを知る。ちょいと探りを入れてみれば、開発中の報が 2003/05/29 の /.J にあがっている。いまさらなネタであることが露見したが、まぁそれもよし。それにしても情報が少ないが、流行ってないのかね。

F# - Microsoft Resaerch

ライセンス

見ての通り、F# は MS によって開発されており、バイナリも無償で提供されている。ただし、製品ではなく、リサーチ プロジェクトという位置付けの、まだ試験段階の言語である。ライセンスについては、Microsoft Research Shared Source License Agreement (MSR-SSLA) なるものをベースにしており、リンク先のトップページにはこうある。

The F# License is a Microsoft Research Shared Source License Agreement. F# may be used to build commercial applications as covered by that license, in particular see the exemptions (i) and (ii) in the main body of the license. When downloading software from this site, you will be presented with a dialog box explaining the terms of use. You must agree to these terms as a condition of downloading the software.

F# - Microsoft Resaerch

拙い英語力で訳すとこう。

F# のライセンスは、Microsoft Research Shared Source License Agreement です。F# を市販向けアプリケーションのビルドに使用した場合、そのアプリケーションは、本ライセンスに準拠する必要があります。特に、ライセンス本文の免責事項 (i) と (ii) に注意してください。このサイトからソフトをダウンロードするときに、使用許諾ダイアログが表示されるので、それに同意してください。

まぁだいたいそんな感じらしい。そんな奇特な人がいるかどうかはさておいて、商用開発に使用する場合は注意が必要だ、てことかな。研究プロジェクトだし、当然といえば当然のライセンス形態というべきか。

関数指向でいこう

F# は OCaml を下敷きに作られた言語である。OCaml をご存じない方のために説明すると、OCaml とは、Objective Caml の略称であり、ML 系の方言である Caml にオブジェクト指向の要素を取り入れた関数型プログラミング言語である。.NET において、F# は最初の関数型プログラミング言語なのだ。

関数型プログラミング(FP: Functional Programming)のアプローチが取りざたされる昨今、C# の進化にもその潮流が顕著に見られる中、同じフレームワーク上でその真髄を体感させてくれるのだから、これは .NET プログラマにとって福音以外のなにものでもない。

よい知らせも知らされなければ意味がない。しかし、知ったならいますぐ始めよう。いまから始めても決して遅くはないのだから。なにせ、バージョン 1.9.1.18 が 2007/07/01 にリリースされたばかりの活きた言語だ。ググってもほとんど情報がないマイナー言語と笑うことなかれ。まだアーリーアドプターとして参戦できる余地があるってことなんだから、そうしない手はないはずだ。

インストール

まぁ、下手くそなアジテーションはこれぐらいにして、早速インストールして使ってみよう。先に挙げたリンク先から [F# Downloads] - [Download F# 1.9.1.18] とたどれば、現行最新版のダウンロード ページだ。MSI 形式のインストーラをダウンロードしたら、それを実行して、Next ポチポチ Install しばらく待ったらはい Finish。[スタート] メニューの [プログラム] に、[Microsoft Research F# 1.9.1.18] の項目が追加されているはずだ。

[Microsoft Research F# 1.9.1.18] の中身を覗く前に、ちょっと説明をはさむ。F# はコンパイル言語であり、かつ、インタプリタ言語でもある(この点で、IronPython に似ている)。つまり、コンパイルして EXE(.NET アセンブリ)にするもよし、ソースコードままのスクリプト ファイルをインタプリタで実行するもよし、また、トップレベルと呼ばれるインタプリタ インタフェースでシェル ライクに使うもよしなのである。

そして、[Microsoft Research F# 1.9.1.18] の中身だ。[F# Interactive (Console)] は bin/fsi.exe への引数なしのショートカットで、他のはすべてドキュメントへのショートカットだ。bin/fsi.exe はインタプリタであり、引数なしで起動すれば対話的なコンソールを起動し、スクリプト ファイルを指定して実行すればそれを逐次実行する。

肝心要のコンパイラはというと、bin/fsc.exe がそれだ。コマンドラインから叩いてもいいが、おそらく、ここまで熱心に読んでくれたあなたは Visual Studio 2005 ユーザではないだろうか。もしそうなら、F# のインストール時に F# for Visual Studio が一緒にセットアップされて、VS 上に F# の開発環境が整っているはずだ。つまるところ、F# プロジェクトを開いて、インテリセンスにあやかりながらコードを書いて、[F5] をポンでビルド実行ができるということだ。

[追記 at 2008/04/25]
Standard Edition 以上の VS をお持ちでないという方は、Microsoft Visual Studio 2008 Shell (integrated mode) という選択肢がある。詳細はこちらから。
[/追記]

テキスト エディタの設定

ということで、ここからは VS の話題に移る。さっそく VS を立ち上げてみよう。まず、コードを書き始める前にやっておきたいのが、テキスト エディタのタブ(インデント)の設定だ。[ツール] - [オプション] から [オプション] ダイアログを開き、左側のツリーから [テキスト エディタ] - [F#] を選択。[タブ] グループにおいて、[インデント サイズ] を 2 にし、[空白の挿入] を選択する。

なぜそうするかといえば、ライブラリ(lib/)のコードがそうなっているからで、F# や OCaml における標準的な書き方というか、コーディング標準がどうなってるかは知らないが、それに合わせ打っておけばまず間違いはないだろうからだ。

HHHHHello World!

さてお待ちかね。VS から F# で Hello World! と行きましょうか。と、その前に。とはいったものの、単に Hello World! したってなんのおもしろみもない。ということで、少しひねって、文頭の H を指定された回数分連ねて、それに続けて Hello World! の文字列を返す scratch_hello 関数を作ってみることとする。scratch_hello 4 なら、HHHHHello World! を返すって具合だ。パターンマッチと再帰を使って、ちょっとくらいは FP 気分に浸ってみよう。

まずは、[新しいプロジェクト] ダイアログから、[プロジェクトの種類] を [その他のプロジェクトの種類] - [F#] と選択し、[テンプレート] は [F# プロジェクト] を選択する。[プロジェクト名] を「HelloFSharp」に設定し、[OK] をクリックすれば新しいプロジェクトの作成完了だ。

次に、エントリ ポイントを書くための program.fs と、scratch_hello 関数を書くための junklib.fs を、プロジェクトに追加する。そう、初手からモジュール分割でいこう。

.fs ファイルの追加手順は、プロジェクトの右クリックから [追加] - [新しい項目] を選択して、[新しい項目の追加] ダイアログで、[カテゴリ] から [F# Source Files] を選択、そして [テンプレート] の [F# Source File] を選択し、[ファイル名] を指定して [追加] する。追加した .fs ファイルにはテンプレート コードが書かれているが、単なる書き方サンプルでしかないのでざっくり削除して OK だ。

それではコード。まずは junklib.fs の内容。

module Igeta.HelloFSharp.JunkLib

(* 文頭の H を指定された回数分連ねて Hello World! *)
let rec scratch_hello n =
  match n with
  | 0 -> "Hello World!\n"
  | _ -> "H" ^ scratch_hello (n - 1);;

そして、次に、program.fs の内容。

module Igeta.HelloFSharp.Program
open Igeta.HelloFSharp.JunkLib

(* ここから実行させる *)
let main () =
  print_string (scratch_hello 4);
  print_string "Press enter key to end program.\n";
  ignore (read_line ());;

main ();;

コードの解説は後回しにして、ひとまず [F5] をポチっとやって実行してみよう。もし、ミス タイプなくコードを打ち込んでいるにもかかわらずエラーが出力されたなら、この記事どおりにきっちり手順を進めてくれた証拠である。

コンパイル順序

プロジェクト エクスプローラを見てほしい。いま、HelloFSharp プロジェクトの下には、program.fs、junklib.fs の順にファイルが並んでいるはずだ。

F# for Visual Studio では、新しく追加されたファイルはプロジェクトの一番下に追加される。つまり、上から下へ、追加された順番に並んでいるわけだ。対して、コンパイルの順序はというと、これも上から下へと順々にコンパイルされていく。

上記の状態では、コンパイラは、program.fs、junklib.fs の順にコンパイルすることになる(憶測脚注: リンカの順序といった方が正しいのか?)。つまり、使う側であるエントリ ポイントのコードが先に、使われる側であるライブラリ コードが後にコンパイルされてしまい、使う側からライブラリが参照できずにエラーになっている、というわけだ。

では、どうやって並び順、コンパイル順序を変えてやるかというと、ファイル名を変更することで可能だ。ファイル名を変更すると、変更したファイルはリストの最後尾に配置されるからだ。よって、後にコンパイルしたい program.fs を、いったん別の適当な名前に [名前の変更] をして、また元に戻す、というやりかたでできる。

なんだそのスマートじゃないやりかたは、と思われた方は、プロジェクト ファイル(.fsharpp)をハックしてくれても構わない。Files の要素を、以下の順番に直せば OK だ。

"Files"
{
    "junklib.fs"
    {
    "ProjRelPath" = "T"
    }
    "program.fs"
    {
    "ProjRelPath" = "T"
    }
}

文法ひとまわり

それではコードの解説、というか、ちょっとした文法解説。ただ、すべてをていねいに説明するには、別エントリに書き出せるくらいのボリュームが必要、というか、初学者である僕には土台無理な話なので、ここでは感覚がつかめる程度の説明とさせていただく。

(* *)

コメントは (* *) で囲って書く。OCaml 譲りのブロック コメントであるが、これがおもしろいことに、他の多くの言語ではできない、コメントのネストが可能である。さらに、F# では、C# と同じように、// によるライン コメント、/// によるドキュメント コメントもサポートされている。

; と ;;

論理行の終わりを示すもの、ではない。単一のセミコロンは、「式1; 式2」と書くことで、式1の値を無視し、式2の値を返すようにするものである。F#(OCaml)では、値を返さない関数を書くとき、その戻り値に unit 型である () を返す習慣になっているが、セミコロンは、この () を返す関数を使用するときに使う。つまり、式1に () を返す関数を書き、式2に実際の値を返す関数を書く、という使い方をする。

そして、どちらかというと、ダブルセミコロンのほうが C ライクな言語に見るセミコロンの役割に近しい。では、ダブルセミコロンはなにかというと、シェルやコマンドプロンプトを想像してくれると分かりやすいと思うのだが、それにおけるエンターキー、とりあえずいったんこういうコマンドを発行してくださいねー、というようなことを、トップレベルに対して行うための終端記号(terminator、ターミネータ)である。

トップレベルに置かれる、関数やモジュールやクラスなどの定義、また、実行する処理の一群などの末尾を示すもの。それがダブルセミコロンだ。

let

let 式は、変数と関数を定義するためのキーワードだ。

let 変数名 = 式1 [in 式2]
let 関数名 引数1 [引数2]... = 式1 [in 式2]

定義からわかるように、右辺には必ず式(式1)がくる。つまり、let は、シンボル(変数名 / 関数名)とその値(式1)を束縛するキーワードだ。逆にいえば、なんらかの値に対して、それを端的に表す名前を付ける。そのために使うのが let である。

ちなみに、in を使うと、in に続く式(式2)でのみ使用可能な、局所的な変数 / 関数を定義できる。よくある場面としては、関数内で変数を定義する場合などであり、Perl でいうところの my に相当するイメージ。ちょっと不思議な言い回しをすると、let の中で let を書けるということである。

税率をローカル変数として保持し、税込みの価格を算出する tax_on 関数を例に取れば、以下のようになる。

(* 税込み価格 *)
let tax_on p =
  let rate = 1.05 in
  int_of_float (float_of_int p * rate);;

tax_on 1000;; (* 1050 *)

ところで、レガシーな VB において、参照型は Set で、値型は Let で代入する、ということをご存知だろうか。ここから類推されるのは、変数も関数も let で束縛できるのならば、どちらも値型ではないかということであり、そしてその類推は正しい。関数が値であるという事実は、関数型言語の最大の特徴のひとつである。ただし、変数と関数は、「値」として同じもののように扱えるということであり、まったく等価なものではないので注意。

また、C ライクな手続き型の言語であれば、関数定義の後には、{ } (中カッコ)で括られたコードブロック、すなわち文(複文)がくる。手続き型の言語において、関数は、文をひとまとめにしたものでしかないからだ。しかし、関数型の言語における関数は、数学におけるそれと同じように、引数に応じて計算し値を返す式でなければならない。処理すべき順に並べられた命令列でなく、基本的には、ひと続きの大きな式でなければならないのである。

rec

rec は、recursive | recursion の rec であり、その関数が再帰呼び出し可能であることを表すキーワードだ。「let rec 関数名 ~」として定義することで、再帰関数を定義する。F# (OCaml)では、あらかじめ、再帰呼び出し可能であることを、関数定義時に宣言しておかなければならないのである。

rec は、再帰のために使われるのであり、名が体を表しているが、実際的、実装的観点からいえば、次のようになっている。通常、let によるシンボルの定義では、定義するシンボルと同じ名前のシンボルがその式内で使われた場合、それは別のものとして扱われるのだが、rec を指定することによって、同じものとして扱うことができる。言い方を変えるなら、関数内において、自分自身への参照を使えるようにするものであり、また別の言い方をするなら、関数名のスコープを、その式の中にまで持ち込むようにするもの、ということもできる。

match ~ with

下記の定義では、わかりやすさのため改行を入れてあるが、もちろんワンラインで書いてもよい。

match 式0 with
  [|] パターン1 -> 式1
  [| パターン2 -> 式2]
  ...

match 式は、パターン マッチと呼ばれる。処理を分岐するという意味ではいわゆる if 文に近いが、値を返す式であるということを考慮すれば、三項演算子(条件演算子)により近く、また、シンタックス的には switch 文っぽい。で結局、match 式はどんなものかというと、式0 と合致するパターンを上から順々に探して、合致したら右辺の式を評価してその値を返すものである。

パターン マッチは、リスト処理で最も良く使われるが、パターンにシンボルを入れることによって、let で行われるような変数束縛が可能である。この点、整数値リストの要素の合計を算出する sum 関数を用いて説明したい。いま、整数型の要素を持つリストを 1 つ引数に取る、再帰呼び出し可能な関数、sum を定義して、1~9 の整数を要素とするリストを指定して実行したところである。

(* 整数値リストの合計 *)
let rec sum l =
  match l with
  | [] -> 0
  | hd::tl -> hd + sum tl;;

sum [1;2;3;4;5;6;7;8;9];; (* 45 *)

パターンにある [] は空リストを表す。空リストが引数に指定された場合は、0 を返すようにしている。そして、重要なのは hd::tl というパターンである。これは、別に x::xs などとしても構わない。:: は、リストの先頭に要素を 1 つ追加する特殊な中置演算子であり、前後に書かれているのは変数名である。通常、式の中に現れるときは「追加する要素 :: 元のリスト」と書くことで、新しいリストを返すのだが、パターンにかかれることによって逆向きに作用し、マッチするリストを「先頭要素 :: 先頭要素を除いたリスト」に分解して、それぞれの変数に束縛をする。

つまり、「sum [1;2;3;4;5;6;7;8;9]」を評価するときであれば、hd には 1 が、tl には [2;3;4;5;6;7;8;9] が束縛される、といった具合だ。そして、結局のところ、再帰呼び出しを経て、「sum [1;2;3;4;5;6;7;8;9]」は「1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0」となり、45 を返すこととなる。ずいぶん端折ったが、パターン マッチと再帰については、すぐ次で、もうすこし詳しく説明するのであしからず。

ちなみに、パターンの追加条件としてなら、「| パターン when 式 -> ~」とすることで bool 型を返す式を条件に組み込むことができる。というか、F# (OCaml)には if 式がある。bool 型の式で分岐したければ、素直に if 式を使っていい。

評価の流れ

細かい文法の話はこれぐらいにして、実際の流れを追ってみよう。ずいぶん間が空いてしまったので、プログラム コードを再掲する。

(* junklib.fs *)
module Igeta.HelloFSharp.JunkLib

(* 文頭の H を指定された回数分連ねて Hello World! *)
let rec scratch_hello n =
  match n with
  | 0 -> "Hello World!\n"
  | _ -> "H" ^ scratch_hello (n - 1);;
(* program.fs *)
module Igeta.HelloFSharp.Program
open Igeta.HelloFSharp.JunkLib

(* ここから実行させる *)
let main () =
  print_string (scratch_hello 4);
  print_string "Press enter key to end program.\n";
  ignore (read_line ());;

main ();;

rec 付きで定義さていることからわかるように、scratch_hello 関数は再帰関数である。再帰は慣れないとわかりづらいが、FP では多用されるプログラミング スタイルだ。引数に 4 が与えられたときの scratch_hello の再帰の過程をリストにしてみると、以下のようになる。

  • scratch_hello 4
  • "H" ^ scratch_hello 3
  • "H" ^ "H" ^ scratch_hello 2
  • "H" ^ "H" ^ "H" ^ scratch_hello 1
  • "H" ^ "H" ^ "H" ^ "H" ^ scratch_hello 0
  • "H" ^ "H" ^ "H" ^ "H" ^ "Hello World!\n"

「scratch_hello 4」が評価されるところから見ていこう。scratch_hello 関数上のパターン マッチにおいて、パターンは 0 と _ があり、引数の n が 0 かそれ以外かで分岐するようになっている。いま、n = 4 なのだから、_ にマッチし「"H" ^ scratch_hello (n - 1)」が評価される。次いで、n - 1 を評価すると「"H" ^ scratch_hello (4 - 1)」、つまり「"H" ^ scratch_hello 3」となる。

ここで、scratch_hello が再帰的に呼ばれて、今度は n = 3 で scratch_hello を評価する。引数 n は 0 でないから、再び _ にマッチして「"H" ^ "H" ^ scratch_hello (3 - 1)」となり、「"H" ^ "H" ^ scratch_hello 2」となる。そして、同じように、次は n = 2 で、次は n = 1 で、と評価されていく。

そして、最終的に scratch_hello は n = 0 で呼び出され、そのとき初めて 0 とパターン マッチする。「scratch_hello 0」は "Hello World!\n" を返し、ここで再帰が終了する。すると、いま、式は展開されて「"H" ^ "H" ^ "H" ^ "H" ^ "Hello World!\n"」となった。あとは、これを評価してやるだけだ。^ (アクサン シルコンフレクス / ハット記号)は、文字列連結を行う中置演算子(ここでは、文字列連結に OCaml ライクな ^ を使ったが、C# ライクに + を使ってもよい。というか、むしろそっちの方が F# っぽいかもしれない)だから、結果 "HHHHHello World!\n" となる。

よって、晴れて scratch_hello 4 は "HHHHHello World!\n" を返すのである。

さて、scratch_hello 関数を中心に話を進めたが、main 関数についてすこし補足しておく。main 関数とはいっても、これは僕が勝手にそう命名しただけであり、C 言語などに見るように、main と名付けられた関数がエントリ ポイントになる、という規則があるわけではない。F# では、スクリプト言語的に、トップレベルに書かれたコードが、書かれた順番に評価(実行)されるだけである。

そして、最後にもうひとつ、ignore 関数について。この ignore 関数は、なんらかの型の引数をひとつだけ取り、unit 型を返す組み込み関数である。つまるところ、式の戻り値の unit 化である。main 関数は、特になにか値を返すものではないので、ignore 関数を用いて、便宜上の値である () を返すようにしている。いわゆる、void 型を返す関数としているわけだ。

おわりに

初心者ながらに F# をご紹介してみました。まだまだ、説明したりないことだらけだし、しかも scratch_hello 関数にはバグがあったりするのだけれど、FP のニュアンスや F# のにおいをすこしでも感じてもらえればなあ、とか思います。

それでは、今回ばかりはこの言葉で締めくくりたい。

Enjoy!

参考文献

改訂履歴

  • 2007/11/04: F# は「.NET 上に移植された OCaml といえる」との表記を削除した。誤解を招く不適切な表現だと判断したため。
  • 2008/02/04: 一箇所、scratch_hello は末尾再帰であるという記述があったが、これは誤り。「末尾再帰」を「再帰」に修正した。長らく誤情報を放置しており、申し訳ない。
  • 2008/02/04: もろもろ細かい部分の修正、追記。
  • 2008/02/07: 手続き型と関数型における関数の違いについて述べた部分で、用語の使い方がめちゃくちゃだったのを修正。
  • 2008/02/14: ;; (ダブルセミコロン)は「区切り文字」と表現していたが、「終端記号」に変更する。その方が適切と思われる。
  • 2008/02/14: 「:: は、リスト連結の中置演算子」との記述は誤りであり、「:: は、リストの先頭に要素を 1 つ追加する特殊な中置演算子」に変更する。
  • 2008/02/14: 改訂を行うに当たって参考にした「Objective Caml 入門」を参考文献に追加。

|

« [JavaScript] スコープの基礎 | トップページ | 多様化の時代、捉え方によって見え方も違うのだと »

コメント

コメントを書く



(ウェブ上には掲載しません)




トラックバック

この記事のトラックバックURL:
http://app.f.cocolog-nifty.com/t/trackback/253814/7299638

この記事へのトラックバック一覧です: F# を始めよう:

» [News]Microsoftの新プログラミング言語「F#」,Visual Studioへの搭載目指す [おぎわら@.NET道場 Blog(わんくま編)]
[News]Microsoftの新プログラミング言語「F#」,Visual Studioへの搭載目指す [続きを読む]

受信: 2007/10/28 23:51

« [JavaScript] スコープの基礎 | トップページ | 多様化の時代、捉え方によって見え方も違うのだと »