Getting a Good Grasp of F# (仮)

関数型言語F#をもっと楽しみたい

F#で集合を扱う その1

F#で数学における「集合」を扱うために、名前空間Microsoft.FSharp.Collections には Set というモジュールが用意されています。

整数0から5までの集合を作るには、

let is = Set.ofList [0..5]

このコードは、整数0から5のリスト [0; 1; 2; 3; 4; 5] Set.ofList という関数を適用して集合 is を生成しています。

F# Interactiveで実行してみると

> let is = Set.ofList [0..5];;
val is : Set<int> = set [0; 1; 2; 3; 4; 5]

Set.ofList は set で代用できるので以下のコードも同じ意味。

let is = set [0..5]
> let is = set [0..5];;
val is : Set<int> = set [0; 1; 2; 3; 4; 5]

コンソール出力ではリストとの区別は分かりにくいのですが 、F#の集合(Set)は内部的に平衡二分木によって要素を保持しています。要素の重複は許可されません。順序付けのために各要素を比較する必要があるため、この集合における要素は前提条件として、型制約があり IComparable インターフェイスを実装している必要があります。普通のプリミティブ型などは特に問題なく要素として使えます。

例えば、順序を無視して要素を与えても同じ集合を生成できます。

> let is = set [1; 0; 2; 4; 3; 5];;
val is : Set<int> = set [0; 1; 2; 3; 4; 5]

リストどうしでは要素の並び順が違えば同値とはみなしませんが、

> [0; 1; 2; 3; 4; 5] = [1; 0; 2; 4; 3; 5];;
val it : bool = false

集合どうしでは要素の与え方に関わらず同値とみなします。

> set [0; 1; 2; 3; 4; 5] = set [1; 0; 2; 4; 3; 5];;
val it : bool = true

注意:上の例では、単純にイコール記号(=)を使ってリストどうしや集合どうしの同値判定をしていますが、この場合は内部的に System.Object クラスの Equals メソッドのオーバーライドが行われていて、データ構造に配慮した適切な同値判定が行われています。

集合を定義する方法は複数あり、パイプライン演算子 |> を用いて要素を1つずつ集合に追加していくこともできます。

> let s2 = 
    set []
    |> Set.add 1
    |> Set.add 0
    |> Set.add 2
    |> Set.add 4
    |> Set.add 3
    |> Set.add 5 ;;

val s2 : Set<int> = set [0; 1; 2; 3; 4; 5]

 集合において要素の重複は許可されないので、下記コードのように同じ値を何度追加しても2回目以降は要素を追加することはできません。

> let s3 =
    set []
    |> Set.add 1    
    |> Set.add 1    
    |> Set.add 1 ;; 
 
val s3 : Set<int> = set [1]

 

F#のコードをユニットテストで検証する その4(xUnit)

前回xUnit.net を利用したユニットテストの続きです。前回とは違うやり方で、ひとつのテスト関数に複数のテストデータを渡す方法を試します。

サンプルコード その1
  1. module Sample3
  2. open Xunit
  3.  
  4. let SampleData01 : seq<obj array> =  
  5.         seq [
  6.             [| "";      0 |]
  7.             [| "ab";    2 |]
  8.             [| "ABC";   3 |]
  9.             [| "ABCD";  4 |]
  10.             [| "12345"; 5 |]
  11.         ]
  12.  
  13. [<Theory>]
  14. [<MemberData("SampleData01")>]
  15. let test01(str: string, expected: int) : unit =
  16.     Assert.Equal(str.Length, expected)

4~11行目で5個のテストデータを定義しています。

4行目の変数 SampleData01 で定義した5つのテストデータ(6~10行目)がテスト実行時に、15行目のテスト関数 test01 に渡されます。

15行目のテスト関数 test01 には Theoryアトリビュートが付いています。さらにMemberDataアトリビュートを使ってテストデータ群が格納された変数 SampleData01 の識別子名を文字列として指定して関連付けています。

4行目の変数の型注釈は以下のように書いても同じ意味です。コンパイルも通ります。

  1. let SampleData01 : System.Collections.Generic.IEnumerable<System.Object[]> =  

 

Visual Studio のテストエクスプローラーを実行してみます。

f:id:pongitsune:20160215211834p:plain

 次に、個々のテストデータの型をコンパイル時にチェックできるスタイルに書き直してみます。

サンプルコード その2
  1. module Sample4
  2. open Xunit
  3.  
  4. let SampleData02 = new TheoryData<string, int>()
  5. SampleData02.Add ("",      0)
  6. SampleData02.Add ("ab",    2)
  7. SampleData02.Add ("ABC",   3)
  8. SampleData02.Add ("ABCD",  4)
  9. SampleData02.Add ("12345", 5)
  10.  
  11. [<Theory>]
  12. [<MemberData("SampleData02")>]
  13. let test02(str: string, expected: int) : unit =
  14.     Assert.Equal(str.Length, expected)

