F# と C# の記法比較

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

関数

関数

元ネタ 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; }
}