null 値
null 値 Null Values
元ネタ http://msdn.microsoft.com/ja-jp/library/dd233197.aspx
.NET のメソッドに null 値を渡すには、呼び出し元のコードで null キーワードを使用します。
F#
open System // Pass a null value to a .NET method. let ParseDateTime (str: string) = let (success, res) = DateTime.TryParse(str, null, System.Globalization.DateTimeStyles.AssumeUniversal) if success then Some(res) else None
TryParse メソッドは、パースを行って成功/失敗を bool 値を返すと同時に、成功した場合は out で値を返す。 C# の場合は、ParseDateTime メソッドで戻り値を DateTime? にして、失敗の場合は null を返すようにする
C#
public DateTime? ParseDateTime(string str) { DateTime res ; if (DateTime.TryParse(str, null, System.Globalization.DateTimeStyles.AssumeUniversal, out res)) return res; else return null; }
.NET のメソッドから取得した null 値を解釈するには、可能であればパターン マッチを使用します。 入力ストリームの末尾を超えて読み取ろうとしたときに ReadLine から返される null 値を、パターン マッチを使用して解釈する方法を次のコード例に示します。
F#
// Open a file and create a stream reader. let fileStream1 = try System.IO.File.OpenRead("TextFile1.txt") with | :? System.IO.FileNotFoundException -> printfn "Error: TextFile1.txt not found."; exit(1) let streamReader = new System.IO.StreamReader(fileStream1) // ProcessNextLine returns false when there is no more input; // it returns true when there is more input. let ProcessNextLine nextLine = match nextLine with | null -> false | inputString -> match ParseDateTime inputString with | Some(date) -> printfn "%s" (date.ToLocalTime().ToString()) | None -> printfn "Failed to parse the input." true // A null value returned from .NET method ReadLine when there is // no more input. while ProcessNextLine (streamReader.ReadLine()) do ()
F# の場合は一時的な関数をその場で書けるが、C# の場合はメソッド化しないといけない。
C#
public void test() { System.IO.FileStream fileStream1 = null; try { fileStream1 = System.IO.File.OpenRead("TextFile1.txt"); } catch ( System.IO.FileNotFoundException ex ) { Console.WriteLine("Error: TextFile1.txt not found."); return ; } var streamReader = new System.IO.StreamReader(fileStream1); // A null value returned from .NET method ReadLine when there is // no more input. while (ProcessNextLine(streamReader.ReadLine())) ; } bool ProcessNextLine(string nextLine) { switch (nextLine) { case null: return false; default: var date = ParseDateTime(nextLine); if (date != null) { Console.WriteLine("{0}", date.Value.ToLocalTime().ToString()); } else { Console.WriteLine("Failed to parse the input."); } return true; } }
ただし、必ずメソッド化しないと駄目というわけでもなく、ラムダ式を使うと似た感じで書ける。
C#
public void test2() { System.IO.FileStream fileStream1 = null; try { fileStream1 = System.IO.File.OpenRead("TextFile1.txt"); } catch (System.IO.FileNotFoundException ex) { Console.WriteLine("Error: TextFile1.txt not found."); return; } var streamReader = new System.IO.StreamReader(fileStream1); Func<string, bool> ProcessNextLine = nextLine => { switch (nextLine) { case null: return false; default: var date = ParseDateTime(nextLine); if (date != null) { Console.WriteLine("{0}", date.Value.ToLocalTime().ToString()); } else { Console.WriteLine("Failed to parse the input."); } return true; } }; // A null value returned from .NET method ReadLine when there is // no more input. while (ProcessNextLine(streamReader.ReadLine())) ; }
F# の型に対する null 値は、Unchecked.defaultof を呼び出す Array.zeroCreate を使用する場合など、別の方法でも生成されることがあります。
F#
match box value with | null -> printf "The value is null." | _ -> printf "The value is not null."
box を使う必要は C# ではないと思われる。
値
値 Values
元ネタ http://msdn.microsoft.com/ja-jp/library/dd233185.aspx
値の束縛
束縛とは、名前と定義を関連付けることを意味します。 let キーワードは、次の例のように、値のバインディングを行います。
F#
let a = 1 let b = 100u let str = "text" // A function value binding. let f x = x + 1
正確には const をつけて変更できないようにするのだが、コード的には変更しなければ良い。
C#
const int a = 1; const char b = (char)100; const string str = "text"; // A function value binding. Func<int, int> f = x => x + 1;
変更不可である理由
純粋な関数型言語では、変数は存在せず、関数は数学関数として厳密に機能します。 手順型言語のコードでは変数割り当てを使用して値を変更しますが、関数型言語の等価なコードには、入力される変更不可の値、変更不可の関数、および出力される別の変更不可の値があります。 この数学的な厳密性により、プログラムの動作についての強力な推論が実現します。 この強力な推論により、コンパイラでコードをより厳密にチェックし、より効率的に最適化を行い、開発者が正しいコードを容易に理解および記述できるようになります。 したがって、関数型コードは、通常の手順型コードよりもデバッグが容易になるのが普通です。
要は、変更しないことを優先することによって、変更しているかもしれないリスク(変数の汚染)を減してデバッグを容易にする、ってこと。また、型推論を優先させるためには、変更しないことを優先するほうがよい。
変更可能な理由
変更可能な変数に初期値を割り当てるには、値を定義するときと同じ方法で、let キーワードを使用します。 ただし、次の例に示すように、<- 演算子を使用して、変更可能な変数の後に新しい値を割り当てることができます。
F#
let mutable x = 1 x <- x + 1
C# の場合は、変数>値になるので、数の変更は楽。
C#
int x = 1 ; x = x + 1 ;
インライン関数
インライン関数 Inline Functions
元ネタ http://msdn.microsoft.com/ja-jp/library/dd548047.aspx
inline 修飾子は、最上位の関数、モジュール レベルの関数、またはクラスのメソッド レベルの関数に適用できます。
最上位のインライン関数、インラインのインスタンス メソッド、およびインラインの静的メソッドを次のコード例に示します。
F#
let inline increment x = x + 1 type WrapInt32() = member inline this.incrementByOne(x) = x + 1 static member inline Increment(x) = x + 1
C# にはインラインがない。JIT で最適化するからだそうだ。
Why doesn't C# have an 'inline' keyword? - Eric Gunnerson's Compendium - Site Home - MSDN Blogs http://blogs.msdn.com/b/ericgu/archive/2004/01/29/64644.aspx
外部関数
外部関数 External Functions
元ネタ http://msdn.microsoft.com/ja-jp/library/dd393785.aspx
次のエクスポート関数を含むネイティブ C++ DLL があると仮定します。
#include <stdio.h> extern "C" void __declspec(dllexport) HelloWorld() { printf("Hello world, invoked by F#!\n"); }
この関数は、次のコードを使用して F# から呼び出すことができます。
F#
open System.Runtime.InteropServices module InteropWithNative = [<DllImport(@"C:\bin\nativedll", CallingConvention = CallingConvention.Cdecl)>] extern void HelloWorld() InteropWithNative.HelloWorld()
書き方は C# でも F# でも同じ。
C#
using System.Runtime.InteropServices; class InteropWithNative { [DllImport(@"C:\bin\nativedll", CallingConvention = CallingConvention.Cdecl)] public static extern void HelloWorld(); } class Program() { void Main() { InteropWithNative.HelloWorld(); } }
エントリ ポイント
エントリ ポイント Entry Point
元ネタ http://msdn.microsoft.com/ja-jp/library/dd402151.aspx
簡単な main 関数の例を次に示します。
[<EntryPoint>] let main args = printfn "Arguments passed to function : %A" args // Return 0. This indicates success. 0
C# の場合は、こんな感じ。Main 関数が呼び出される。
C#
class Program { static void Main(string[] args) { Console.WriteLine( "Arguments passed to function : {0}", args ); }
再帰関数: rec キーワード
再帰関数: rec キーワード Recursive Functions: The rec Keyword
元ネタ http://msdn.microsoft.com/ja-jp/library/dd233232.aspx
rec キーワードは、再帰関数を定義するときに let キーワードと一緒に使用します。
n 番目のフィボナッチ数を計算する再帰関数を次のコードに示します。
F#
let rec fib n = if n <= 2 then 1 else fib (n - 1) + fib (n - 2)
「メソッドは型の中では暗黙的に再帰的になります。rec キーワードを追加する必要はありません。 クラス内の Let 束縛は、暗黙的に再帰的ではありません。」ということなので、let する時だけ rec が必要になる。
C# の場合は、そのまま。
C#
public int fib(int n) { if (n <= 2) return 1; else return fib(n - 1) + fib(n - 2); }
相互再帰関数
このような関数は、and を使用して 1 つの let 束縛で一緒に定義し、相互にリンクする必要があります。
F#
let rec Even x = if x = 0 then true else Odd (x - 1) and Odd x = if x = 1 then true else Even (x - 1)
C# の場合は、そのまま。
F#
public bool Even(int x) { if (x == 0) return true; else return Odd(x - 1); } public bool Odd(int x) { if (x == 1) return true; else return Even(x - 1); }
ラムダ式: fun キーワード
ラムダ式: fun キーワード Lambda Expressions: The fun Keyword
元ネタ http://msdn.microsoft.com/ja-jp/library/dd233201.aspx
ラムダ式、つまり匿名関数を定義するには、fun キーワードを使用します。
F#
// Lambda expressions with parameter lists. fun a b c -> ... fun (a: int) b c -> ... fun (a : int) (b : string) (c:float) -> ... // A lambda expression with a tuple pattern. fun (a, b) -> … // A lambda expression with a list pattern. fun head :: tail -> …
C# でも書き方は似ている。fun キーワードはいらない。
C#
// parameter lists and tuple pattern Func<int, string, float, int> f = (a, b, c) => 0; // list pattern はこんな感じ? Func<int, List<int>, int> f2 = (head, tail) => head + tail.Sum();
list pattern の場合は、head と tail を取り出せば良いので、こんな感じか?
expression は関数の本体であり、その最後の式によって戻り値が生成されます。 有効なラムダ式の例は次のとおりです。
F#
fun x -> x + 1 fun a b c -> printfn "%A %A %A" a b c fun (a: int) (b: int) (c: int) -> a + b * c fun x y -> let swap (a, b) = (b, a) in swap (x, y)
最後の swap は無理矢理 F# と同じように書くとこんな風になる。
C#
Func<int, int> f = x => x + 1; Func<object, object, object, object> f2 = (a, b, c) => { Console.WriteLine("{0} {1} {2}", a, b, c); return null; }; Func<int, int, int, int> f3 = (a, b, c) => a + b * c; Func<object, object, object> f4 = (x, y) => { Func<Tuple<object, object>, Tuple<object, object>> swap = (t) => { return new Tuple<object, object>(t.Item2, t.Item1); }; return swap(new Tuple<object, object>(x, y)); };
ただし、内部で swap 関数を作る必要がなければ、Tuple を返すだけでよい。
C#
Func<object,object,Tuple<object,object>> f5 = (a,b) => new Tuple<object,object>( b, a);
ラムダ式の使用
この場合は、匿名関数が、リストのすべての要素に 1 を追加します。
F#
let list = List.map (fun i -> i + 1) [1;2;3] printfn "%A" list
C# には map 関数がないので自作するのだが、次のように拡張メソッドで作れる。ただし、拡張メソッドでは T が使えないので int 型限定。C# 6.0 ではジェネリックが使える予定なハズ。
C#
void test3() { var lst = new List<int> { 1, 2, 3 }; var dest = lst.Map(x => x + 1); } public static class ListExtensions { public static List<int> Map( this List<int> src, Func<int,int> fn) { var dest = new List<int>(); foreach (var it in src) { dest.Add(fn(it)); } return dest; } }
適用関数とリストの順序を守って、ListEx という新しいクラスを作ると T で書ける。
C#
void test4() { var lst = new List<int> { 1, 2, 3 }; var dest = ListEx<int>.Map(x => x + 1, lst); } public class ListEx<T> { public static List<T> Map(Func<T, T> fn, List<T> src) { var dest = new List<T>(); foreach (T it in src) { dest.Add(fn(it)); } return dest; } }