4~9行目で5個のテストデータを定義しています。変数 SampleData02 にテストデータ群が格納されます 。5~9行目にかけて TheoryData クラスのAddメソッドを使ってテストデータを1つずつ追加しています。

4行目の TheoryData<string, int> において、test02 に渡すデータの型が string 型とint 型のペアであることを明示的に指定し、コンパイル時の型チェックが行われるようにしてあります。

テストを実行すると「サンプルコード その1」と同様の結果が得られます。

f:id:pongitsune:20160215214059p:plain

 

F#のコードをユニットテストで検証する その3(xUnit)

前回と同じく Visual Studio 2013 のテストエクスプローラーから xUnit.net を使ったユニットテストを行います。今回は1つのテスト関数に複数のテストデータを渡す方法を試します。

xUnit.net を使ったコードの例
  1. module Sample2
  2. open Xunit
  3.  
  4. let fibonacci (nth: int) : int=
  5.     let rec auxFib (acc1: int) (acc0: int) (count: int) : int =
  6.         match count with
  7.         | 0 -> acc0
  8.         | 1 -> acc1
  9.         | _ -> auxFib (acc1 + acc0) acc1 (count - 1)
  10.     auxFib 1 0 nth
  11.  
  12. [<Theory>]
  13. [<InlineData( 0,  0)>]
  14. [<InlineData( 1,  1)>]
  15. [<InlineData( 2,  1)>]
  16. [<InlineData( 3,  2)>]
  17. [<InlineData( 4,  3)>]
  18. [<InlineData( 5,  5)>]
  19. [<InlineData( 6,  8)>]
  20. [<InlineData( 7,  13)>]
  21. [<InlineData( 8,  21)>]
  22. [<InlineData( 9,  34)>]
  23. [<InlineData(10,  55)>]
  24. [<InlineData(11,  89)>]
  25. [<InlineData(12,  144)>]
  26. [<InlineData(13,  233)>]
  27. [<InlineData(14,  377)>]
  28. [<InlineData(15,  610)>]
  29. let ``Fibonacci number``(nth: int, expected: int) : unit =
  30.         let actual = fibonacci nth
  31.         Assert.Equal(expected, actual)

 4行目の fibonacci というフィボナッチ数列の第 n 項(n は0以上の整数)を求める関数をユニットテストの対象とします。

12行目 Theory アトリビュートをテスト関数 ``Fibonacci number`` (29行目から始まる)に付加します。

Theory 以降に続く13~28行目の InlineData アトリビュートの引数で指定したテストデータが ``Fibonacci number`` の引数として渡ります。InlineData からデータを渡す回数分のテストが実行されます。ここでは16個のテストデータを置いたので16回のテストが実行されることになります。

 テストデータ用のフィボナッチ数の値は、サイト「THE ONーLINE ENCYCLOPEDIA OF INTEGER SEQUENCES® 」からお借りしました。

ユニットテストの実行結果

f:id:pongitsune:20160214214938p:plain

テスト関数の定義は一つですが16回のテストが実行されていることがわかります。

F#のコードをユニットテストで検証する その2(xUnit)

xUnit.net を利用してVisual Studio 2013のテストエクスプローラーでユニットテストを実行してみます。

その準備として以下の xUnit.net および xUnit.net テストランナーの NuGet パッケージをプロジェクトにインストールします。

 NuGet パッケージ管理画面からインストール

f:id:pongitsune:20160213214520p:plain

xUnit.net を利用したコードの例
  1. module Sample1
  2. open Xunit
  3.  
  4. let countWordsInStr (str: string) : int =
  5.     let clist: char list = [ for c in str -> c ] // stringをcharの配列に変換
  6.     let rec inWord (curList: char list) (count: int) : int =
  7.         match curList with
  8.         | [(**)] -> count
  9.         | hd::tl when hd = ' ' -> inSpace tl count
  10.         |  _::tl               -> inWord  tl count
  11.     and inSpace (curList: char list) (count: int) : int=
  12.         match curList with
  13.         | [(**)] -> count
  14.         | hd::tl when hd = ' ' -> inSpace tl count
  15.         |  _::tl               -> inWord  tl (count + 1) // 空白→文字の遷移でインクリメント
  16.     inSpace clist 0
  17.      
  18. [<Fact>]
  19. let ``Word Count - Part0``() =
  20.     Assert.Equal(0,  countWordsInStr " ")
  21.  
  22. [<Fact>]
  23. let ``Word Count - Part1``() =
  24.     Assert.Equal(1,  countWordsInStr " aaa")    
  25.  
  26. [<Fact>]
  27. let ``Word Count - Part2``() =
  28.     Assert.Equal(2,  countWordsInStr "aaa bbbb")    
  29.  
  30. [<Fact>]
  31. let ``Word Count - Part3``() =
  32.     Assert.Equal(3,  countWordsInStr "aaa bbbb ccccccc ")
  33.    
  34. [<Fact>]
  35. let ``Word Count - Part4``() =
  36.     Assert.Equal(4,  countWordsInStr "a b c d")

