Table of Contents
ここまでは、ずっとインタプリタ環境のもとでF#のコードを実行してきました。しかし、F#にはインタプリタで動作させる以外にも、コンパイルしてEXEやDLLを生成したり、スクリプトとして動作させたりすることができます。
この節では、それら3つの実行環境の利用方法について詳しく説明していきます。
F#インタプリタは、これまでに使ってきたように、ユーザからの入力を読み込み(read)、その入力を評価(evaluate)し、応答を画面に出力(print)という繰り返しを行うことで、対話的にプログラミングを行うことができる実行環境です。この3つのアクションの繰り返しによる対話環境は、一般的にREPループ(read-eval-print loop)と呼ばれ、F#以外のOCaml、Haskell、Ruby、Pythonなどでも存在します。
F#インタプリタの本体はfsi.exeという実行可能ファイルで、次小節で説明するF#コンパイラ本体と同じディレクトリ内に入っています。
この実行可能ファイルを直接起動すると、コンソール上で動作するF#インタプリタが起動しますが、Visual Studio上で動作するF#インタプリタも、内部的にはこのファイルを利用しています。
F#インタプリタには、いくつかの命令が用意されています。これらの命令は「#
」から始まる文字列で、ディレクティブ(directive)と呼ばれます。このディレクティブは、F#インタプリタから直接入力することができます。これらのディレクティブを使うと、F#インタプリタ環境そのものに対する指示を出すことができます。どのようなコマンドがあるかは、F#インタプリタ上で#help;;
と入力することで一覧表示することができます。ここでは、それらのコマンドをいくつか詳しく説明していきます。
F#インタプリタに対して、ファイルに保存されたF#のプログラムを一気に読み込ませたいときには、このディレクティブを使います。#load
ディレクティブにより読み込まれたプログラムは、読み込まれた直後に評価、すなわち実行されます。例を見てみましょう。
まず、files.fsというファイル(.fsはF#のソースコードの拡張子)に、以下のコードが記述されているとします。
let files = System.IO.Directory.GetFiles(@".") Array.iter (printfn "File=%s") files
これを#load
ディレクティブによって読み込むと、直後にそのコードが実行されます。
> #load "files.fs";;
[Loading C:\files.fs]
File=C:\.rnd
File=C:\autoexec.bat
File=C:\config.sys
File=C:\hiberfil.sys
File=C:\pagefile.sys
F#インタプリタの実行環境に、動的にアセンブリ参照を追加するときにはこのディレクティブを利用します。例として、F# PowerPackへの参照を追加してみましょう。F# PowerPackは、Microsoftによって提供されているF#の追加ライブラリで、以下の場所からダウンロードすることができます。
Microsoft F# PowerPack for .NET 4.0 Beta1
これをC:\Program
Files\FSharp-1.9.6.16\bin\PowerPackに展開した場合、以下のようにF#
PowerPackのアセンブリへの参照を追加することができます。ここでは、F#
PowerPack内に含まれる複素数を表現するComplex
クラスを利用してみます。
> let c1 = Math.Complex.Create(1.0,5.0);; // PowerPackへの参照が追加されていないのでComplexクラスを認識できない let c1 = Math.Complex.Create(1.0,5.0);; --------------^^^^^^^ stdin(1,14): error FS0039: The value, constructor, namespace or type 'Complex' is not defined. A construct with this name was found in FSharp.PowerPack.dll, which contains some modules and types that were implicitly referenced in some previous versions of F#. You may need to add an explicit reference to this DLL in order to compile this code. > #r @"C:\Program Files\FSharp-1.9.6.16\bin\PowerPack\FSharp.PowerPack.dll";; // 参照を追加 --> Referenced 'C:\Program Files\FSharp-1.9.6.16\bin\PowerPack\FSharp.PowerPack.dll' > let c1 = Math.Complex.Create(1.0,5.0);; // 複素数値1+5iを作成 val c1 : Math.Complex = 1r+5i > let c2 = Math.Complex.Create(2.0,3.0);; // 複素数値2+3iを作成 val c2 : Math.Complex = 2r+3i > c1 + c2;; // 複素数値同士の加算 val it : Math.Complex = 3r+8i {Conjugate = 3r-8i; ImaginaryPart = 8.0; Magnitude = 8.544003745; Phase = 1.212025657; RealPart = 3.0; i = 8.0; r = 3.0;}
アセンブリの検索対象パスを追加します。たとえば上の#rディレクティブの説明では、アセンブリの場所をフルパス名で指定していますが、#I
ディレクティブで参照パスを追加してから、#r
ファイル名だけを指定して参照することもできます。
> #I @"C:\Program Files\FSharp-1.9.6.16\bin\PowerPack";; // 検索対象パスを追加 --> Added 'C:\Program Files\FSharp-1.9.6.16\bin\PowerPack' to library include path > #r @"FSharp.PowerPack.dll";; // ファイル名のみを指定して参照を追加 --> Referenced 'C:\Program Files\FSharp-1.9.6.16\bin\PowerPack\FSharp.PowerPack.dll'
F#インタプリタのヘルプを表示します。
> #help;; F# Interactive directives: #r "file.dll";; reference (dynamically load) the given DLL. #I "path";; add the given search path for referenced DLLs. #load "file.fs" ...;; load the given file(s) as if compiled and referenced. #time ["on"|"off""];; toggle timing on/off. #help;; display help. #quit;; exit. F# Interactive command line options: See 'fsi --help' for options Visual Studio key bindings: Up/Down = cycle history CTRL-DOT = interrupt session ALT-ENTER = send selected source text to FSI session (adds ;;) Please send bug reports to fsbugs@microsoft.com
F#インタプリタの制御方法には、ここまでに紹介したディレクティブによる以外にも、F#インタプリタのインスタンスを直接制御する方法があります。実はF#インタプリタのインスタンスには、fsi
という名前がつけられており、ユーザが直接それにアクセスすることができます。このインスタンスは、Microsoft.FSharp.Compiler.Interactive.InteractiveSession
クラスのインスタンスで、いくつかのメソッドとプロパティが公開されています。ここでは、それぞれのメソッドやプロパティのはたらきについて紹介していきます。
まずfsiがもつメソッドでもっとも重要なのは、AddPrinter : ('a -> string)
->
unit
です。これは、F#インタプリタ上における型'a
の文字列表現を制御するためのメソッドです。たとえば、int
やstring
の値ならば、簡単に文字列として画面に出力することができますが、自分で作成した複雑なクラスの場合は、適切な文字列表現を自動的に画面に出力することは困難です。自分で作成したクラスならば、ToString
メソッドのオーバーライドにより、インスタンスの文字列表現をカスタマイズすることは可能ですが、元のクラス定義を書き換える必要があります。
そこで、fsi
のAddPrinter
メソッドを使うと、クラス定義を書き換えることなくF#インタプリタ上での文字列表現をカスタマイズできます。このメソッドの型を見ると、('a
->
string)
という関数値を受け取る高階関数であることがわかります。ここに、カスタマイズしたい型'a
の値を文字列に変換する関数(プリンタ)を渡すことで、F#インタプリタの出力をカスタマイズすることができます。例をみてみましょう。
> type Distance = class new : x:float * y:float -> Distance member X : float member Y : float end;; // Distanceクラスを定義 > let d = new Distance(3.0,4.0);; // Distanceクラスのインスタンスを生成 val d : Distance > d;; // 値を確認(未フォーマット) val it : Distance = FSI_0004+Distance {X = 3.0; Y = 4.0;} > fsi.AddPrinter(fun (t:Distance) -> System.String.Format("({0:f3},{1:f3})",t.X,t.Y));; // Distanceのプリンタをインストール val it : unit = () > d;; // 値を確認(プリンタによるフォーマット済み) val it : Distance = (3.000,4.000)
ここでは、Distance
クラスに対するプリンタをインストールしています。ここでインストールしたプリンタは、X
とY
の値をそれぞれ小数点第3位まで表示し、全体を()
で囲った文字列表現をします。プリンタは、以下のように簡単に別のものに切り替えることができます。
> let polar_printer (v:Distance) = // 極座標としてフォーマットする関数 let length = sqrt(v.X*v.X+v.Y*v.Y) let angle = atan(v.X/v.Y) // 角度の計算は手抜き System.String.Format("Length={0:f3}, Angle={1:f3}", length, angle);; val polar_printer : Distance -> string > fsi.AddPrinter(polar_printer);; val it : unit = () > d;; val it : Distance = Length=5.000, Angle=0.644
fsi
には、ほかにもいくつかのプロパティが存在します。
メンバ名 | 型 | 概要 |
FloatingPointFormat | string | 浮動小数点数の書式指定子の設定/取得をします。この書式指定文字列は、.NETの書式指定仕様に従います。 |
PrintWidth | int | 書式指定付き文字列出力時の表示幅を設定/取得します。 |
PrintDepth | int | ツリー構造データ出力時の階層の深さを設定/取得します。 |
PrintLength | int | リストなどの線形データ構造を出力する際の、最大の表示個数を設定/取得します。 |
ShowProperties | bool | インスタンスを表示する際に、そのプロパティも表示するかのフラグを設定/取得します。 |
CommandLineArgs | string[] | プログラムに渡される引数(fsiに対する引数は除く)のリストを取得します。 |
F#では、ソースコードをコンパイルして実行可能ファイルやライブラリを生成することができます。
.NET Frameworkには、コンパイル済みコードをまとめる単位としてプロセスアセンブリとライブラリアセンブリの2種類が存在します。簡単にいってしまうと、プロセスアセンブリはEXEファイルのことで、ライブラリアセンブリはDLLファイルのことです。
F#コンパイラは、Visual Studioの統合開発環境から利用できますが、当然コマンドラインから直接利用することもできます。ここでは、これらの利用方法を説明します。
Visual StudioからF#のプログラムをコンパイルするには、他の言語同様「プロジェクト」を作成する必要があります。Visual Studioで作成できるF#のプロジェクトの種類には、F# Library、F# Application、F# Tutorialの3種類が存在します。
F# Libraryはライブラリアセンブリを作成するプロジェクトで、F# Applicationはプロセスアセンブリを作成するプロジェクトです。F# Tutorialは、実際にはプロセスアセンブリを作成するプロジェクトですが、これは単にF#の機能を紹介するためのサンプルプログラムにすぎません。
では、実際にVisual StudioからF#のプロジェクトを作成する方法を説明します。Visual Studio 2010を立ち上げて、ファイル(F)-新規作成(N)-プロジェクト(P)を選択すると、以下の画面が現れます。
ここではF# Applicatioinを選び、プロジェクトにConsoleApplication1という名前をつけてOKを押してみましょう。すると、以下のようF#プロジェクトの画面に切り替わります。
画面の右側に見えるソリューションエクスプローラーの中に、作成したソリューションConsoleApplication1ができ、その下に同じ名前のF#プロジェクトが作成されました。さらにこのプロジェクトのしたには、Program.fsというF#のソースファイルが作成されています。またReferencesの下には、このプロジェクトが参照しているアセンブリの一覧が現れています。FSharp.Coreというアセンブリは、あまり見慣れない名前ですが、これはF#の標準ライブラリや内部的に使用するものを含んだアセンブリです。
では、実際にコードをコンパイルして実行してみましょう。左側のソースファイルに以下のようにコードを書き、メニューからビルド(B)-ソリューションのビルド(B)を選んでください。下側の出力のタブにビルドの結果が出力されます。
C:\Projects\ConsoleApplication1\ConsoleApplication1\bin\DebugにConsoleApplication1.exeというプロセスアセンブリが生成されました。これを実行するには、メニューからデバッグ(D)-デバッグなしで開始(H)を選んでください。以下のように、コマンドプロンプトが立ち上がり、その中でビルドしたプログラムが実行されます。
F#コンパイラの本体はfsc.exeという名前のファイルで、これをコマンドプロンプト上から実行することで、F#のコードをコンパイルすることができます。Visual Studioも内部的にはこのファイルを利用してコードをコンパイルします。実際にコマンドプロンプト上から直接コードをコンパイルするには、fsc.exe本体とF#関連のアセンブリへのパスを通しておく必要があります。
fsc.exeとF#関連のアセンブリは、F#の環境をCドライブにインストールした場合、デフォルトではC:\Program Files\Microsoft F#\v4.0(Visual Studio 2010を入れた場合)およびC:\Program Files\FSharp-1.9.6.16\bin(F#コンパイラを入れた場合)の中に入っています。
ここで注意なのですが、この2つの場所にあるF#関連ファイルはコンパイラとしてはバージョンは同じなのですが、それぞれターゲットとしている.NETのバージョンが異なります。Visual Studio 2010についてくるF#関連ファイルは.NET 4.0向けのものであり、F#コンパイラ単体についてくるのは.NET 2.0向けのものです。どちらのバージョン向けのものかを確認するには、コンパイラやインタプリタを実行したときに最初に表示されるバージョン情報の中に含まれています。
F#コンパイラのみをインストールした場合は、自分でそのインストール先のパスを環境変数PATHに追加する必要がありますが、Visual Studio 2010をインストールした場合は、スタートメニューからMicrosoft Visual Studio 2010→Visual Studio Tools→Visual Studio 2010 Command Promptを選べば、F#をコンパイルするための環境が準備されたコマンドプロンプトが起動します。
自分でパスを追加する場合は、コマンドプロンプト上から以下のように入力します(C:\Program Files\FSharp-1.9.6.16\binに本体が存在する場合)。
C:\>set PATH=%PATH%;C:\Program Files\FSharp-1.9.6.16\bin
では、例として以下のコードが書かれたソースファイルをコンパイルしてみましょう。エディタなどで以下の1行のコードを書き、hello.fsという名前で保存してください。ここでは、C:\Project\FSharpというディレクトリに保存したとして話を進めます。
printfn "Hello, world!"
コンパイルする方法は単純で、F#コンパイラfscに対して引数としてソースファイル名を渡すだけです。実際に例を見てみましょう。
C:\Project\FSharp>fsc hello.fs Microsoft F# Compiler, (c) Microsoft Corporation, All Rights Reserved F# Version 1.9.6.16, compiling for .NET Framework Version v4.0.20506 C:\Project\FSharp>dir ドライブ C のボリューム ラベルがありません。 ボリューム シリアル番号は B41A-XXXX です C:\Project\FSharp のディレクトリ 2009/06/14 18:03 <DIR> . 2009/06/14 18:03 <DIR> .. 2009/06/14 18:03 4,096 hello.exe 2009/06/14 18:03 25 hello.fs 2 個のファイル 4,121 バイト 2 個のディレクトリ 35,501,461,504 バイトの空き領域 C:\Project\FSharp>hello Hello, world!
hello.fsというコードをコンパイルすると、デフォルトではhello.exeという名前の実行可能ファイルが生成されます。生成される実行可能ファイルは、特に何も指定しなければ--target:exe
オプションが指定されたとみなされコンソールアプリケーションが生成されます。ここで--target:winexe
を指定するとWindowsアプリケーションが生成され、--target:library
を指定するライブラリアセンブリが生成されます。--target:library
オプションの場合は、hello.dllという名前のライブラリアセンブリが生成されます。生成されるプロセスアセンブリのファイル名を指定するには、-o
オプションを使用します。
C:\Project\FSharp>fsc hello.fs -o hello2.exe Microsoft F# Compiler, (c) Microsoft Corporation, All Rights Reserved F# Version 1.9.6.16, compiling for .NET Framework Version v4.0.20506 C:\Project\FSharp>dir ドライブ C のボリューム ラベルがありません。 ボリューム シリアル番号は B41A-XXXX です C:\Project\FSharp のディレクトリ 2009/06/14 18:17 <DIR> . 2009/06/14 18:17 <DIR> .. 2009/06/14 18:03 4,096 hello.exe 2009/06/14 18:03 25 hello.fs 2009/06/14 18:17 4,096 hello2.exe 3 個のファイル 8,217 バイト 2 個のディレクトリ 35,501,383,680 バイトの空き領域
また、参照するアセンブリを指定するには、-r
オプションを使用します。例として、(本来は使用しないので参照を指定する必要はありませんが)System.Windows.Forms.dllへの参照を追加する場合は以下のように使用します。
C:\Project\FSharp>fsc hello.fs -r:System.Windows.Forms.dll Microsoft F# Compiler, (c) Microsoft Corporation, All Rights Reserved F# Version 1.9.6.16, compiling for .NET Framework Version v4.0.20506
最後にF#特有のオプションを紹介します。それは--standalone
というオプションです。実はF#の実行には、FSharp.Core.dllというアセンブリが欠かせません。このアセンブリの中には、F#が内部的に使用するものが多く含まれています。したがって、このアセンブリが存在しないマシンに対しては、このアセンブリも一緒に配布する必要があります。しかしこのオプションを使用すると、コンパイルする際に必要なF#のアセンブリを静的リンクしてくれるため、別途配布する必要は無くなります。
そのほかのオプションは、--help
コマンドで一覧表示することができます。
F#では、記述したF#のコードをスクリプトとして動かすことができます。スクリプトとして動かすと、わざわざコンパイルしてプロセスアセンブリを作らなくても、ソースファイルに書かれたコードを直接実行することができます。F#スクリプトを動かすためには、fsi.exeが必要となります。章のはじめでも説明したように、本来fsi.exeはF#のインタプリタですが、F#スクリプトを動かすためにも使われます。
F#のソースコードには.fsという拡張子がつけられますが、スクリプトファイルには.fsxまたは.fsscriptという拡張子がつけられます。F#の開発環境がインストールされている場合は、エクスプローラ上でその拡張子のファイルを右クリックすると、ポップアップメニューの中に「Run with F# Interactive」というメニュー項目が現れます。これを選択すると、そのスクリプトファイルをF#スクリプトとして実行することができます。
例として、メッセージボックスを出力する以下のプログラムを実行してみましょう。まずscript.fsxというファイルを作成し、そのファイルに以下のコードを記述します。
open System.Windows.Forms MessageBox.Show("Hello, world!")
スクリプトファイルの中では、#r
ディレクティブや#load
ディレクティブなどの一部のディレクティブを使用することができます。ただし、このディレクティブは、コンパイル時は利用することができないので注意してください。たとえば、#r
ディレクティブで参照するアセンブリを指定するには、以下のようにスクリプトを記述します。
#r "System.Windows.Forms.dll" open System.Windows.Forms MessageBox.Show("Hello, world!")
この例ではSystem.Windows.Forms.dllへの参照を追加していますが、本来はデフォルトで参照しているため、わざわざ#r
ディレクティブで指定する必要はありません。ここでは#r
ディレクティブの使用例を示すために使用しました。
F#には、型や値や関数をひとつにグループ化する、モジュール(module)という概念があります。オブジェクト指向におけるクラスという概念も、それらをグループ化するための概念ですが、モジュールには以下の特徴があります。
モジュールの中に、さらにモジュールを含むことが可能
モジュールを継承することはできない
モジュールには、トップレベルモジュール(top-level module)とローカルモジュール(local module)の2種類が存在します。トップレベルモジュールは、ファイル全体を包むモジュールで、1つのファイルに1つしか存在できません。一方、ローカルモジュールは、トップレベルモジュール内に包まれるモジュールで、1つのトップレベルモジュール内に複数存在することができます。
.NET Framework自体にはモジュールという概念は存在しませんが、実際には静的なメンバのみをもつクラスとして実装されます。
トップレベルモジュールとローカルモジュールの定義は、それぞれ以下のように行います。
// トップレベルモジュール module [アクセス指定子
] [名前空間名
.]モジュール名
[値の宣言
|型の宣言
|関数の宣言
]* // ローカルモジュール module [アクセス指定子
]モジュール名
= [値の宣言
|型の宣言
|関数の宣言
]*
トップレベルモジュールでは、モジュールを包み込む名前空間を指定することができます。この名前空間が事前に定義されていない場合は、その名前空間を新たに作り、その中にトップレベルモジュールが置かれます。また、トップレベルモジュールの宣言では、モジュール名の後の=
が無く、各宣言をインデントしてはいけません。一方、ローカルモジュールでは、各宣言は必ずインデントする必要があります。また、モジュール定義には、public
、private
、internal
といったアクセス指定子をつけることができます。アクセス指定子が省略された場合は、public
として扱われます。
トップレベルモジュールの宣言は省略することができます。省略された場合は、そのコードを含むファイルのファイル名が、自動的にトップレベルモジュール名(ただしモジュール名の先頭は強制的に大文字にされる)として採用されます。たとえば、myprogram.fsというファイル名だった場合は、自動的にMyprogramというトップレベルモジュール名がつけられます。
ここで、モジュール定義の例を見てみましょう。
// ファイル名: Library1.fs // トップレベルモジュールMathLibの定義 module MathLib // ローカルモジュールConstantの定義 module Constant = let pi = 3.141592653589793 let plus2 x y = x + y let plus3 x y z = x + y + z
このコードでは、トップレベルモジュールとしてMathLibを定義し、その中にローカルモジュールとしてConstantを定義しています。このソースコードはLibfary1.fsという名前なので、デフォルトではこれをコンパイルするとLibrary1.dllという名前のファイルが作成されます。
モジュールを利用するには、そのモジュールを含むアセンブリへの参照を追加する必要があります。たとえば、前小節で作成したMathLibというモジュールをF#インタプリタから利用する場合は、以下のようにします。
> #r @"C:\Projects\Library1\Library1\bin\Debug\Library1.dll";; // ライブラリアセンブリLibrary1.dllを参照 --> Referenced 'C:\Projects\Library1\Library1\bin\Debug\Library1.dll' > MathLib.plus2 2 3;; // トップレベルモジュール内のplus2関数を呼び出す Binding session to 'C:\Projects\Library1\Library1\bin\Debug\Library1.dll'... val it : int = 5 > MathLib.Constant.pi;; // ローカルモジュール内のpiの値を参照 val it : float = 3.141592654
この例のように、モジュール内のメンバにアクセスするには、メンバ名の前をモジュール名で修飾する必要があります。しかし以下の例のように、あらかじめモジュールをオープンしておくと、モジュール名の修飾を省略することができます。
> pi;; // まだモジュールをオープンしていないので、メンバ名だけではアクセスできない pi;; ^^ stdin(6,1): error FS0039: The value or constructor 'pi' is not defined. > open MathLib.Constant;; // モジュールMathLib.Constantをオープン > pi;; // オープンしたモジュール内のメンバに、修飾なしでアクセス val it : float = 3.141592654 > plus2 3 5;; // MathLibはまだオープンしていないので、修飾なしではアクセスできない plus2 3 5;; ^^^^^ stdin(9,1): error FS0039: The value or constructor 'plus2' is not defined.
後で紹介する名前空間も、同じようにopen
キーワードによってオープンしておくことができます。
F#では、他の.NET Framework上で動作する言語同様、名前空間(namespace)を利用することができます。モジュールと同様、名前空間はコードをグループ化するためのものですが、いくつかの違いがあります。
1つの名前空間は、複数のソースコードに分割することができる
名前空間の直下には型とモジュールとdo
束縛を置くことができるが、値と関数は置くことができない
名前空間はコードの編成単位の最上位に位置するもので、名前空間の下にはモジュールが置かれ、モジュールの下にクラスが置かれます。名前空間の直下には、1つのトップレベルモジュールまたは複数のローカルモジュールのどちらかを置くことができます。名前空間の定義方法は以下のとおりです。
namespace [親名前空間名
.]名前空間名
名前空間
を包むさらに上位の名前空間を指定する場合は、親名前空間名
にその名前空間名を指定します。例を見てみましょう。
namespace MyNamespace module TopLevelModule let print() = printfn "I'm a member of top level module."
これは名前空間の直下にトップレベルモジュールを置く例です。トップレベルモジュールは、名前空間の直下に1つしか置くことができません。前節で説明したように、トップレベルモジュールの場合は、モジュール名の前にそれを包む名前空間名を指定することで、モジュールの定義と同時に名前空間の定義もできます。したがって、上記のコードは以下のように書くこともできます。
module MyNamespace.TopLevelModule let print() = printfn "I'm a member of top level module."
次に、名前空間の下にローカルモジュールを置く場合を説明します。
namespace MyNamespace module LocalModule1 = let print() = printfn "I'm a member of local module 1." module LocalModule2 = let print() = printfn "I'm a member of local module 2."
このようにローカルモジュールは、名前空間の直下に複数個置くことができます。
さらに、名前空間の直下にはdo
束縛を置くこともできます。
namespace MyNamespace module LocalModule1 = let print() = printfn "I'm a member of local module 1." module LocalModule2 = let print() = printfn "I'm a member of local module 2." do MyNamespace.LocalModule1.print() do MyNamespace.LocalModule2.print()
ここに置かれたdo
束縛は、プロセスアセンブリのエントリポイントに配置される、すなわちプロセスアセンブリ(EXE)としてコンパイルした場合ここから実行が開始されます。したがって、上記のコードをコンパイルして実行すると、以下の実行結果が得られます。
軽量構文では、do
キーワードは省略することができます。ここでは、do
束縛であることがわかりやすいように明示的にdo
キーワードをつけました。
I'm a member of local module 1. I'm a member of local module 2.
ここまでの説明の中で、時々アクセス制御に関する説明がでてきました。はじめにアクセス制御について触れたのはオブジェクト指向のときで、次がモジュールのときでした。オブジェクト指向のときは、クラスのメンバに対してアクセス指定子を指定できることを説明し、モジュールのときは、モジュール自体にアクセス制御を指定できることを説明しました。しかしF#では、この2箇所以外にもいくつかアクセス制御を指定することができます。
アクセス指定子が可能な箇所をまとめてみると以下のようになります。ものによっては、アクセス指定子を書く場所がわかりにくいので注意してください。また、ここで示す文法の定義は見易さを優先したため、一部を省略するなどあまり厳密でないところがあります。
let束縛
// 値の束縛 let [アクセス制御
] [mutable]識別子名
[:型指定
] =式
// 関数値の束縛 let [rec] [アクセス制御
] [mutable]識別子名
引数
[:型指定
] =式
モジュール
// トップレベルモジュール module [アクセス指定子
] [名前空間名
.]モジュール名
[値の宣言
|型の宣言
|関数の宣言
]* // ローカルモジュール module [アクセス指定子
]モジュール名
= [値の宣言
|型の宣言
|関数の宣言
]*
型定義
// 暗黙的クラス定義 type [アクセス指定子
]クラス名
= ... // 明示的クラス定義 type [アクセス指定子
]クラス名
() = ... // レコード型定義 type [アクセス指定子
]レコード型名
= ... // 列挙型定義 type [アクセス指定子
]列挙型名
= ... // discriminated union定義 type [アクセス指定子
]レコード型名
= ... // 計量単位定義(応用編で解説) [<Measure>] type [アクセス指定子
]計量単位名
[= ...]
フィールド
val [アクセス指定子
]フィールド名
:型指定
非抽象メソッド
[static] member [アクセス指定子
] [自己識別子
.]メソッド名
引数
[:型指定
] = 式
コンストラクタ
// プライマリコンストラクタ typeクラス名
[アクセス指定子
] (引数
) [as自己識別子
] =クラス本体
// (追加)コンストラクタ new [アクセス指定子
] (引数
) [as自己識別子
] =オブジェクト初期化式
非抽象プロパティ
// 読み取り・書き込みプロパティ [static] member [アクセス指定子
] [自己識別子
.]プロパティ名
with [アクセス指定子
] get() = 式 and [アクセス指定子
] set 引数 = 式 // 読み取り・書き込みプロパティ(上の記法と等価) [static] member [アクセス指定子
] [自己識別子
.]プロパティ名
=式
[static] member [アクセス指定子
] [自己識別子
.]プロパティ名
with set引数
=式
// 読み取り専用プロパティ [static] member [アクセス指定子
] [自己識別子
.]プロパティ名
=式
// 読み取り専用プロパティ(上の記法と等価) [static] member [アクセス指定子
] [自己識別子
.]プロパティ名
with get() =式
// 書き込み専用プロパティ [static] member [アクセス指定子
] [自己識別子
.]プロパティ名
with set引数
=式
非抽象インデックス付きプロパティ
// インデックス付きプロパティ member [アクセス指定子
]自己識別子
.プロパティ名
with [アクセス指定子
] get(インデックス
) =式
and [アクセス指定子
] setインデックス
引数
=式
// 読み取り専用インデックス付きプロパティ member [アクセス指定子
]自己識別子
.プロパティ名
(インデックス
) =式
// 読み取り専用インデックス付きプロパティ(上の記法と等価) member [アクセス指定子
]自己識別子
.プロパティ名
with get(インデックス
) =式
// 書き込み専用インデックス付きプロパティ member [アクセス指定子
]自己識別子
.プロパティ名
with setインデックス
引数
=式
型の内部実装
// discriminated unionのメンバ type [discriminated-union名
] = [アクセス指定子
] [|メンバ名
[of型
] ]+ // レコード型のメンバ typeレコード型名
= [アクセス指定子
] { [メンバ名
:型
;]+ }
読み取り・書き込みプロパティのアクセス制御は、プロパティ全体に指定することもできますし、get
とset
に別々のものを指定することもできます。一番最後のにある型の内部実装とは、discriminated
unionおよびレコード型に対して適用できるもので、その型のメンバ全体のアクセス制御を指定することができます。これにより、外部に対して型名だけを公開して、内部実装を隠蔽するといったことができます。
F#インタプリタは、ユーザの入力に対して毎回何らかの応答を返します。その中でも、新たに型や名前空間やモジュールなど定義した場合は、シグネチャ(signature)と呼ばれる文字列を返します。シグネチャには、定義したものの名前と、それが含むメンバの一覧が書かれています。いくつか例を見てみましょう。
> type sex = Male | Female;; // discriminated unionの定義 type sex = | Male | Female > type Point = { X : float; Y : float };; // レコード型の定義 type Point = {X: float; Y: float;} > type Size(width,height) = // クラスの定義 member v.Width : float = width member v.Height : float = height;; type Size = class new : width:float * height:float -> Point member Width : float member Height : float end > module MyModule = let f x = 5;; // ローカルモジュールの定義 module MyModule = begin val f : 'a -> int end
この4つの入力では、それぞれ新たにdiscriminated union、レコード型、クラスの定義、モジュールの定義を入力し、F#インタプリタがその応答としてシグネチャを返しています。シグネチャには、あくまで定義したものの名前とそのメンバの一覧が書かれているだけで、メンバの内部実装は書かれません。
F#コンパイラによってソースファイルをコンパイルする場合でも、--sig
オプションを指定することによって、そのファイル内で定義されたもののシグネチャをファイルに呼び出力することができます。ここで出力されるファイルはシグネチャファイル(signature
file)と呼ばれ、通常はソースファイル名(拡張子を除く)に加えて.fsiという拡張子をもつファイル名をつけます。例として、以下のソースファイルをライブラリアセンブリとしてコンパイルし、同時にシグネチャファイルを生成してみましょう。
// hello.fs // 新しい名前空間、モジュールの定義 module MyNamespace.MyModule let print_hello() = printfn "hello, world!"
このソースファイルでは、まずMyNamespace名前空間の中にMyModuleモジュールを作成し、そのモジュールの中にprint_hello()
関数を定義しています。これを実際にコンパイルし、hello.fsiの中身を確認してみましょう。
C:\Project\FSharp>fsc hello.fs --sig:hello.fsi --target:library Microsoft F# Compiler, (c) Microsoft Corporation, All Rights Reserved F# Version 1.9.6.16, compiling for .NET Framework Version v4.0.20506 C:\Project\FSharp>dir ドライブ C のボリューム ラベルがありません。 ボリューム シリアル番号は B41A-XXXX です C:\Project\FSharp のディレクトリ 2009/06/21 17:48 <DIR> . 2009/06/21 17:48 <DIR> .. 2009/06/21 17:59 5,120 hello.dll 2009/06/21 17:58 113 hello.fs 2009/06/21 17:59 105 hello.fsi 3 個のファイル 5,338 バイト 2 個のディレクトリ 33,796,431,872 バイトの空き領域 C:\Project\FSharp>type hello.fsi #light namespace MyNamespace module MyModule = begin val print_hello : unit -> unit end
このように、ソースファイル内で定義したもののシグネチャがhello.fsiに出力されたことが確認できます。
シグネチャファイルは、自分で定義した型のシグネチャを確認する以外に、もうひとつ重要な使い道があります。それは、アセンブリに含まれる型やモジュール、およびそのメンバのアクセス制御です。F#では、この節の冒頭で説明したようにC#と同じようにアクセス指定子によってアクセス制御をする方法以外に、もうひとつシグネチャファイルを利用してアクセス制御をすることができます。
実はシグネチャファイルは、自分で内容を編集することができ、その編集したものをF#コンパイラに入力ファイルとして与えることができます。その場合、F#コンパイラはシグネチャファイルを解釈し、その内容にしたがって、同じファイル名(拡張子を除く)をもつソースファイル(たとえばprogram.fsiだったらprogram.fs)に含まれる要素の公開・非公開を制御することができます。このシグネチャファイルとペアになっているソースファイルのことを、とくに実装ファイル(implementation file)と呼びます。
具体的には、以下の手順に従ってアクセス制御を行います。
ソースファイルをコンパイルする際に--sig
オプションを指定してシグネチャファイルを生成する。
シグネチャファイルをエディタで開く。そこにはシグネチャとして、ソースファイル内に含まれる型やモジュールのメンバ一覧が書かれているので、非公開にしたいメンバをそのシグネチャから削除し、公開したいメンバをそのまま残して保存する。
最初のソースファイルを再度コンパイルする。その際、編集したシグネチャファイルも同時に入力ファイルとしてコンパイラに渡す。すると、シグネチャファイルに書かれたメンバのみがpublic
となって公開され、削除されたメンバはinternal
となって非公開になる。
実際に上記の手順にしたがって、シグネチャファイルによるアクセス制御をしてみましょう。まず、以下のコードが書かれたソースファイルMathLib.fsを作成します。ここでは、このソースファイルをライブラリとしてコンパイルすることを考えます。
// ファイル名: MathLib.fs module MathLib // 階乗の計算を末尾再帰で行う補助関数 let rec fact_helper n accum = if n = 1 then accum else fact_helper (n-1) (n*accum) // 階乗の計算を行う関数(実際の計算は補助関数に任せる) let fact n = fact_helper n 1
このソースファイルの中では、MathLibというトップレベルモジュールの中にfact_helper
とfact
という2つの関数を定義しています。ここでは、fact_helper
はあくまで補助関数として使っているだけで、外部には公開したくないとします。そこで、まずシグネチャファイルを得るためにこのソースファイルをコンパイラにかけます。ライブラリとしてコンパイルするので、コンパイルオプションには--target:library
を指定します。また、その際に忘れずに--sig
オプションでシグネチャファイルの名前を指定するようにしてください。
C:\Project\FSharp>fsc MathLib.fs --sig:MathLib.fsi --target:library Microsoft F# Compiler, (c) Microsoft Corporation, All Rights Reserved F# Version 1.9.6.16, compiling for .NET Framework Version v4.0.20506 C:\Project\FSharp>type MathLib.fsi #light module MathLib val fact_helper : int -> int -> int val fact : int -> int
これで、MathLib.fsiというシグネチャファイルが生成されました。次にこのシグネチャファイルを編集して、非公開にしたい関数fact_helper
のシグネチャを削除してください。すると以下のようなシグネチャファイルになるはずです。
#light module MathLib val fact : int -> int
ここで、再びMathLib.fsをコンパイルします。ただし今回は、たった今編集したシグネチャファイルも入力ファイルとしてF#コンパイラに与えます。
C:\Project\FSharp>fsc MathLib.fsi MathLib.fs --target:library Microsoft F# Compiler, (c) Microsoft Corporation, All Rights Reserved F# Version 1.9.6.16, compiling for .NET Framework Version v4.0.20506
ここで注意しなければならないのは、コンパイラに与える入力ファイルの順序です。あるソースファイルに対して、シグネチャファイルが存在する場合、必ずシグネチャファイルのほうを先にかかなければなりません。実際、間違った順番で与えると以下のようにコンパイルエラーになります。
C:\Project\FSharp>fsc MathLib.fs MathLib.fsi --target:library Microsoft F# Compiler, (c) Microsoft Corporation, All Rights Reserved F# Version 1.9.6.16, compiling for .NET Framework Version v4.0.20506 MathLib.fsi(3,1): error FS0191: An implementation of file or module MathLib has already been given. Compilation order is significant in F# because of type infer ence. You may need to adjust the order of your files to place the signature file before the implementation. In Visual Studio files are type-checked in the order they appear in the project file, which can be edited manually or adjusted using the solution explorer
このファイル名の順序は、Visual Studioで開発する場合でも同様に影響します。Visual Studioでは、ソリューションエクスプローラに並んでいるファイル名の順番がそのままコンパイラへ渡すファイルの順番となります。この順番は、ファイル名を右クリックすると出てくるMove Down/Move Upというメニューによって並び替えることができます。
ここまででシグネチャファイルによってアクセス制御されたライブラリMathLib.dllが出来上がりました。では実際にこのライブラリを利用してみましょう。
> #r "C:\Projects\FSharp\MathLib.dll";; // ライブラリアセンブリを参照 --> Referenced 'C:\Projects\FSharp\MathLib.dll' > MathLib.fact 5 ;; // モジュール内の関数を呼び出す Binding session to 'C:\Projects\FSharp\MathLib.dll'... val it : int = 120 > MathLib.fact_helper 5 1;; // モジュール内の非公開関数を呼び出す MathLib.fact_helper 5 1;; --------^^^^^^^^^^^ stdin(3,9): error FS0039: The value, constructor, namespace or type 'fact_helper' is not defined.
コンパイル時のF#コンパイラと、実際にそれを使う場合のF#インタプリタがどの.NETのバージョン向けものかに注意してください。もし.NET 4.0向けのF#コンパイラで作成したライブラリアセンブリを、.NET 2.0向けのF#インタプリタから参照しようとするとエラーメッセージが出て失敗します。詳しくはF#コンパイラの説明を参照してください。
先ほど編集したシグネチャファイルにおいて、fact
関数はシグネチャを残したままにしてあるので普通に呼び出すことができています。。しかしfact_helper
関数のほうは、シグネチャを削除したので呼び出すことができません。