F# と C# の記法比較

MSDN F# リファレンスを使い C# と記法を比較する

do 束縛

let 束縛

do 束縛 do Bindings

元ネタ http://msdn.microsoft.com/en-us/library/dd393786.aspx

関数または値の定義を使用しないでコードを実行する場合は、do 束縛を使用します。 do 束縛内の式は、unit を返す必要があります。 最上位の do 束縛内のコードは、モジュールが初期化されるときに実行されます。 do キーワードは省略可能です。

F#

open System
open System.Windows.Forms

let form1 = new Form()
form1.Text <- "XYZ"

[<STAThread>]
do
   Application.Run(form1)

C# の場合、グローバルな関数は作れないので必ずクラスのメソッドになる。static class にすればよい。

C#

using System;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            var form1 = new Form();
            form1.Text = "XYZ";
            Application.Run(form1);
        }
    }
}

let 束縛

let 束縛 let Bindings

元ネタ http://msdn.microsoft.com/ja-jp/library/dd233238.aspx

1つの名前を 1 つの単純な値にバインドする式です。

F#

let i = 1

バインド(束縛)を強調すれば、const を付けることになる。

C#

const int i = 1;

式の各行にインデントを設定する必要があります。 F#

let someVeryLongIdentifier =
    // Note indentation below.
    3 * 4 + 5 * 6

C# の場合は、インデントはいらないが、見やすくするために付ける。

C#

let someVeryLongIdentifier =
    3 * 4 + 5 * 6;

次のコードに示すように、タプルを指定できます。

F#

let i, j, k = (1, 2, 3)

「バインドをタプルでできる」というよりも、タプルを返す関数から値を取り出す方法と言える。 変数を複数同時に宣言する場合は、こんな感じになる。

C#

const int i = 1, j = 2, k = 3;

タプルから取り出す場合は、こんな感じ。 C#

var t = new Tuple<int, int, int>(1, 2, 3);
int i = t.Item1;
int j = t.Item2;
int k = t.Item3;

let キーワードの先頭の文字と同じ位置になるようにインデントを設定します。

F#

let result =
    let i, j, k = (1, 2, 3)
    // Body expression: 
    i + 2*j + 3*k

result への束縛のニュアンスが違うが、普通に値を計算する。

C#

const int i = 1, j = 2, k = 3;
const int result = i + 2 * j + 3 * k;

関数の束縛

関数の束縛には関数名とパラメーターが含まれます。

F#

let function1 a =
    a + 1

C# の場合、関数と値は区別されるが、普通に関数を書くとこんな感じ。

C#

int function1(int a)
{
    return a + 1;
}

通常、パラメーターは、タプル パターンなどのパターンです。

F#

let function2 (a, b) = a + b

タプルはひとつの値なので、空白で並べるパターンとは異なる(カリー化になる)。 C# で定義された関数を F# から呼び出すときは、このタプルを使う。内部的にタプルから複数のパラメーターに直している、と思われる。

C#

int function2(int a, int b)
{
    return a + b;
}

ただし、先の function2 を正確に表すにはジェネリックを使うことになる。が、そのままでは + 演算子が使えないので適宜インターフェースを作る必要あり?

C#

class LetBindings<T> 
{
    T function2(T a, T b)
    {
        return a + b; // これはできない
    }
}

let 束縛式の評価は、最後の式の値になります。

F#

let result =
    let function3 (a, b) = a + b
    100 * function3 (1, 2)

C# の場合は return をつける

C#

int function3(int a, int b)
{
    return a + b;
}
int test()
{
    return 100 * function3(1, 2);
}

ラムダ式の場合は、関数型表記になるので最後のreturnは省略できる。

C#

void test2()
{
    Func<int, int, int> function3 = (a, b) => a + b;
    int result = function3(1, 2);
}

型の注釈

パラメーターの型を整数型にした、function1 の完全な型の注釈は次のようになります。

F#

let function1 (a: int) : int = a + 1

F# の場合は型を指定するのがデフォルトだが、C# の場合は型指定をするのがデフォルト。

C#

int function1( int a ) { return a + 1; }

クラス内の let 束縛

プライベート フィールドとして field1 と field2 を定義する MyClass クラスのコード例を次に示します。

