読者です 読者をやめる 読者になる 読者になる

Getting a Good Grasp of F# (仮)

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

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 パッケージの管理」を選んで、

パッケージマネージャーの画面に移動します

画面右上にあるプルダウンメニューでデフォルト選択されているパッケージソースを

f:id:pongitsune:20170413230140p:plain

新規に追加したパッケージソースに変更します。(ここでは「nuget.org」を選択)

 

これで作業完了です。パッケージ情報が得られるようになりました。これでパッケージのインストールも正常に行うことが出来る状態になりました。

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

NUnit を使って例外送出のテストをしてみます。以下に示すコードでは DivideByZeroException が確実にスローされることを確認するテストを実行しています。

例外送出のユニットテストの例

  1. module UnitTestingSamples2
  2. open NUnit.Framework
  3. open System
  4.  
  5. [<Test>]
  6. let ``ゼロ除算例外のテスト 1``() =
  7.     let td = new TestDelegate(fun() -> 1 / 0 |> ignore)
  8.     let result =
  9.         Assert.Throws<DivideByZeroException>(td)
  10.     TestContext.Out.WriteLine(sprintf "結果は %A" result)
  11.  
  12. [<Test>]
  13. let ``ゼロ除算例外のテスト 2``() =
  14.     let result =
  15.         Assert.Throws<DivideByZeroException>(
  16.             fun() -> 1 / 0 |> ignore
  17.         )
  18.     TestContext.Out.WriteLine(sprintf "結果は %A" result)

この例では(動作が全く同じ)2つの例外送出のテスト関数が書いてあります。ゼロ除算をわざと行って例外送出を行っています。

5~10行目:``ゼロ除算例外のテスト 1`` では TestDelegate クラスとラムダ式を使って Assert.Throws へ引数として渡すオブジェクトを作っています。

12~18行目:``ゼロ除算例外のテスト 2`` では TestDelegate クラスを経由せず、Assert.Throws の引数として直接ラムダ式を記述していますが同じ動作が期待できます。

Assert.Throws メソッドには型パラメータとして(送出される予定の型である) DivideByZeroException クラスを与えます。

テストエクスプローラーによる実行結果

f:id:pongitsune:20161026195436p:plain

``ゼロ除算例外のテスト 1``  で Assert.Throws メソッドの返り値をテスト結果として出力するために10行目の TestContext.Out.WriteLine メソッドを使っています。

テストエクスプローラー内で「``ゼロ除算例外のテスト 1`` 」の項目を選んで「出力」の部分をクリックすると以下のような TestContext.Out.WriteLine メソッドによる出力を見ることが出来ます。

f:id:pongitsune:20161026200304p:plain

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#のプロジェクトが所属する)ソリューションへインストールします。

テストコードの例

  1. module UnitTestingSamples
  2. open NUnit.Framework
  3.  
  4. [<Test>]
  5. let simpleTest() =
  6.     Assert.AreEqual(2.0**10.0, 1024.0)
  7.  
  8. [<TestCase("",      0)>]
  9. [<TestCase("ab",    2)>]
  10. [<TestCase("ABC",   3)>]
  11. [<TestCase("ABCD",  4)>]
  12. [<TestCase("12345", 5)>]
  13. let parameterizedTest(str: string, expected: int) :unit =
  14.     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回実行されます。

テストエクスプローラーから実行した結果

f:id:pongitsune:20161025145455p:plain

 

F#からWPFのGUIを扱う その4

