プログラミングについてのWiki。

このテキストはpublic domainです。出典を明記せずに転載していただいても構いません。編集は自由に行っていただいても結構ですが、このことに同意した上でお願いいたします。

C#経験者が手っ取り早くF#を使おうとすると、クラス定義の文法がかなり違うことに戸惑うと思います。そこでクラス定義を中心にC#からF#への変換方法をまとめました。なお、F#からC#への変換は逆コンパイラを使えば可能です。

可能な限り出典としてMSDNのリファレンスを示しました。詳細はそちらを参照してください。
出典: Keyword Reference (F#)

F#の例はすべてlight書式(Pythonのようにインデントでブロックを示す方式)です。



クラス

出典: Classes (F#)
F#ではデフォルトがpublicのため、C#の例にはpublicを付けています。詳細はアクセス制御の項目を参照してください。

コンストラクタ

出典: コンストラクター (F#)
コンストラクタにはプライマリコンストラクタと追加のコンストラクタの二種類があります。

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)

this

F#にはthisキーワードがありません。自分自身を参照するには指定する必要があります。メソッドやプロパティでの使い方はそれぞれの項で取り上げるため、ここではコンストラクタだけを取り上げます。
コンストラクタからの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#ではクラス名の前に書きます。
// 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() = {}

staticクラス

F#でコンストラクタを省略するとstaticクラスになります。インスタンスを作成することはできません。
// C#
public static class Test { }
// F#
type Test = class end
staticコンストラクタ
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に引数を付けてベースクラスのコンストラクタを呼び出します。二番目以降のコンストラクタからは必ず暗黙的なコンストラクタを呼び出さないといけないため、ベースクラスの別のコンストラクタを呼ぶことはできません。
// 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)

base

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とvalの二種類があり、アクセス制御なども絡みますが、基本はletだと考えられます。
privateフィールド (let)
出典: let Bindings in Classes (F#)
// C#
public class Test
{
    private int a, b;
}
// F#
type Test() =
    let mutable a = 0
    let mutable b = 0
letはprivate固定です。letを定義するときには、必ず暗黙的なコンストラクタを指定する必要があります。

F#ではデフォルトが定数(C#のconst)のため、値を変更できるようにするのがmutable指定です。必ず初期値を指定する必要があるため、0を代入しています。0がintであることから、自動的にaとbはintであると型推論されます。C#ではローカル変数しか型推論できませんが、F#ではフィールドでも型推論できます。
private constフィールド
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
publicフィールド
出典: Explicit Fields The val Keyword (F#)
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で初期化
上で作成したクラスをnewで初期化することができます。
// 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 <- 6
F#の<-は代入です。初期化のときだけ=で、その後の変更は<-です。初期化以外で=を使うと比較になります。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 == 0a = 0
privateフィールド (val)
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の使い分け
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
プロパティの詳細は後述します。
public constフィールド
// 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 + b
thisの項目で述べたように、thisはキーワードではなく任意の名前が付けられます。サンプルではxを使うことが多いようです。
abstractメソッド
メソッド定義とは書式が異なります。書式は「引数の型->戻り値の型」です。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
virtualメソッド
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メソッド
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
引数にoutを含むメソッド
出典: Tuples (F#)
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() |> ignore
C言語で(void)として戻り値を捨てることに相当します。
// C
int Foo() { return 1; }
(void)Foo();

プロパティ

出典: Properties (F#)
アクセス制御や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
abstractプロパティ
メソッドと違い引数が存在しません。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
virtualプロパティ
メソッド同様、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 <- value
letはメンバ定義の前に置かないとエラーになります。
overrideプロパティ
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が使われません。
// 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 $ 100
F#には逆向きの|>演算子もあります。
// F#
100 |> baz |> bar |> foo
関数を合成する>>演算子もありますが、演算子の結合順位の関係上、括弧で囲みます。
// F#
100 |> (baz >> bar >> foo)

abstractクラス

出典: Abstract Classes (F#)
属性[<AbstractClass>]で指定します。
//C#
public abstract class Test
{
    public abstract void Foo();
}
//F#
[<AbstractClass>]
type Test() =
    abstract Foo: unit -> unit

sealedクラス

属性[<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で定義します。

クラスと異なる点は以下の通りです。
  • 暗黙的にコンストラクタが作成されます。
  • 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メンバだけを定義した型は自動的にインターフェースとなります。
// 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() :> IEnumerator
F#では戻り値で自動的にアップキャストが行われないため、明示的にIEnumeratorにキャストしています。詳細はキャストの項目を参照してください。

delegate

出典: 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)

F#ネイティブの関数型

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

出典: Null Values (F#)
F#で定義した型はそのままではnullとして扱えません。決まった手順が必要です。

nullの代入

nullはキーワードとして存在しますが、そのままでは代入できません。Unchecked.defaultofを使用します。
// C#
Test t = null;
// F#
let t = Unchecked.defaultof<Test>

関数でのnull

戻り値はUnchecked.defaultofを使用します。引数にはnullを渡せます。
// C#
Test Foo(Test a) { return null; }
Foo(null);
// F#
let Foo(a:Test) = Unchecked.defaultof<Test>
Foo(null) |> ignore
F#では戻り値を使わないため明示的に無視しています。

null判定

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 // コンパイルを通す

is

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化しなければエラー

as

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 |> int
obj型からのキャストは次のunbox化の項目を参照してください。

box化/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[])
// 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などが存在しないのは、関数型言語として制御構造は再帰を使って表現することが望ましいためです。詳細は専門の書籍などを参照してください。

if

出典: Conditional Expressions: if... then...else (F#)
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のため、型の不一致でエラーになります。そのため前者を明示的に無視します。
// 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")

while

出典: Loops: while...do Expression (F#)
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
break
該当するキーワードが存在しません。手っ取り早く同じ動作をさせるにはフラグを立てます。
// 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
continue
該当するキーワードが存在しません。条件分岐で表現します。
// 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)

do〜while

該当する構文がありません。フラグを使って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 + 1
breakやcontinueはwhileと同様に工夫が必要です。

for

出典: Loops: for...to Expression (F#)
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

foreach

出典: Loops: for...in Expression (F#)
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
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が必要ないような構造にするべきです。

return

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

switch

出典: Match Expressions (F#)
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を並列に記述できないためネストさせます。
// 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()
×

この広告は60日間更新がないwikiに表示されております。

Wiki内検索

Menu

ここは自由に編集できるエリアです。

管理人のみ編集できます