F#

type MyClass(a) =
    let field1 = a
    let field2 = "text"
    do printfn "%d %s" field1 field2
    member this.F input =
        printfn "Field1 %d Field2 %s Input %A" field1 field2 input

クラスの中で do を使うとコントラクタで動く。このあたりは、C# のほうが整然としているかも。

C#

public class MyClass
{
    private int field1;
    private string fiedl2 = "text";
    public MyClass(int a)
    {
        this.field1 = a;
        Console.WriteLine("{0} {1}", this.field1, this.fiedl2);
    }
    void F(string input)
    {
        Console.WriteLine("Field1 {0} Field2 {1} Input {2}", 
            this.field1, this.fiedl2, input );
    }
}

let 束縛の型パラメーター

モジュール レベル、型、またはコンピュテーション式の let 束縛では、明示的な型パラメーターを使用できます。 関数の定義内など、式の let 束縛では、型パラメーターを使用できません。

C# だとどうなのか?は分からず。

let 束縛に対する属性

属性はモジュール内の最上位の let 束縛に適用できます。

F#

[<Obsolete>]
let function1 x y = x + y

属性の書き方は同じ

C#

[Obsolete]
int function1( int x, int y ) { return x + y ; }

スコープおよびユーザーはバインディングを可能にする

モジュールのユーザーはこの場合、開いている Module1 インポート宣言を使用して開き、モジュールを、その直接参照します function1 をできます。

F#

module Module1 =
    let function1 x = x + 1.0

module Module2 =
    let function2 x =
        Module1.function1 x

    open Module1
    let function3 x =
        function1 x

C# の場合、namespace を使ってもいいが、static method を使ったほうがそれらしい。

C#

public class Module1
{
    public static double function1(double x) { return x + 1.0; }
}
public class Module2
{
    public static double function2(double x)
    {
        return Module1.function1(x);
    }
}

関数

関数

元ネタ http://msdn.microsoft.com/ja-jp/library/dd233229.aspx

単純な関数定義の例を以下に示します。

単純な関数

F#

let f x = x + 1

C#

int f( int x ) { return x+1; }

スコープ

たとえば次のコードは、モジュール スコープではエラーになりますが、関数内ではエラーになりません。

F#

let list1 = [ 1; 2; 3]
// Error: duplicate definition.
let list1 = []  
let function1 =
   let list1 = [1; 2; 3]
   let list1 = []
   list1

スコープという点では、少し違うが C# だとこんな感じ

C#

static void Main(string[] args)
{
    var list1 = new List<int> { 1, 2, 3 };
    // Error: duplicate definition
    var list1 = new List<int>();
}
List<int> function1()
{
    var list1 = new List<int> { 1, 2, 3 };
    // Error: duplicate definition
    var list1 = new List<int>();
    return list1;
}

これに対して、次のコードは、どのレベルのスコープでも許容されます。

F#