前回(F#からWPFのGUIを扱う その3)の別解として、今度は Win32 API を直接利用せずにインターフェイス IValueConverter の実装を別の方法で試みます。

MyBitmapConverter クラスを再実装します。よって、前回のF#のコードの34~52行目を以下のコードで置き換えます。

  1. [<System.Windows.Data.ValueConversion(typeof<System.Drawing.Bitmap>, typeof<System.Windows.Media.ImageSource>)>]
  2. type MyBitmapConverter() =
  3.     interface System.Windows.Data.IValueConverter with
  4.         override x.Convert(value: obj, targetType: System.Type, parameter: obj, culture: System.Globalization.CultureInfo) =
  5.             let drawingBmp = value :?> System.Drawing.Bitmap
  6.             assert(not <| obj.ReferenceEquals(drawingBmp, null))
  7.  
  8.             // ビットマップからピクセルデータ取り出し    
  9.             let  bmpData: System.Drawing.Imaging.BitmapData =
  10.                     drawingBmp.LockBits(
  11.                         System.Drawing.Rectangle(System.Drawing.Point(0, 0), drawingBmp.Size),
  12.                         System.Drawing.Imaging.ImageLockMode.ReadOnly,
  13.                         drawingBmp.PixelFormat)
  14.  
  15.             let scanLineWidth = abs(bmpData.Stride) // Strideプロパティが負の場合にも対処
  16.             let buffSize = scanLineWidth * drawingBmp.Height
  17.             use graphics = System.Drawing.Graphics.FromImage(drawingBmp)
  18.             // BGRの順にデータが並んでいるので注意
  19.             let bmpSrc = System.Windows.Media.Imaging.BitmapSource.Create(
  20.                                             drawingBmp.Width,
  21.                                             drawingBmp.Height,
  22.                                             double graphics.DpiX,
  23.                                             double graphics.DpiY,
  24.                                             System.Windows.Media.PixelFormats.Bgr24,
  25.                                             null,
  26.                                             bmpData.Scan0,
  27.                                             buffSize,
  28.                                             scanLineWidth)
  29.             bmpSrc.Freeze()
  30.             drawingBmp.UnlockBits(bmpData)
  31.             bmpSrc :> obj
  32.  
  33.         override x.ConvertBack(value: obj, targetType: System.Type, parameter: obj, culture: System.Globalization.CultureInfo) =
  34.             failwith("not implemented")

内部実装は変えましたが 実行結果は前回と同じ外観です。

F#からWPFのGUIを扱う その3

WPFImage コントロールに、動的に生成したビットマップ画像を表示させるコードを書いてみます。

生成する画像については「Windows Presentation Foundation 4.5 Cookbook」Chapter 9 : Graphics and Animation / Manipulating a bitmap programmatically にあるマンデルプロ集合のビットマップ画像表示サンプルを一部お手本にします。

WPFGUI 部分はC#のプロジェクトで作成し、それをF#のプロジェクトから操作出来るように「F#からWPFのGUIを扱う その1」と同様の手順で Visual Studio 2013 のソリューションを作成します。

F#からWPFのGUIを扱う その1」に合わせて、 WPF を F# で操作するためのソリューションを作る作業手順は以下の図のような流れで行っています。

image/svg+xml C#のプロジェクト WPF アプリケーションとして新規作成 F#のプロジェクト コンソール アプリケーションとして新規作成 クラスライブラリに変更 App.xaml を削除App.config を削除 Windows アプリケーションに変更 スタートアッププロジェクトに設定 C#のプロジェクトをアセンブリ参照に追加 以下をアセンブリ参照に追加 ・WindowsBase・PresentationCore・PresentationFramework・System.Xaml WPF のコントロールを操作するコードを書く XAML デザイナーにてコントロール(UI要素)を配置 XAML ファイル内のコントロールに必要なプロパティを追加 ・x:Name="識別子となる文字列"・x:FieldModifier="public"

今回も同様の手順で C#のプロジェクト名は WpfTest としています。

新規作成されたC#のプロジェクトのXAMLファイルを以下のように修正します。

600 × 600 ピクセルのサイズの Image コントロールを1つ配置しNameプロパティを「 TestImage1 」とします。F#のコードからアクセスできるようにプロパティ「 x:FieldModifier="public" 」も追加します。

Window のサイズはクライアント領域の Image コントロールのサイズにフィットさせる設定「 SizeToContent="WidthAndHeight" 」とします。

  1. <Window x:Class="WpfTest.MainWindow"
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.         Title="Mandelbrot Set" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize">
  5.     <Grid>
  6.         <Image x:FieldModifier="public" Width="600" Height="600" x:Name="TestImage1" />
  7.     </Grid>
  8. </Window>

 

F#のプロジェクト WinAppTest において、今回は Bitmap クラス (System.Drawing) オブジェクトへ描画処理を行うのでさらにSystem.Drawing へのアセンブリ参照を追加します。最終的にF#のプロジェクトは以下のような参照設定となります。

ファイル Program.fs に書くコードは以下の通り。

  1. open System.Numerics
  2.  
  3. let mandelbrotColor(c: Complex): int =
  4.     let colorInit = 256.0
  5.     let rec calcColor (z: Complex) (col: double) : int =
  6.         if (z.Real + z.Imaginary < 4.0) && (col > 0.0) then
  7.             calcColor (z * z + c) (col - 1.0)
  8.         else
  9.             int col
  10.  
  11.     calcColor Complex.Zero colorInit
  12.  
  13. let createMandelbrotBitmap(): System.Drawing.Bitmap =
  14.     let drawingBmp = new System.Drawing.Bitmap(600, 600,
  15.                                                 System.Drawing.Imaging.PixelFormat.Format24bppRgb)
  16.     let from = Complex(-1.5, -1.0)
  17.     let dest = Complex(1.0, 1.0)
  18.     let deltax = (dest.Real - from.Real) / (double drawingBmp.Width)
  19.     let deltay = (dest.Imaginary - from.Imaginary) / (double drawingBmp.Height)
  20.     
  21.     for y = 0 to (drawingBmp.Height - 1) do
  22.         for x = 0 to (drawingBmp.Width - 1) do
  23.             let col = mandelbrotColor(from + Complex(double(x) * deltax, double(y) * deltay))
  24.             drawingBmp.SetPixel(x, y, System.Drawing.Color.FromArgb(col, col, col))
  25.     
  26.     do
  27.         use g = System.Drawing.Graphics.FromImage(drawingBmp)
  28.         use font = new System.Drawing.Font("Meiryo UI", 25.0f, System.Drawing.GraphicsUnit.Pixel)  
  29.         use brush = new System.Drawing.SolidBrush(System.Drawing.Color.Red)
  30.         g.DrawString("マンデルブロ集合", font, brush, 230.0f, 200.0f)
  31.     
  32.     drawingBmp
  33.  
  34. [<System.Runtime.InteropServices.DllImport("gdi32")>]
  35. extern bool DeleteObject(nativeint hObject)  
  36.  
  37. [<System.Windows.Data.ValueConversion(typeof<System.Drawing.Bitmap>, typeof<System.Windows.Media.ImageSource>)>]
  38. type MyBitmapConverter() =
  39.     interface System.Windows.Data.IValueConverter with
  40.         override x.Convert(value: obj, targetType: System.Type, parameter: obj, culture: System.Globalization.CultureInfo) =
  41.             let drawingBmp = value :?> System.Drawing.Bitmap
  42.             assert(not <| obj.ReferenceEquals(drawingBmp, null))
  43.             let hBitmap: nativeint = drawingBmp.GetHbitmap() // GDIビットマップオブジェクトへのハンドルを取得
  44.             let sourceRect = System.Windows.Int32Rect.Empty
  45.             let sizeOptions = System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions()
  46.             let bmpSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(hBitmap, System.IntPtr.Zero, sourceRect, sizeOptions)
  47.             bmpSource.Freeze()
  48.             DeleteObject(hBitmap) |> ignore // GDIビットマップオブジェクトの後始末
  49.             bmpSource :> obj
  50.  
  51.         override x.ConvertBack(value: obj, targetType: System.Type, parameter: obj, culture: System.Globalization.CultureInfo) =
  52.             failwith("not implemented")
  53.  
  54. [<EntryPoint; System.STAThread>]
  55. let main _ =
  56.     let app = new System.Windows.Application()
  57.     let win = new WpfTest.MainWindow()
  58.  
  59.     use bitmap: System.Drawing.Bitmap = createMandelbrotBitmap()
  60.     let binding = new System.Windows.Data.Binding(
  61.                                             Source = bitmap,
  62.                                             Converter = new MyBitmapConverter())
  63.     win.TestImage1.SetBinding(System.Windows.Controls.Image.SourceProperty, binding) |> ignore
  64.  
  65.     app.Run win

3行目:mandelbrotColor 関数は複素平面上の指定座標における描画色(輝度、256階調)を計算する関数です。

13行目:createMandelbrotBitmap 関数は GDI+ ビットマップ(System.Drawing.Bitmap)を生成します。ビットマップのサイズは 600 × 600 ピクセルです。ビットマップ上すべてのピクセルの色を計算してその値を書き込みます。また、「マンデルブロ集合」という文字列を描き加えます。
ビットマップは、1ピクセルあたり 24 bit、つまりR・G・Bそれぞれに 8 bit を割り当てるカラー画像フォーマットです。ただし、24行目で各ピクセル中のR・G・Bそれぞれに対して等しい輝度の値を与えているのでここで描画されるマンデルプロ集合は見かけ上はモノクロ画像に見えます。

34~35行目Win32 APIDeleteObject 関数を直接呼び出すための宣言です。MyBitmapConverter クラスのメソッド Convert 内で DeleteObject 関数を呼び出すため必要になります。

38行目WPFImage コントロールへのデータバインディングに今回必要な型変換クラス MyBitmapConverter の定義です。System.Drawing.Bitmap オブジェクトを Image コントロールの Source プロパティ(ImageSource クラス)に適合するように型変換を行うクラスです。IValueConverter インターフェイス を実装しています。37行目ValueConversion アトリビュートによって変換前の型と変換後の型を予め指定する必要があります。

55行目以降がエントリーポイントとなる main 関数です。
57行目C#のプロジェクトの名前空間WpfTest 」にある WPFウィンドウとなる MainWindow クラスのオブジェクトを生成しています。

59行目System.Drawing.Bitmap オブジェクトの生成。ただしそのままでは Image コントロールの Source プロパティとしては使えない型なので、
60~62行目Binding オブジェクトを生成する際に、System.Drawing.Bitmap 型を System.Windows.Media.Imaging.BitmapSource 型に変換する MyBitmapConverter クラスオブジェクトを Converter プロパティとして渡します。

63行目SetBinding メソッドを使って Image コントロール(今回の例では「TestImage1」という名前)の Source プロパティに Binding オブジェクトを付与します。

参考資料:「Windows Presentation Foundation 4.5 Cookbook」Chapter 9 : Graphics and Animation / Manipulating a bitmap programmatically

実行結果

以下のようなウィンドウが表示されます。