F# における interface 宣言と実装、そしてオブジェクト式
C#には、インタフェースを宣言するためのキーワード interface が用意されていますが、 F# でのインタフェース宣言は( クラス宣言と同じく)キーワード type を用います。
C# の宣言例
public interface IGreeting { void Say(); }
F# の宣言例
type IGreeting = abstract Say: unit -> unit
これらはどちらも宣言内容は同じになります。F# のインタフェース宣言ではメソッドに abstract 指定が必須になります。またアクセス修飾子(Access Modifiers)を省略するとF#では public に設定されます。
ちなみに上記の F# コードにおける unit
型とは C# における void と同様に「値を何も返さない」ことを表しており、F# コード中では () と記述します。また unit
型は「引数を何も受け取らない」ことも表すので、引数のない関数・メソッドあるいは引数のないコンストラクタの記述にも使われます。
インタフェースの実装例
上記のF# コードのインタフェース IGreeting の宣言に続けて以下のコードで実装を試みます。
type MorningGreeting() = interface IGreeting with override __.Say() = printfn "Good morning !" type EveningGreeting() = interface IGreeting with override __.Say() = printfn "Good evening !" let morning1 = new MorningGreeting() let evening1 = new EveningGreeting() // 実装したメソッドを呼び出すには明示的なキャストが必要(方法その1) (morning1 :> IGreeting).Say() (evening1 :> IGreeting).Say()
インタフェース IGreeting のメソッドを具体的に実装した MorningGreeting クラスおよび EveningGreeting クラスを新規に宣言し、メソッド Say() を呼び出しています。
実行結果
Good morning !
Good evening !
キャストのやり方の別解
// 実装したメソッドを呼び出すには明示的なキャストが必要(方法その2) let morning2 = new MorningGreeting() :> IGreeting let evening2 = new EveningGreeting() :> IGreeting morning2.Say() evening2.Say()
F# ではこのようにインタフェースを実装したクラスのメソッドを呼び出すために、インタフェース型(この例では IGreeting )への明示的なキャストが必要になります。
このコード内の :>
記号はキャスト演算子であり、安全に型変換できるか否かはコンパイラがチェックしてくれます。
オブジェクト式の利用
F# には、インタフェース(あるいは抽象クラス)を実装したクラスのインスタンスをクラス宣言なしで生成する「オブジェクト式(Object Expressions)」と呼ばれる文法があります。 C# における匿名型(Anonymous Types)の文法に似ています。
以下は、インタフェース IGreeting のメソッドの実装をその場で定義して、クラスインスタンスを new 演算子で生成。そのメソッドを呼び出すコードです。前出のコードと違って IGreeting へのキャストは不要になりました。
let morning3 = { new IGreeting with override __.Say() = printfn "Good morning, Object Expression !!!" } let evening3 = { new IGreeting with override __.Say() = printfn "Good evening, Object Expression !!!" } // オブジェクト式を使ったためキャストは不要になった morning3.Say() evening3.Say()
実行結果
Good morning, Object Expression !!!
Good evening, Object Expression !!!
F#からWPFのGUIを扱う 《文字列データとしてのXAMLからGUI表示へ》
以前、F#からWPFのGUIを扱う その1~その4 にて、C#プロジェクトを混在させてWPFを利用したプログラミングを行いました。
今回はF#のコードだけを使ってWPFを扱ってみます。XAMLファイルは用いずにXAMLコードを F#コード内で文字列データとして持つことにします。今回の開発環境には Visual Studio Community 2017 を使っています。
1. 新規のF#プロジェクト(コンソールアプリケーションを選択)を作ります。
2. プロジェクトのプロパティ設定で「出力の種類」を「Windows アプリケーション」に変更します。
3. WPF に必要なアセンブリ参照を追加します。以下の4つです。
- WindowsBase
- PresentationCore
- PresentationFramework
- System.Xaml
参照設定が完了すると以下のような構成になります。
4. ウィンドウ上に、黒い輪郭線を持ち青く塗られた矩形(Rectangle要素)と黒い輪郭線を持ち赤く塗られた円(Ellipse要素)を配置するF#コードを以下に示します。XamlReader で XAMLコードを文字列として読み込んでWindow クラス (System.Windows) オブジェクトに変換してGUIとして表示しています。
// プロジェクトのプロパティ設定をWindowsアプリケーションとして設定すること open System.Windows open System open System.Windows.Markup // ダブルクォート3連続(""")で XAMLの属性値 "~" を含む文字列もエスケープ文字なしでそのまま扱える let xamlStr = """ <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="500"> <Grid> <Rectangle Fill="Blue" HorizontalAlignment="Left" Height="100" Margin="130,70,0,0" Stroke="Black" VerticalAlignment="Top" Width="100"/> <Ellipse Fill="Red" HorizontalAlignment="Left" Height="100" Margin="260,120,0,0" Stroke="Black" VerticalAlignment="Top" Width="100"/> </Grid> </Window> """ [<EntryPoint; STAThread>] // STAThreadを指定しないとWPFアプリは実行時はエラーとなるので注意 let main _ = let window = XamlReader.Parse(xamlStr) :?> Window window.WindowStartupLocation <- WindowStartupLocation.CenterScreen // 表示位置:モニタ中央 Application().Run(window)
5. 実行結果:モニタ中央に以下のようなウィンドウが表示されます。デフォルト設定では XAML UI デバッグツールのボタンがウィンドウ上に付いています。
6. XAML UI デバッグツールを非表示にしたい場合は、Visual Studioのメニューバーの「ツール」→「オプション」→「デバッグ」→「全般」へと進み、項目「XAML の UI デバッグツールを有効にする」のチェックボックスOFFにします。
7. 再度実行するとXAML UI デバッグツールのボタンは消えます。
VS2017 で NuGet パッケージ情報が取得できない?(トラブルシューティング)
Visual Sturdio Community 2017 (この記事時点でのバージョンは15.1 (26403.3) )をインストールした後、Nuget パッケージの情報を取得できない状態になっていることに気付きました。このままでは NuGet パッケージの検索もインストールも出来ません。その理由は単に Nuget パッケージ情報の取得先となるURL設定に不備があったためなのですが、以下はその際に行った修復作業記録です。
修復作業の流れ
メニューから「ツール」→「オプション」→「NuGet パッケージマネージャー」→「パッケージソース」の画面へ移行する。
「利用可能なパッケージソース」の欄が空になっているので、右上方にあるボタンを押して項目を新規作成します。
下の方にある入力欄に「名前」として「nuget.org」を、「ソース」としてのURLを「https://api.nuget.org/v3/index.json
」と入力して ボタンを押して登録します。
これでパッケージ情報取得のための環境設定が出来ました。
プロジェクトから右クリックメニューで「NuGet パッケージの管理」を選んで、
パッケージマネージャーの画面に移動します
画面右上にあるプルダウンメニューでデフォルト選択されているパッケージソースを
新規に追加したパッケージソースに変更します。(ここでは「nuget.org」を選択)
これで作業完了です。パッケージ情報が得られるようになりました。これでパッケージのインストールも正常に行うことが出来る状態になりました。
F#のコードをユニットテストで検証する その6 (NUnit)
NUnit を使って例外送出のテストをしてみます。以下に示すコードでは DivideByZeroException が確実にスローされることを確認するテストを実行しています。
例外送出のユニットテストの例
- module UnitTestingSamples2
- open NUnit.Framework
- open System
- [<Test>]
- let ``ゼロ除算例外のテスト 1``() =
- let td = new TestDelegate(fun() -> 1 / 0 |> ignore)
- let result =
- Assert.Throws<DivideByZeroException>(td)
- TestContext.Out.WriteLine(sprintf "結果は %A" result)
- [<Test>]
- let ``ゼロ除算例外のテスト 2``() =
- let result =
- Assert.Throws<DivideByZeroException>(
- fun() -> 1 / 0 |> ignore
- )
- TestContext.Out.WriteLine(sprintf "結果は %A" result)
この例では(動作が全く同じ)2つの例外送出のテスト関数が書いてあります。ゼロ除算をわざと行って例外送出を行っています。
5~10行目:``ゼロ除算例外のテスト 1`` では TestDelegate クラスとラムダ式を使って Assert.Throws へ引数として渡すオブジェクトを作っています。
12~18行目:``ゼロ除算例外のテスト 2`` では TestDelegate クラスを経由せず、Assert.Throws の引数として直接ラムダ式を記述していますが同じ動作が期待できます。
Assert.Throws メソッドには型パラメータとして(送出される予定の型である) DivideByZeroException クラスを与えます。
テストエクスプローラーによる実行結果
``ゼロ除算例外のテスト 1`` で Assert.Throws メソッドの返り値をテスト結果として出力するために10行目の TestContext.Out.WriteLine メソッドを使っています。
テストエクスプローラー内で「``ゼロ除算例外のテスト 1`` 」の項目を選んで「出力」の部分をクリックすると以下のような TestContext.Out.WriteLine メソッドによる出力を見ることが出来ます。
F#のコードをユニットテストで検証する その5 (NUnit)
Visual Studio 2013 から NUnit を使ってF# コードのユニットテストを行ってみます。(F# のプロジェクトはコンソールアプリケーションのプロジェクトとして作成)
プロジェクトへ必要な Nuget パッケージをインストールします。(記事作成時の NUnit のバージョンは3.5.0)
Visual Studio 2013 のテストエクスプローラーでユニットテストができるように NUnit 3 Test Adapter for Visual Studio(記事作成時のバージョンは3.5.0)を(F#のプロジェクトが所属する)ソリューションへインストールします。
テストコードの例
- module UnitTestingSamples
- open NUnit.Framework
- [<Test>]
- let simpleTest() =
- Assert.AreEqual(2.0**10.0, 1024.0)
- [<TestCase("", 0)>]
- [<TestCase("ab", 2)>]
- [<TestCase("ABC", 3)>]
- [<TestCase("ABCD", 4)>]
- [<TestCase("12345", 5)>]
- let parameterizedTest(str: string, expected: int) :unit =
- Assert.AreEqual(str.Length, expected)
2行目: NUnit の名前空間 NUnit.Framework をインポートします。
4~6行目:Assert.AreEqual を使って値の等値性検査を行います。関数 simpleTest では2の10乗が1024に等しいかどうかを検査しています。
8~14行目:Assert.AreEqual を使って値の等値性検査を行います。関数 parameterizedTest には2つの引数があり、TestCase で指定したテストデータの組み合わせが渡されます。この例では TestCase が5つ置かれているので、関数 parameterizedTest を使ったテストが5回実行されます。
テストエクスプローラーから実行した結果