let list1 = [ 1; 2; 3]
let sumPlus x =
// OK: inner list1 hides the outer list1.
   let list1 = [1; 5; 10]  
   x + List.sum list1```

似た方法としては、ラムダ式があるけど、ラムダ式内ではエラーになる。

C#

static void Main(string[] args)
{
    var list1 = new List<int> { 1, 2, 3 };
    Func<int,int> sumPlus = x =>
        {
            var list1 = new List<int> { 1, 5, 10 };
            // ラムダ式内はエラーになる
            return x + list1.Sum();2014/05/27/関数
        };
}

パラメータ

パラメーターの名前は関数名の後に指定します。 次の例のように、パラメーターの型を指定できます。

F#

let f (x : int) = x + 1

次の関数定義では、1 の型が int であるため、引数 x の型は int と推論されます。

F#

let f x = x + 1

C# の場合は、引数の推論をしないので両方とも同じ

C#

int f( int x ) { return x + 1 ; }

関数を汎用的にしようとします。この関数は、任意の型の 1 つの引数からタプルを作成します。

F#

let f x = (x, x)

C# の場合はジェネリックを使う。

C#

class MakeTuple<T>
{
    public static Tuple<T, T> f(T x) 
    { 
        return new Tuple<T, T>(x, x); 
    }
}

関数本体

関数本体に含まれていることをインデントによって示す必要があります。

F#

let cylinderVolume radius length =
    // Define a local value pi.
    let pi = 3.14159
    length * pi * radius * radius

ローカル変数の使い方は同じ。スコープは {,} で表す。

double cylinderVolume(double radius, double length)
{
    // Define a local value pi.
    double pi = 3.14159;
    return length * pi * radius * radius;
}

戻り値

戻り値を明示的に指定するには、次のようなコードを使用します。

F#

let cylinderVolume radius length : float =
   // Define a local value pi.
   let pi = 3.14159
   length * pi * radius * radius

パラメーターの型にもこの型を適用するには、次のコードを使用します。

F#

let cylinderVolume (radius : float) (length : float) : float

C# の場合は型指定を省略しないので、同じ書き方になる。

double cylinderVolume(double radius, double length)
{
    // Define a local value pi.
    double pi = 3.14159;
    return length * pi * radius * radius;
}

関数の呼び出し

各引数はスペースで区切ります。

F#

let vol = cylinderVolume 2.0 3.0

C# の場合、戻り値は型指定をしてもよいし、型推論して var で受けてもよい。

C#

var vol = cylinderVolume( 2.0, 3.0 );
or
double vol = cylinderVolume( 2.0, 3.0 );

引数の部分適用

次のようにして、パイプの体積を特定する関数を作成することができます。

F#

let smallPipeRadius = 2.0
let bigPipeRadius = 3.0

// These define functions that take the length as a remaining
// argument:

let smallPipeVolume = cylinderVolume smallPipeRadius
let bigPipeVolume = cylinderVolume bigPipeRadius

F#

let length1 = 30.0
let length2 = 40.0
let smallPipeVol1 = smallPipeVolume length1
let smallPipeVol2 = smallPipeVolume length2
let bigPipeVol1 = bigPipeVolume length1
let bigPipeVol2 = bigPipeVolume length2

radius だけ指定して、length を指定しないので、smallPipeVolume と bigPipeVolume は関数になる。 これを無理やり C# で書くと、ラムダ式を使ってこんな感じで書ける。

C#

double smallPipeRadius = 2.0;
double bigPipeRadius = 3.0;

Func<double, double> smallPipeVolume = length => cylinderVolume(smallPipeRadius, length);
Func<double, double> bigPipeVolume = length => cylinderVolume(bigPipeRadius, length);

C#

double length1 = 30.0;
double length2 = 40.0;
double smallPipeVol1 = smallPipeVolume( length1 );
double smallPipeVol2 = smallPipeVolume( length2 );
double bigPipeVol1 = bigPipeVolume( length1 );
double bigPipeVol2 = bigPipeVolume( length2 );

再帰関数

フィボナッチ数列は、古代から知られている数列で、数例の各数値が、前の 2 つの数値の和になります。

F#

let rec fib n = if n < 2 then 1 else fib (n - 1) + fib (n - 2)

C# のフィボナッチ数列

C#

int fib(int n)
{
    if (n < 2)
        return 1;
    else
        return fib(n - 1) + fib(n - 2);
}

関数値

関数値を引数として受け取る関数の例を次に示します。

F#

let apply1 (transform : int -> int ) y = transform y

F#

let increment x = x + 1
let result1 = apply1 increment 100

F# の場合は、関数値(function values)と呼ばれるが、C# の場合はラムダ式を渡すことになる。

C#

int apply1(Func<int, int> transform, int y)
{
    return transform(y);
}

メソッドを定義する場合

C#

int increment(int x)
{
    return x + 1;
}
void test()
{
    int result = apply1( increment, 100 );
}

ラムダ式を渡す場合

C#

int res = apply1(x => { return x + 1; }, 100);

ラムダ式に渡す x の型は推論してくれるので、書き方が F# に似る。

複数の引数を指定するには、次のように、連続する -> トークンで区切ります。

F#

let apply2 ( f: int -> int -> int) x y = f x y

let mul x y = x * y

let result2 = apply2 mul 10 20

これは C# も同じ。引数の数は増やすことができる。

C#

void test()
{
    int result2 = apply2( mul, 10, 20 );
}
int apply2(Func<int, int, int> f, int x, int y)
{
    return f(x, y);
}
int mul(int x, int y)
{
    return x * y;
}

ラムダ式

次のように、代わりにラムダ式を使用することもできます。

F#

let result3 = apply1 (fun x -> x + 1) 100

let result4 = apply2 (fun x y -> x * y ) 10 20

C# でラムダ式を使う。引数が複数ある場合は(,)で囲む

C#

int result3 = apply1( x => { return x + 1; }, 100 );

int result4 = apply2((x, y) => { return x * y; }, 10, 20);

関数合成とパイプライン処理

function1 と function2 という 2 つの関数を合成すると、function1 に続いて function2 を適用する別の関数になります。

F#

let function1 x = x + 1
let function2 x = x * 2
let h = function1 >> function2
let result5 = h 100

C# の場合は、ラムダ式を使ってあらかじめ合成しておく。

C#

void test5()
{
    Func<int, int> h = x => function2(function1(x));
    int result5 = h(100);
}
int function1(int x) { return x + 1; }
int function2(int x) { return x * 2; }

コンポジション(関数合成)だけ括りだして、次の書き方もできる

C#

Func<int, int> Composition(Func<int, int> func1, Func<int, int> func2)
{
    return x => func2(func1(x));
}


Func<int, int> h2 = Composition(function1, function2);

パイプライン処理を使用すると、複数の関数呼び出しを一連の操作として連結することができます。

F#

let result = 100 |> function1 |> function2

パイプライン処理は、呼び出し順に呼ぶけなので、普通に関数呼び出しと同じ。

C#

int result = function2(function1(100));

ただし、呼び出し順序が逆になるので、適当なメソッドチェーンを作って呼び出し順序を揃える。

C#

public static class IntExtentions
{
    public static int function1(this int x) { return x + 1; }
    public static int function2(this int x) { return x * 2; }
}

void test6()
{
    int result = ((int)100)
        .function1()
        .function2();
}

コンポジションの演算子は 2 個の関数を受け取り、関数を返します; 一方、パイプライン演算子は、関数引数と引数を受け取り、値を返します。 次のコード例は、関数の定義と使用の違いを参照することによって、パイプラインとコンポジションの演算子の違いを示します。

F#

// Function composition and pipeline operators compared.

let addOne x = x + 1
let timesTwo x = 2 * x

// Composition operator
// ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3
let Compose2 = addOne >> timesTwo

// Backward composition operator
// ( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3
let Compose1 = addOne << timesTwo

// Result is 5
let result1 = Compose1 2

// Result is 6
let result2 = Compose2 2

// Pipelining
// Pipeline operator
// ( <| ) : ('T -> 'U) -> 'T -> 'U
let Pipeline1 x = addOne <| timesTwo x

// Backward pipeline operator
// ( |> ) : 'T1 -> ('T1 -> 'U) -> 'U
let Pipeline2 x = addOne x |> timesTwo

// Result is 5
let result3 = Pipeline1 2

// Result is 6
let result4 = Pipeline2 2

C# の場合は、ラムダ式とメソッドチェーンで表してみる。

C#

void test7()
{
    // Function composition and pipeline operators compared.
    Func<int, int> addOne = x => x + 1;
    Func<int, int> timesTwo = x => 2 * x;
    // Composition operator
    // ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 ->Z 'T3
    Func<int,int> Compose2 = x => timesTwo(addOne(x));
    // Backward composition operator
    // ( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3
    Func<int, int> Compose1 = x => addOne(timesTwo(x));

    // Result is 5
    int result1 = Compose1(2);
    // Result is 6
    int result2 = Compose2(2);

    // Pipelining
    // Pipeline operator
    // ( <| ) : ('T -> 'U) -> 'T -> 'U
    // let Pipeline1 x = addOne <| timesTwo x
    int result3 = ((int)2).tiemsTwo().addOne();
    // Backward pipeline operator
    // ( |> ) : 'T1 -> ('T1 -> 'U) -> 'U
    // let Pipeline2 x = addOne x |> timesTwo
    int reulst4 = ((int)2).addOne().tiemsTwo();
}

public static class IntExtentions
{
    public static int addOne(this int x) { return x + 1; }
    public static int tiemsTwo(this int x) { return x * 2; }
}