4行目の countWordsInStr がテスト対象となる関数です。引数で与えられた文字列に含まれている単語数を数えます。ここではホワイトスペースのみを区切り文字として認識するように書いています。

6~15行目はこの関数内で使用するローカル関数です。let rec...and...の構文使って相互再帰(mutual recursion)する関数を2つ定義しています。16行目でこのローカル関数を使ってcountWordsInStr の戻り値を作り、そのまま返しています。

18行目以降の5つの関数がテスト用関数の定義になります。Factアトリビュートを付加した関数がテストランナーによって実行されます。xUnitAssert.Equal メソッドで引数に渡された二つの値が等しいかを検査しています。

注)8行目・13行目にある(**)はF#用コメントです。はてなブログでは角括弧が消えてしまうことがあるのでその対策として入れてあります。

Visual Studio2013のテストエクスプローラーの実行結果

f:id:pongitsune:20160213214246p:plain

 

F#のコードをユニットテストで検証する その1

デフォルトのVisual Studio 2013の環境で、F#のコードのユニットテストを実行してみます。以下は普通のコンソールアプリケーションのプロジェクトに含まれているコードです。
Microsoft.VisualStudio.TestTools.UnitTesting名前空間を使うので、予め Microsoft.VisualStudio.QualityTools.UnitTestFramework への参照をプロジェクトに追加しておきます。

コードの例
  1. module Test01
  2. (* 参照の追加:Microsoft.VisualStudio.QualityTools.UnitTestFramework *)
  3. open Microsoft.VisualStudio.TestTools.UnitTesting
  4.  
  5. let rangeData = [1..10]
  6.  
  7. [<TestClass>]
  8. type ``A unit testing exercise``() =
  9.     let mutable myList = []
  10.  
  11.     [<TestInitialize>]  
  12.     member this.setup(): unit =
  13.         // テストの準備
  14.         myList <- rangeData
  15.  
  16.     [<TestMethod>]
  17.     member this.body() : unit =
  18.         let sum = List.fold (+) 0 myList  // リスト要素の総和を求める
  19.         Assert.AreEqual(55 , sum)
  20.  
  21.     [<TestCleanup>]  
  22.     member this.teardown(): unit =
  23.         // テストの後始末
  24.         ()

F#ではキーワード type を使ってclass宣言を行います。8行目の``A unit testing exercise``ユニットテストに用いるクラス名です。ユニットテストで使うことを明示するために TestClassアトリビュートを付加しておきます。クラス名をバッククォート記号「`」二つずつを前後に使って囲っているのは識別子名にホワイトスペースが含まれているからです。F#ではバッククォート記号で識別子名を囲むことによって識別子に使えない文字を混ぜることができます。

12行目のTestInitializeアトリビュートの付いたメソッドsetupはテスト実行前に必要な処理を書きます。F#ではアトリビュート識別子は [< ... >] で囲みます。
メソッドシグネチャは(unit -> unit)。

17行目のTestMethodアトリビュートの付いたメソッドbodyはテストで使われる本体部分です。
メソッドシグネチャは(unit -> unit)。ここではリストの要素(1から10までの整数)の総和を求めて55に等しいかを Assert.AreEqual メソッドを使って検査しています。

22行目のTestCleanupアトリビュートの付いたメソッドteardownはテスト完了後に必要な後処理を書きます。この例では特に何もしないので()を返しています。
メソッドシグネチャは(unit -> unit)。

テストを実行するために、メニューバーから「テスト」→「ウィンドウ」→「テストエクスプローラー」を選択してテスト画面を開きます。

f:id:pongitsune:20160210204115j:plain

テストクスプローラー上で「すべて実行」をクリックするとソースコードはビルドされユニットテストが実行されて結果が表示されます。以下はこのテストが成功した場面。

f:id:pongitsune:20160210204322j:plain

ここで先ほどのコード19行目の Assert.AreEqual を Assert.AreNotEqual に書き変えてわざとテストを失敗させてみます。再度テストを実行すると以下のような結果が得られます。

f:id:pongitsune:20160210204655j:plain