このテキストはpublic domainです。出典を明記せずに転載していただいても構いません。編集は自由に行っていただいても結構ですが、このことに同意した上でお願いいたします。
C#経験者が手っ取り早くF#を使おうとすると、クラス定義の文法がかなり違うことに戸惑うと思います。そこでクラス定義を中心にC#からF#への変換方法をまとめました。なお、F#からC#への変換は逆コンパイラを使えば可能です。
可能な限り出典としてMSDNのリファレンスを示しました。詳細はそちらを参照してください。
出典: Keyword Reference (F#)
F#の例はすべてlight書式(Pythonのようにインデントでブロックを示す方式)です。
C#で定義したpublic constはF#からstaticプロパティに見えるため、F#ではgetだけのstaticプロパティで表すようです。
C#経験者が手っ取り早くF#を使おうとすると、クラス定義の文法がかなり違うことに戸惑うと思います。そこでクラス定義を中心にC#からF#への変換方法をまとめました。なお、F#からC#への変換は逆コンパイラを使えば可能です。
可能な限り出典としてMSDNのリファレンスを示しました。詳細はそちらを参照してください。
出典: Keyword Reference (F#)
F#の例はすべてlight書式(Pythonのようにインデントでブロックを示す方式)です。
出典: コンストラクター (F#)
コンストラクタにはプライマリコンストラクタと追加のコンストラクタの二種類があります。
C#ではコンストラクタを省略すると、暗黙的にコンストラクタが作成されます。(implicit constructor)
F#の{}はC#でのブロックではなく、フィールドの初期値を記述するためのものです。今回は何も初期化しないので空です。初期化の書式はpublicフィールドの項目で取り上げます。
コンストラクタにはプライマリコンストラクタと追加のコンストラクタの二種類があります。
C#ではコンストラクタを省略すると、暗黙的にコンストラクタが作成されます。(implicit constructor)
// C# public class Test { }明示的に定義すると以下のようになります。
// C# public class Test { public Test() { } }F#では完全に省略することはできませんが、型名の後に引数を付けるだけで、コンストラクタを定義したことになります。この形式をプライマリコンストラクタと呼びます。
// F# type Test() = class end追加形式でコンストラクタを定義する場合、クラス名の後の引数は不要です。
// F# type Test = class new() = {} endクラスの定義が空でなければclass〜endは省略できます。
// F# type Test = new() = {}以後、省略できる場合はclass〜endを省略します。
F#の{}はC#でのブロックではなく、フィールドの初期値を記述するためのものです。今回は何も初期化しないので空です。初期化の書式はpublicフィールドの項目で取り上げます。
// C# using System; public class Test { public Test() { Console.WriteLine("Test"); } }
// F# open System type Test() = do Console.WriteLine("Test")以後、usingやopenは省略します。
追加形式で定義すると以下のようになります。
// C# public class Test { public Test() { } public Test(int a) { } }
// F# type Test = new() = {} new(a:int) = {}プライマリコンストラクタが存在すると、それ以外のコンストラクタから必ず呼び出す必要があります。C#では引数の後でthis()として呼び出す方式に相当します。
// C# public class Test { public Test() { } public Test(int a) : this() { } }
// F# type Test() = new(a:int) = Test()処理を入れると以下のようになります。
// C# public class Test { public Test() { Console.WriteLine("Test"); } public Test(int a) : this() { Console.WriteLine("a = {0}", a); } }
// F# type Test() = do Console.WriteLine("Test") new(a:int) = Test() then Console.WriteLine("a = {0}", a)
F#にはthisキーワードがありません。自分自身を参照するには指定する必要があります。メソッドやプロパティでの使い方はそれぞれの項で取り上げるため、ここではコンストラクタだけを取り上げます。
F#では暗黙的なコンストラクタからthisを参照するためには、引数の後にasで指定します。
// C# public class Test { public Test() { Console.WriteLine("{0}", this); } }
// F# type Test() as this = do Console.WriteLine("{0}", this)thisはキーワードではないため、任意の名前に変えられます。サンプルではselfやxを使うことが多いようです。
// F# type Test() as self = do Console.WriteLine("{0}", self)追加形式のコンストラクタからthisを参照するときも、引数の後にasで指定します。
// F# type Test = new() as this = {} then Console.WriteLine("{0}", this)
出典: Access Control (F#)
F#にはpublic, internal, privateがあります。protectedはありません。他の言語で作成されたprotectedメンバにアクセスすることは可能です。F#でoverrideしてもprotectedのままです。
F#にはpublic, internal, privateがあります。protectedはありません。他の言語で作成されたprotectedメンバにアクセスすることは可能です。F#でoverrideしてもprotectedのままです。
F#ではクラス名の前に書きます。
// C# private class Test { public Test() { } }
// F# type private Test() = class end
F#では暗黙的なコンストラクタでは引数の前に書きます。
// C# public class Test { private Test() { } }
// F# type Test private() = class end明示的なコンストラクタではnewの前に書きます。
// F# type Test = private new() = {}
F#でコンストラクタを省略するとstaticクラスになります。インスタンスを作成することはできません。
// C# public static class Test { }
// F# type Test = class end
static doで記述します。doに対応する暗黙的なコンストラクタが必要です。C#ではstaticコンストラクタにアクセス制御を指定するとエラーになりますが、F#では指定できます。外部からインスタンスを作らせないためprivateにしておくと良いでしょう。
// C# public static class Test { static Test() { Console.WriteLine("Test"); } }
// F# type Test private() = static do Console.WriteLine("Test")
クラスの継承について取り上げます。インターフェースの実装は構文が異なります。詳細はインターフェースのの項目を参照してください。
クラス定義の先頭でinheritを宣言します。暗黙的なコンストラクタでは、inheritに引数を付けてベースクラスのコンストラクタを呼び出します。二番目以降のコンストラクタからは必ず暗黙的なコンストラクタを呼び出さないといけないため、ベースクラスの別のコンストラクタを呼ぶことはできません。
クラス定義の先頭でinheritを宣言します。暗黙的なコンストラクタでは、inheritに引数を付けてベースクラスのコンストラクタを呼び出します。二番目以降のコンストラクタからは必ず暗黙的なコンストラクタを呼び出さないといけないため、ベースクラスの別のコンストラクタを呼ぶことはできません。
// C# public class Test { } public class Test2 : Test { } public class Test3 : Test2 { public Test3() : base() { Console.WriteLine("Test3"); } public Test3(int a) : this() { Console.WriteLine("{0}", a); } }
// F# type Test() = class end type Test2() = inherit Test() type Test3() = inherit Test2() do Console.WriteLine("Test3") new(a:int) = Test3() then Console.WriteLine("{0}", a)明示的なコンストラクタでは、{}の中でベースクラスのコンストラクタを呼び出します。暗黙的な場合と異なり、ベースクラスの別のコンストラクタを呼ぶことができます。
// C# public class Test4 : Test3 { public Test4() : base() { Console.WriteLine("Test4"); } public Test4(int a) : base(a + 1) { Console.WriteLine("{0}", a); } }
// F# type Test4 = inherit Test3 new() = { inherit Test3() } then Console.WriteLine("Test4") new(a:int) = { inherit Test3(a + 1) } then Console.WriteLine("{0}", a)
thisとは対照的にbaseはキーワードのため、特に何かを宣言しなくても使うことができます。
// C# public class Test { public Test() { Console.WriteLine("{0}", base.GetHashCode()); } }
// F# type Test() = inherit Object() do Console.WriteLine("{0}", base.GetHashCode())inheritを省略してもObjectから継承されますが、baseが隠蔽されてアクセスできなくなります。ここはC#と異なる点です。
// F# type Test() = do Console.WriteLine("{0}", base.GetHashCode()) // エラー
出典: let Bindings in Classes (F#)
F#ではデフォルトが定数(C#のconst)のため、値を変更できるようにするのがmutable指定です。必ず初期値を指定する必要があるため、0を代入しています。0がintであることから、自動的にaとbはintであると型推論されます。C#ではローカル変数しか型推論できませんが、F#ではフィールドでも型推論できます。
// C# public class Test { private int a, b; }
// F# type Test() = let mutable a = 0 let mutable b = 0letはprivate固定です。letを定義するときには、必ず暗黙的なコンストラクタを指定する必要があります。
F#ではデフォルトが定数(C#のconst)のため、値を変更できるようにするのがmutable指定です。必ず初期値を指定する必要があるため、0を代入しています。0がintであることから、自動的にaとbはintであると型推論されます。C#ではローカル変数しか型推論できませんが、F#ではフィールドでも型推論できます。
F#ではmutableを省略するとconstになります。C#でconstはクラスメンバのため、F#ではstaticにしています。
// C# public class Test { private const int a = 1, b = 2; }
// F# type Test() = static let a = 1 static let b = 2
出典: Explicit Fields The val Keyword (F#)
letはprivate固定です。publicフィールドを定義するにはvalを使います。valは初期値をその場で指定できないため型推論ができません。型を明示する必要があります。[<DefaultValue>]は初期値の指定を省略するための属性です。F#で定義したクラスには更に(false)が必要で、nullで初期化されます。
valはletと初期値の指定方法が異なります。初期値を指定する場合はプライマリコンストラクタを使うことができません。追加形式のコンストラクタから初期化します。
letはprivate固定です。publicフィールドを定義するにはvalを使います。valは初期値をその場で指定できないため型推論ができません。型を明示する必要があります。[<DefaultValue>]は初期値の指定を省略するための属性です。F#で定義したクラスには更に(false)が必要で、nullで初期化されます。
// C# public class Test { public int a, b; }
// F# type Test() = [<DefaultValue>] val mutable a:int [<DefaultValue>] val mutable b:int [<DefaultValue(false)>] val mutable c:Test
valはletと初期値の指定方法が異なります。初期値を指定する場合はプライマリコンストラクタを使うことができません。追加形式のコンストラクタから初期化します。
// C# public class Test { public int a = 1, b = 2; }
// F# type Test = val mutable a:int val mutable b:int new() = { a = 1; b = 2 }処理を入れると以下のようになります。
// C# public class Test { public int a = 1, b = 2; public Test() { Console.WriteLine("Test"); } }
// F# type Test = val mutable a:int val mutable b:int new() = { a = 1; b = 2 } then Console.WriteLine("Test")F#の書式では初期化を処理に先立って行いますが、この構文はC++に似ています。
// C++ class Test { public: int a, b; Test(): a(1), b(2) { printf("Test\n"); } };
上で作成したクラスをnewで初期化することができます。
出典: Values (F#), Arithmetic Operators (F#)
// C# var t = new Test { a = 3, b = 4 };
// F# let t = new Test(a = 3, b = 4)インスタンス作成後に値を変更するのは以下の通りです。
// C# t.a = 5; t.b = 6;
// F# t.a <- 5 t.b <- 6F#の<-は代入です。初期化のときだけ=で、その後の変更は<-です。初期化以外で=を使うと比較になります。mutableを付けなければ変更は一切できません。
出典: Values (F#), Arithmetic Operators (F#)
C# | F# | |
---|---|---|
定数 | const int a = 1; | let a = 1 |
変数 | int a = 1; | let mutable a = 1 |
代入 | a = 2; | a <- 2 |
比較 | a == 0 | a = 0 |
valをprivateにすることもできます。
// C# public class Test { private int a = 1, b = 2; }
// F# type Test = val mutable private a:int val mutable private b:int new() = { a = 1; b = 2 }
letの方が書式が簡潔なので、わざわざvalをprivateで使う必要はあまりないようです。前例をletで書き直せば以下の通りです。
// F# type Test() = let mutable a = 1 let mutable b = 2フィールドはletで定義して、外に見せたいものだけプロパティにするのが、F#では一番手軽かもしれません。
// C# public class Test { private int a = 1, b = 2; public int A { get { return a; } } public int B { get { return b; } } }
// F# type Test = let mutable a = 1 let mutable b = 2 member this.A = a member this.B = bプロパティの詳細は後述します。
// C# public static class Test { public const int A = 1, B = 2; }staticなvalではmutableが強制されます。letはpublicにできません。そのためletでもvalでもpublic constを表現できません。
C#で定義したpublic constはF#からstaticプロパティに見えるため、F#ではgetだけのstaticプロパティで表すようです。
// C# public class Test { public static int A { get { return 1; } } public static int B { get { return 2; } } }
// F# type Test = static member A = 1 static member B = 2
出典: Methods (F#)
- メソッドやプロパティはletや暗黙的コンストラクタのdoよりも後ろに書かないとエラーになります。
- valや明示的なコンストラクタ(new)との位置に決まりはありません。これらはメソッドやプロパティと同じ扱いで、letやdoの後ろであれば任意の位置に記述できます。
- memberキーワードで指定します。staticはmemberの前(static member)、アクセス制御はmemberの後(member private)に書きます。
- メソッド名の前に自分自身のインスタンスへの参照を指定します。例: member this.Method
- staticでは必要ありません。例: static member Method
- 呼び出すときはthis.Method()とします。thisは省略できません。staticメソッドの場合はクラス名です。
- letで定義したフィールドは例外で、名前だけで呼び出すことができます。thisやクラス名を付けるとエラーになります。
- valで定義したフィールドにはthisやクラス名(staticの場合)が必要です。この面からもvalよりもletの方が便利です。
- F#は基本的に前方参照ができませんが、メンバだけは前方参照が可能です。前方というのはパーサの進行方向を基準にしていて、ソースを表示している画面に対しては下側です。
- 型推論があるため、引数や戻り値の型は省略できます。呼び出し側での使い方にも左右されるため、型指定した方が無難なケースもあります。
- F#では最後に記述した値が戻り値として扱われます。C#のreturnキーワードに該当するものはありません。
// C# public class Test { private int a = 1; public Test() { Foo(); } private void Foo() { Console.WriteLine("Test"); } public int Add(int b) { a = add(a, b); return a; } private static int add(int a, int b) { return a + b; } }
// F# type Test() as this = let mutable a = 1 do this.Foo() member private this.Foo() = Console.WriteLine("Test") member x.Add(b) = a <- Test.add(a, b) a static member private add(a:int, b:int) = a + bthisの項目で述べたように、thisはキーワードではなく任意の名前が付けられます。サンプルではxを使うことが多いようです。
メソッド定義とは書式が異なります。書式は「引数の型->戻り値の型」です。voidはunitと書きます。これらはML由来の書式です。abstractクラスは属性で指定して省略できません。
//C# public abstract class Test { public abstract void Foo(); public abstract int Bar(); public abstract void Baz(int a); }
//F# [<AbstractClass>] type Test() = abstract Foo: unit -> unit abstract Bar: unit -> int abstract Baz: int -> unit複数の引数は*で連結します。C#で通常使う形式です。F#ではtupleと呼びます。
//C# public abstract class Test { public abstract int Add(int a, int b); }
//F# [<AbstractClass>] type Test() = abstract Add: int * int -> intカリー化した関数は->を複数使います。C#では通常使わない形式ですが、関数型言語本来の形式です。
//C# public abstract class Test { public abstract Func<int, int> Curry(int a); }
//F# [<AbstractClass>] type Test() = abstract Curry: int -> int -> int
abstractに続けてdefaultキーワードで定義します。abstractで型が明示されているため、型指定は完全に省略できます。abstractメソッドの例をまとめてvirtualに変換したものを示します。空の関数の()はunit(C#のvoid)を示します。
//C# public class Test { public virtual void Foo() { } public virtual int Bar() { return 1; } public virtual void Baz(int a) { } public virtual int Add(int a, int b) { return a + b; } public virtual Func<int, int> Curry(int a) { return b => a + b; } }
//F# type Test() = abstract Foo: unit -> unit abstract Bar: unit -> int abstract Baz: int -> unit abstract Add: int * int -> int abstract Curry: int -> int -> int default this.Foo() = () default this.Bar() = 1 default this.Baz(a) = () default this.Add(a, b) = a + b default this.Curry a b = a + bカリー化した関数は以下のように呼び出します。
//C# var t = new Test(); int a = t.Curry(2)(3);
//F# let t = new Test() let a = t.Curry(2)(3)
overrideキーワードで定義します。virtualメソッドの例に挙げたクラスTestをoverrideしたものを示します。virtualと同じように型指定を省略できます。
// C# public class Test2 : Test { public override void Foo() { Console.WriteLine("Foo"); base.Foo(); } public override int Bar() { return 2; } public override void Baz(int a) { Console.WriteLine("{0}", a); } public override Func<int, int> Curry(int a) { return b => a * b; } }
// F# type Test2() = inherit Test() override this.Foo() = Console.WriteLine("Foo") base.Foo() override this.Bar() = 2 override this.Baz(a) = Console.WriteLine("{0}", a) override this.Curry a b = a * b
出典: Tuples (F#)
outを使うのは戻り値以外にも値を返したいケースがほとんどです。F#では複数の値を返すことができるため、outが自動的に戻り値に組み込まれます。戻り値が2個の場合、最初の値はfst、二番目の値はsndとして取得します。それぞれfirst, secondの略です。Dictionary.TryGetValue()の場合、C#での戻り値がfst、outで返される値がsndとなります。
outを使うのは戻り値以外にも値を返したいケースがほとんどです。F#では複数の値を返すことができるため、outが自動的に戻り値に組み込まれます。戻り値が2個の場合、最初の値はfst、二番目の値はsndとして取得します。それぞれfirst, secondの略です。Dictionary.TryGetValue()の場合、C#での戻り値がfst、outで返される値がsndとなります。
// C# var dict = new Dictionary<string, int>(); dict.Add("a", 1); int value; if (dict.TryGetValue("a", out value)) Console.WriteLine("value = {0}", value);
// F# let dict = new Dictionary<string, int>() dict.Add("a", 1) let value = dict.TryGetValue("a") if fst value then Console.WriteLine("value = {0}", snd value);letで複数を指定するとtupleの中身を直接取得できます。
// F# let dict = new Dictionary<string, int>() dict.Add("a", 1) let result, value = dict.TryGetValue("a") if result then Console.WriteLine("value = {0}", value);自分でメソッドを定義する場合、戻り値を()で囲んでコンマで区切ります。
// C# public class Test { private int a = 1, b = 2; public int GetValue(out int value) { value = b; return a; } }
// F# type Test() = let mutable a = 1 let mutable b = 2 member this.GetValue() = (a, b)tupleの表現は引数と同じですが、実際にF#では引数がtupleとして取り扱われます。複数の値を持つ形式はカリー化として区別されます。
F#のセンテンスでは戻り値を黙殺すると警告されます。
This expression should have type 'unit', but has type 'int'.戻り値が必要ない場合は明示的に無視します。
// C# public class Test { public static int Foo() { return 1; } } Test.Foo();
// F# type Test = static member Foo() = 1 ignore(Test.Foo())ignore()で囲むと肝心の処理が分かりにくくなるため、通常は|>演算子を使うようです。
// F# Test.Foo() |> ignoreC言語で(void)として戻り値を捨てることに相当します。
// C int Foo() { return 1; } (void)Foo();
出典: Properties (F#)
アクセス制御やthisなどはメソッドと同じです。プロパティ特有のget/setの書き方を示します。getだけの書き方は非常に簡単です。
アクセス制御やthisなどはメソッドと同じです。プロパティ特有のget/setの書き方を示します。getだけの書き方は非常に簡単です。
// C# public class Test { private int test1 = 0; public int Test1 { get { return test1; } set { test1 = value; } } public int Test2 { get { return 2; } } public int Test3 { set { Console.WriteLine("{0}", value); } } public static int Test4 { get { return 1; } private set { Console.WriteLine("{0}", value); } } }
// F# type Test() = let mutable test1 = 0 member this.Test1 with get() = test1 and set(value) = test1 <- value member this.Test2 = 2 member this.Test3 with set(value:int) = Console.WriteLine("{0}", value) static member Test4 with get() = 1 and private set(value:int) = Console.WriteLine("{0}", value)呼び出すときはメソッド同様thisやクラス名が必要です。値をセットするのは<-演算子です。
// C# var t = new Test(); t.Test1 = Test.Test4;
// F# let t = new Test() t.Test1 <- Test.Test4
F#には自動プロパティは存在しないため、フィールドをラップします。
// C# public class Test { public int Foo { get; set; } }
// F# type Test() = let mutable foo = 0 member this.Foo with get() = foo and set(value) = foo <- value
メソッドと違い引数が存在しません。get/setを記述します。
// C# public abstract class Test { public abstract int Foo { get; } public abstract int Bar { set; } public abstract int Baz { get; set; } }
// F# [<AbstractClass>] type Test() = abstract Foo: int abstract Bar: int with set abstract Baz: int with get, set
メソッド同様、abstractをその場でdefault定義します。
// C# public class Test { public virtual int Foo { get { return 1; } } public virtual int Bar { set { Console.WriteLine("{0}", value); } } public virtual int Baz { get; set; } }
// F# type Test() = let mutable baz = 0 abstract Foo: int default this.Foo = 1 abstract Bar: int with set default this.Bar with set(value) = Console.WriteLine("{0}", value) abstract Baz: int with get, set default this.Baz with get() = baz and set(value) = baz <- valueletはメンバ定義の前に置かないとエラーになります。
virtualプロパティの例に挙げたクラスTestをoverrideしたものを示します。
// C# public class Test2 : Test { public override int Baz { get { return base.Baz; } set { Console.WriteLine("{0}", value); base.Baz = value; } } }
// F# type Test2() = inherit Test() override this.Baz with get() = base.Baz and set(value) = Console.WriteLine("{0}", value) base.Baz <- value
出典: Events (F#)
F#でC#のeventと同じことをするためには、Event.createでイベントを作成してプロパティで公開します。Event.createの戻り値はtupleになっていて、fstがInvoke用、sndが追加・削除用です。F#ではsenderが使われません。
F#でC#のeventと同じことをするためには、Event.createでイベントを作成してプロパティで公開します。Event.createの戻り値はtupleになっていて、fstがInvoke用、sndが追加・削除用です。F#ではsenderが使われません。
// C# public class Test { public event EventHandler<EventArgs> Foo; public void OnFoo(EventArgs e) { if (Foo != null) Foo.Invoke(this, e); } } var t = new Test(); t.Foo += (sender, e) => Console.WriteLine("Foo!"); t.OnFoo(EventArgs.Empty);
// F# type Test() = let fooInvoke, foo = Event.create<EventArgs>() member this.Foo = foo member this.OnFoo(e:EventArgs) = fooInvoke(e) let t = new Test() t.Foo.Add(fun e -> Console.WriteLine("Foo!")) t.OnFoo(EventArgs.Empty)
Addが関数のためC#の+=より括弧が多くなっていますが、<|演算子を使えば括弧を減らせます。
// C# t.Foo += (sender, e) => Console.WriteLine("Foo!");
// F# t.Foo.Add <| fun e -> Console.WriteLine("Foo!")F#の<|演算子は、Haskellの$と同じように括弧を省略するのにも使えますが、複数の関数を適用する場合は<<演算子で合成します。
// C# foo(bar(baz(100)))
// F# foo << bar << baz <| 100
// Haskell foo $ bar $ baz $ 100F#には逆向きの|>演算子もあります。
// F# 100 |> baz |> bar |> foo関数を合成する>>演算子もありますが、演算子の結合順位の関係上、括弧で囲みます。
// F# 100 |> (baz >> bar >> foo)
出典: Abstract Classes (F#)
属性[<AbstractClass>]で指定します。
属性[<AbstractClass>]で指定します。
//C# public abstract class Test { public abstract void Foo(); }
//F# [<AbstractClass>] type Test() = abstract Foo: unit -> unit
属性[<Sealed>]で指定します。
//C# public sealed class Test { }
//F# [<Sealed>] type Test() = class end
F#ではクラス定義で前方参照できません。循環参照するクラスはandで連結して定義する必要があります。
// C# public class Test { public Test2 CreateTest2() { return new Test2(); } } public class Test2 : Test { }
// F# type Test() = member this.CreateTest2() = new Test2() and Test2() = inherit Test()C++のように仮にclass Test2;と定義してコンパイルを通すことはできません。
出典: Structures (F#)
定義をstruct〜endで行います。省略するとクラスとして解釈されてしまうため省略できません。フィールドはvalで定義します。
クラスと異なる点は以下の通りです。
定義をstruct〜endで行います。省略するとクラスとして解釈されてしまうため省略できません。フィールドはvalで定義します。
クラスと異なる点は以下の通りです。
- 暗黙的にコンストラクタが作成されます。
- valに[<DefaultValue>]を付けなくても初期化されます。
- フィールドを変更するにはインスタンスをmutableにする必要があります。
// C# public struct Test { public int a, b, c; } var t = new Test { a = 1, b = 2, c = 3 }; t.a = 4; t.b = 5; t.c = 6;
// F# type Test = struct val mutable a:int val mutable b:int val mutable c:int end let mutable t = new Test(a = 1, b = 2, c = 3) t.a <- 4 t.b <- 5 t.c <- 6メソッドの定義などはクラスと同じため省略します。
出典: Interfaces (F#)
実装を含まずにabstractメンバだけを定義した型は自動的にインターフェースとなります。
実装を含まずにabstractメンバだけを定義した型は自動的にインターフェースとなります。
// C# public interface ITest { void Foo(); }
// F# type ITest = abstract Foo: unit -> unit
F#にはC#でのインターフェースの明示的な実装に相当する方法しかありません。C#のように同じ名前のメンバを自動的に実装とすることはできません。
// C# public class Test : ITest { void ITest.Foo() { Console.WriteLine("Foo"); } }
// F# type Test() = interface ITest with member this.Foo() = Console.WriteLine("Foo")
継承関係のあるインターフェースは、複数のインターフェースの実装として記述します。以下の例では、IEnumerable<T>はIEnumerableと名前空間が違うことに注意が必要です。
// C# using System.Collections; using System.Collections.Generic; public class Test : IEnumerable<int> { public IEnumerator<int> GetEnumerator() { for (int i = 0; i < 10; i++) yield return i; } IEnumerator<int> IEnumerable<int>.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } }
// F# open System.Collections open System.Collections.Generic type Test() = member this.GetEnumerator() = (seq { for i = 0 to 9 do yield i }).GetEnumerator() interface IEnumerable<int> with member this.GetEnumerator() = this.GetEnumerator() interface IEnumerable with member this.GetEnumerator() = this.GetEnumerator() :> IEnumeratorF#では戻り値で自動的にアップキャストが行われないため、明示的にIEnumeratorにキャストしています。詳細はキャストの項目を参照してください。
出典: Delegates (F#)
- 構文: type 名前 = delegate of 引数の型 -> 戻り値の型
- 引数が複数あるときは()で囲まないとカリー化と解釈されます。
- 関数の引数にdelegateを渡すとき、C#ではメソッドを渡せば自動的にdelegateのインスタンスが作成されますが、F#では明示的にdelegateをnewする必要があります。
- 無名関数はnewする必要がありません。
- delegateの呼び出しにはInvokeメソッドが必要です。F#ネイティブの関数型とは扱いが異なります。
// C# public delegate int Delg(int a, int b); public static class Test { public static int Add(int a, int b) { return a + b; } public static int Call(Delg d) { return d(1, 2); } } var v1 = Test.Call(Test.Add); var v2 = Test.Call((a, b) => a * b);
// F# type Delg = delegate of (int * int) -> int type Test = static member Add(a:int, b:int) = a + b static member Call(d:Delg) = d.Invoke(1, 2) let v1 = Test.Call(new Delg(Test.Add)) let v2 = Test.Call(fun(a, b) -> a * b)
C#との相互運用性が必要ない場合は、delegateを使わずにF#ネイティブの関数型を使った方が便利です。相違点は以下の通りです。
- プロトタイプを宣言する必要がありません。
- newする必要がありません。
- Invokeメソッドを使わずに直接呼び出せます。
// F# type Test = static member Add(a:int, b:int) = a + b static member Call(d:int * int -> int) = d(1, 2) let v1 = Test.Call(Test.Add) let v2 = Test.Call(fun(a, b) -> a * b)
nullはキーワードとして存在しますが、そのままでは代入できません。Unchecked.defaultofを使用します。
// C# Test t = null;
// F# let t = Unchecked.defaultof<Test>
戻り値はUnchecked.defaultofを使用します。引数にはnullを渡せます。
// C# Test Foo(Test a) { return null; } Foo(null);
// F# let Foo(a:Test) = Unchecked.defaultof<Test> Foo(null) |> ignoreF#では戻り値を使わないため明示的に無視しています。
box化してobj型(System.ObjectのF#での表現)に変換してから判定します。
// C# if (a == null) Console.WriteLine("null"); if (a != null) Console.WriteLine("not null");
// F# if box a = null then Console.WriteLine("null") if box a <> null then Console.WriteLine("not null")
F#では初期値や戻り値で自動的にアップキャストが行われないため、:>演算子で明示的にアップキャストする必要があります。
// C# public abstract class Test { public abstract Test Foo(); } public class Test2 : Test { public override Test Foo() { return this; } } Test t = new Test2()
// F# [<AbstractClass>] type Test() = abstract Foo:unit -> Test type Test2() = inherit Test() override this.Foo() = this :> Test let t:Test = new Test2() :> Test変数の上書き代入や関数の引数では自動的にアップキャストが行われます。
// C# public class Test { public static void Foo(Test t) { } } public class Test2 : Test { } Test.Foo(new Test2()); var t = new Test(); t = new Test2();
// F# type Test() = static member Foo(t:Test) = () type Test2() = inherit Test() Test.Foo(new Test2()) let mutable t = new Test() t <- new Test2()
C#でもF#でもダウンキャストは自動的に行われません。F#では:?>演算子でダウンキャストします。直接の派生関係にない型のキャストはコンパイル時にエラーとなりますが、obj型(C#でのobject型)だけは特別にコンパイルが通ります。C#もF#もキャストに失敗すると例外が発生します。C#ではインターフェースへのキャストは常にコンパイルが通りますが、F#では派生関係が確認できないものはコンパイルエラーになります。
// C# public interface ITest { void Foo();} public class Test { } public class Test2 : Test { } Test t1 = new Test2(); var t2 = (Test2)t1; var t3 = new object(); var t4 = (Test2)t3; // キャスト失敗 var t5 = ""; var t6 = (Test2)t5; // コンパイルエラー var t7 = (ITest)t1; // キャスト失敗
// F# type ITest = abstract Foo: unit -> int type Test() = class end type Test2() = inherit Test() let t1 = new Test2() :> Test let t2 = t1 :?> Test2 let t3 = new obj() let t4 = t3 :?> Test2 // キャスト失敗 let t5 = "" let t6 = t5 :?> Test2 // コンパイルエラー let t7 = t1 :?> ITest // コンパイルエラーF#でインターフェースへのキャストのコンパイルを通すには、box化でobj型に変換する必要があります。詳細はbox化の項目を参照してください。
// F# let t7 = box t1 :?> ITest // コンパイルを通す
F#では:?演算子を使います。F#でコンパイルエラーになる条件はダウンキャストと同じで、box化でコンパイルが通るのも同じです。C#では常にコンパイルが通りますが、派生関係が認められないケースでは警告されます。
// C# public interface ITest { void Foo();} public class Test { } public class Test2 : Test { } Test t1 = new Test2(); var t2 = t1 is Test2; // true var t3 = new object(); var t4 = t3 is Test2; // false var t5 = ""; var t6 = t5 is Test2; // false, 警告 var t7 = t1 is ITest; // false
// F# type ITest = abstract Foo: unit -> int type Test() = class end type Test2() = inherit Test() let t1 = new Test2() :> Test let t2 = t1 :? Test2 // true let t3 = new obj() let t4 = t3 :? Test2 // false let t5 = "" let t6 = box t5 :? Test2 // false, box化しなければエラー let t7 = box t1 :? ITest // false, box化しなければエラー
F#には相当する演算子がありません。敢えて記述すれば以下のようになります。
// C# public class Test { } public class Test2 : Test { } var t1 = new Test(); var t2 = t1 as Test2;
// F# type Test() = class end type Test2() = inherit Test() let t1 = new Test() let t2 = if (t1 :? Test2) then t1 :?> Test2 else Unchecked.defaultof<Test2>毎回書くのは現実的ではありません。条件で分岐した方が良いでしょう。
// C# var t1 = new Test(); if (t1 is Test2) { var t2 = (Test2)t1; }
// F# let t1 = new Test() if (t1 :? Test2) then let t2 = t1 :?> Test2
F#では型名を関数のように使ってキャストできます。関数の呼び出し方には柔軟性があるため、C#と同じ書式も使えます。ignoreで出てきた|>演算子を使えば直感的な見た目になります。
// C# char a = 'a'; int b = (int)a;
// F# let a = 'a' let b1 = (int)a let b2 = int a let b3 = int(a) let b4 = a |> intobj型からのキャストは次のunbox化の項目を参照してください。
box化はどんな型でもobj型に変換します。キャストで無条件にコンパイルを通したいときにも使います。unbox化は値型で使います。参照型ではダウンキャストを使います。C#ではキャストで自動化されています。
// C# int a1 = 1; object b = (object)a1; int a2 = (int)b;
// F# let a1 = 1 let b = box a1 let a2 = unbox<int> b
出典: Arrays (F#)
作成はnewではなく専用の関数から行います。アクセスは名前と[]の間にピリオドが必要です。型名はC#と同じです。(例: int[])
作成はnewではなく専用の関数から行います。アクセスは名前と[]の間にピリオドが必要です。型名はC#と同じです。(例: int[])
// C# var a = new int[10]; a[0] = 5; var b = new int[0]; var c = new int[2, 2]; c[1, 1] = 9;
// F# let a = Array.zeroCreate<int> 10 a.[0] <- 5 let b = Array.empty<int> let c = Array2.zeroCreate<int> 2 2 c.[1, 1] <- 9
F#では[|〜|]内にセミコロンで区切って列挙します。
// C# var array = new[] { "abc", "def", "ghi" };
// F# let array = [| "abc"; "def"; "ghi" |]
F#では容易に手続き型の記述ができるため、フロー制御もC#と同じようにできます。ですがループでのbreakやcontinueがないため、構造を工夫する必要があります。ここではC#からの変換がメインのため強引な方法を紹介していますが、関数型言語本来の考え方からは外れています。特にフラグを使ったループなどは問題外です。breakやcontinueなどが存在しないのは、関数型言語として制御構造は再帰を使って表現することが望ましいためです。詳細は専門の書籍などを参照してください。
出典: Conditional Expressions: if... then...else (F#)
F#ではC#での括弧がなくなった代わりにthenが付きます。比較演算子は==ではなく=です。
F#ではC#での括弧がなくなった代わりにthenが付きます。比較演算子は==ではなく=です。
// C# if (a == 1) Console.WriteLine("1"); else if (a == 2) Console.WriteLine("2"); else Console.WriteLine("?");
// F# if a = 1 then Console.WriteLine("1") else if a = 2 then Console.WriteLine("2") else Console.WriteLine("?")
F#のif文が値を返すことを利用すると簡潔に記述できます。
// C# int b; if (a == 1) b = 1; else if (a == 2) b = 2; else b = 3;
// F# let b = if a = 1 then 1 else if a = 2 then 2 else 3考え方はC#の三項演算子と同じです。
// C# int b = a == 1 ? 1 : a == 2 ? 2 : 3;
ifを単なるフロー制御で使ったとき、値を返すメソッドを呼んだため型の不一致でエラーになることがあります。
StringBuilder.Append()はStringBuilderを返しますが、Console.WriteLine()はvoidのため、型の不一致でエラーになります。そのため前者を明示的に無視します。
StringBuilder.Append()はStringBuilderを返しますが、Console.WriteLine()はvoidのため、型の不一致でエラーになります。そのため前者を明示的に無視します。
// C# var sb = new StringBuilder(); if (a > 0) sb.Append("a > 0"); else Console.WriteLine("a <= 0");
// F# let sb = new StringBuilder() if a > 0 then sb.Append("a > 0") |> ignore else Console.WriteLine("a <= 0")
出典: Loops: while...do Expression (F#)
F#の構文はC#とほとんど同じです。条件の後にdoが付きます。
F#の構文はC#とほとんど同じです。条件の後にdoが付きます。
// C# int i = 0; while (i < 10) { Console.WriteLine("i = {0}", i); i++; }
// F# let mutable i = 0 while i < 10 do Console.WriteLine("i = {0}", i) i <- i + 1
該当するキーワードが存在しません。手っ取り早く同じ動作をさせるにはフラグを立てます。
// C# int i = 0; while (true) { Console.WriteLine("i = {0}", i); i++; if (i > 10) break; }
// F# let mutable i = 0 let mutable f = true while f do Console.WriteLine("i = {0}", i) i <- i + 1 if i > 10 then f <- false
該当するキーワードが存在しません。条件分岐で表現します。
// C# int i = 0; while (i < 10) { i++; if ((i & 1) == 1) continue; Console.WriteLine("i = {0}", i); }
// F# let mutable i = 0 while i < 10 do i <- i + 1 if not((i &&& 1) = 1) then Console.WriteLine("i = {0}", i)
該当する構文がありません。フラグを使ってwhileに変換すると簡単です。
// C# int i = 0; do { Console.WriteLine("i = {0}", i); i++; } while (i < 10);
// F# let mutable i = 0 let mutable f = true while f || i < 10 do if f then f <- false Console.WriteLine("i = {0}", i) i <- i + 1breakやcontinueはwhileと同様に工夫が必要です。
出典: Loops: for...to Expression (F#)
F#のforは1ずつの増減しか扱えません。
F#のforは1ずつの増減しか扱えません。
// C# for (int i = 1; i <= 10; i++) Console.WriteLine("i = {0}", i); for (int i = 9; i >= 1; i--) Console.WriteLine("i = {0}", i);
// F# for i = 1 to 10 do Console.WriteLine("i = {0}", i) for i = 9 downto 1 do Console.WriteLine("i = {0}", i)それ以外のforはwhileに変換すると簡単ですが、ループに使う変数のスコープが異なる点に注意が必要です。
// C# for (int i = 1; i < 10; i += 2) Console.WriteLine("i = {0}", i);
// F# let mutable i = 1 while i < 10 do Console.WriteLine("i = {0}", i) i <- i + 2
出典: Loops: for...in Expression (F#)
F#ではforキーワードを使います。構文はC#のforeachとほぼ同じです。
F#ではforキーワードを使います。構文はC#のforeachとほぼ同じです。
// C# var array = new[] { "abc", "def", "ghi" }; foreach (var s in array) Console.WriteLine(s);
// F# let array = [| "abc"; "def"; "ghi" |] for s in array do Console.WriteLine(s)
breakが存在しないため途中で抜けることは出来ません。短いループではフラグを立ててスキップすれば簡単です。
// C# var array = new[] { "abc", "def", "ghi" }; foreach (var s in array) { Console.WriteLine(s); if (s == "def") break; }
// F# let array = [| "abc"; "def"; "ghi" |] let mutable f = true for s in array do if f then Console.WriteLine(s) if s = "def" then f <- falseスキップのコストが無視できないような巨大なループでは、Enumeratorを取得して回すのが比較的簡単ではないでしょうか。
// C# var array = new int[10000]; int i = 0; foreach (var a in array) { if (i == 100) break; i++; }
// F# let array = Array.zero_create<int> 10000 let mutable i = 0 let mutable f = true let e = array.GetEnumerator() while f && e.MoveNext() do let a = unbox<int> e.Current if i = 100 then f <- false else i <- i + 1これはかなり強引です。本来は処理を見直してbreakが必要ないような構造にするべきです。
F#にはreturnに相当するキーワードがありません。関数の最後の値が戻り値になります。関数の途中で抜けるreturnは、条件分岐に書き換える必要があります。
// C# if (a == 1) return 1; Console.WriteLine("not 1"); return 0;
// F# if a = 1 then 1 else Console.WriteLine("not 1") 0
出典: Match Expressions (F#)
F#ではmatch〜withキーワードを使います。C#と同じように次の条件に流れ落ちることができませんが、breakに相当するものは記述する必要がありません。構文が記号的で慣れると読みやすいです。F#のmatchはC#のswitchよりも複雑な条件が扱えますが、詳細は出典を参照してください。
F#ではmatch〜withキーワードを使います。C#と同じように次の条件に流れ落ちることができませんが、breakに相当するものは記述する必要がありません。構文が記号的で慣れると読みやすいです。F#のmatchはC#のswitchよりも複雑な条件が扱えますが、詳細は出典を参照してください。
// C# switch (s) { case "abc": case "def": Console.WriteLine("abc || def"); break; case "ghi": Console.WriteLine("ghi"); break; default: Console.WriteLine("???"); break; }
// F# match s with | "abc" | "def" -> Console.WriteLine("abc || def") | "ghi" -> Console.WriteLine("ghi") | _ -> Console.WriteLine("???")
出典: Exceptions: The try...with Expression (F#)
出典: Exceptions: The try...finally Expression (F#)
catchに相当する部分ではmatchと同じ構文でパターンマッチを行います。throwに相当するのはraise関数です。F#ではcatchとfinallyを並列に記述できないためネストさせます。
出典: Exceptions: The try...finally Expression (F#)
catchに相当する部分ではmatchと同じ構文でパターンマッチを行います。throwに相当するのはraise関数です。F#ではcatchとfinallyを並列に記述できないためネストさせます。
// C# try { throw new Exception(); } catch (FormatException e) { Console.WriteLine(e.Message); } catch (IOException e) { Console.WriteLine(e.ToString()); } catch { Console.WriteLine("???"); } finally { Console.WriteLine("finally"); }
// F# try try raise(new Exception()) with | :? FormatException as e -> Console.WriteLine(e.Message) | :? IOException as e -> Console.WriteLine(e.ToString()) | _ -> Console.WriteLine("???") finally Console.WriteLine("finally")raiseが関数のためC#のthrowより括弧が多くなっていますが、<|演算子を使えば括弧を減らせます。
// C# throw new Exception();
// F# raise <| new Exception()catchした例外を再送する場合、引数で渡すと例外発生場所が変わってしまうため、reraise()を使用します。
// C# try { throw new Exception(); } catch { throw; }
// F# try raise <| new Exception() with _ -> reraise()
最新コメント