関数型プログラミングにすっかり夢中になり、空き時間にF#を学び、その素晴らしさを同僚に熱弁して迷惑がられ、仕事で本格的に使いたくてうずうずしている...

そんなあなたが、突然壁にぶつかります。

職場には「C#のみ」というポリシーがあり、F#の使用が認められないのです。

典型的な企業環境で働いているなら、新しい言語の承認を得るのは長い道のりになるでしょう。 チームメイト、QA担当者、運用担当者、上司、上司の上司、そして廊下の奥にいる謎の人物(今まで話したことのない人)を説得する必要があります。 その過程を始めることをお勧めします(マネージャーに役立つリンク)が、それでもあなたは焦れて「今すぐに何ができるだろう?」と考えているはずです。

一方で、柔軟で自由な職場環境で、好きなことができる立場にいるかもしれません。

しかし、あなたは良心的なので、ミッションクリティカルなシステムをAPLで書き直して姿を消し、後任に頭の痛む暗号のようなコードを残していく、そんな人になりたくはありません。 決して、チームのバス係数に影響を与えるようなことはしたくないのです。

つまり、これらのシナリオでは、仕事でF#を使いたいけれど、コアとなるアプリケーションコードには使えない(または使いたくない)という状況です。

何ができるでしょうか?

心配いりません!この連載では、重要なコードに影響を与えることなく、低リスクで段階的にF#を実践できる方法をいくつか提案します。

シリーズの内容

26の方法のリストを以下に示します。特に興味のあるものに直接アクセスできます。

パート1 - F#を使って対話的に探索し開発する

1. F#を使って.NETフレームワークを対話的に探索する
2. F#を使って自分のコードを対話的にテストする
3. F#を使ってWebサービスを対話的に操作する
4. F#を使ってUIを対話的に操作する

パート2 - 開発およびDevOpsスクリプトにF#を使う

5. ビルドとCIスクリプトにFAKEを使う
6. Webサイトの応答をチェックするF#スクリプト
7. RSSフィードをCSVに変換するF#スクリプト
8. WMIを使ってプロセスの統計をチェックするF#スクリプト
9. クラウドの設定と管理にF#を使う

パート3 - テストにF#を使う

10. 読みやすい名前の単体テストをF#で書く
11. F#を使ってプログラムで単体テストを実行する
12. F#を使って他の方法で単体テストを書くことを学ぶ
13. FsCheckを使ってより良い単体テストを書く
14. FsCheckを使ってランダムなダミーデータを作成する
15. F#を使ってモックを作成する
16. F#を使って自動化されたブラウザテストを行う
17. 振る舞い駆動開発にF#を使う

パート4. データベース関連のタスクにF#を使う

18. F#を使ってLINQpadを置き換える
19. F#を使ってストアドプロシージャの単体テストを行う
20. FsCheckを使ってランダムなデータベースレコードを生成する
21. F#を使って簡単なETLを行う
22. F#を使ってSQL Agentスクリプトを生成する

パート5: F#を使うその他の興味深い方法

23. パーシングにF#を使う
24. ダイアグラムと可視化にF#を使う
25. WebベースのデータストアへのアクセスにF#を使う
26. データサイエンスと機械学習にF#を使う
(ボーナス)27: イギリスの発電所群の発電スケジュールをバランスさせる

はじめに

Visual Studioを使っている場合、F#はすでにインストールされているので、すぐに始められます!誰かの許可を得る必要はありません。

MacまたはLinuxを使っている場合は、残念ながら少し作業が必要です(MacLinuxの手順)。

F#を対話的に使う方法は2つあります:(1) F#対話ウィンドウに直接入力する、または (2) F#スクリプトファイル(.FSX)を作成し、コードスニペットを評価する。

Visual StudioでF#対話ウィンドウを使うには:

  1. メニュー > 表示 > その他のウィンドウ > F# インタラクティブ でウィンドウを表示します。
  2. 式を入力し、二重セミコロン(;;)を使って入力が終わったことをインタープリターに伝えます。

たとえば:

let x = 1
let y = 2
x + y;;

個人的には、スクリプトファイルを作成する方法(ファイル > 新規作成 > ファイルを選択し、"F#スクリプト"を選択)を好みます。自動補完とインテリセンスが利用できるからです。

コードの一部を実行するには、選択してマウス右クリックするか、単にAlt+Enterを押します。

外部ライブラリとNuGetの使用

ほとんどのコードサンプルは、スクリプトディレクトリ下にあることが想定される外部ライブラリを参照しています。

これらのDLLを明示的にダウンロードまたはコンパイルすることもできますが、コマンドラインからNuGetを使う方が簡単だと思います。

  1. まず、Chocolately(chocolatey.orgから)をインストールする必要があります。
  2. 次に、cinst nuget.commandlineを使ってNuGetコマンドラインをインストールします。
  3. 最後に、スクリプトディレクトリに移動し、コマンドラインからNuGetパッケージをインストールします。 例:nuget install FSharp.Data -o Packages -ExcludeVersion ご覧の通り、スクリプトから使う際にNuGetパッケージのバージョンを除外することを好みます。これにより、後で更新しても既存のコードが壊れません。

パート1:F#を使って対話的に探索し開発する

F#が価値を発揮する最初の領域は、.NETライブラリを対話的に探索するツールとしてです。

以前は、これを行うために単体テストを作成し、デバッガーでステップ実行して何が起こっているかを理解する必要があったかもしれません。 しかし、F#を使えば、そうする必要はありません。コードを直接実行できます。

いくつかの例を見てみましょう。

1. F#を使って.NETフレームワークを対話的に探索する

このセクションのコードはgithubで入手可能です。

コーディングをしていると、.NETライブラリの動作について小さな疑問がよく生じます。

たとえば、最近私が遭遇し、F#を対話的に使って答えた質問をいくつか紹介します:

  • カスタムのDateTime形式文字列は正しいですか?
  • XMLシリアル化はローカルのDateTimeとUTCのDateTimeをどのように扱いますか?
  • GetEnvironmentVariableは大文字小文字を区別しますか?

これらの質問はもちろんMicrosoft Learnのドキュメントで見つけることができますが、以下に示す簡単なF#スニペットを実行することで数秒で答えることもできます。

カスタムのDateTime形式文字列は正しいですか?

カスタム形式で24時間表記を使いたいと思います。"h"であることは知っていますが、大文字の"H"か小文字の"h"のどちらでしょうか?

open System
DateTime.Now.ToString("yyyy-MM-dd hh:mm")  // "2014-04-18 01:08"
DateTime.Now.ToString("yyyy-MM-dd HH:mm")  // "2014-04-18 13:09"

XMLシリアル化はローカルのDateTimeとUTCのDateTimeをどのように扱いますか?

日付に関して、XMLシリアル化はどのように機能するのでしょうか?確認してみましょう!

// ヒント: 現在のディレクトリをスクリプトディレクトリと同じに設定します
System.IO.Directory.SetCurrentDirectory (__SOURCE_DIRECTORY__)

open System

[<CLIMutable>] 
type DateSerTest = {Local:DateTime;Utc:DateTime}

let ser = new System.Xml.Serialization.XmlSerializer(typeof<DateSerTest>)

let testSerialization (dt:DateSerTest) = 
    let filename = "serialization.xml"
    use fs = new IO.FileStream(filename , IO.FileMode.Create)
    ser.Serialize(fs, o=dt)
    fs.Close()
    IO.File.ReadAllText(filename) |> printfn "%s"

let d = { 
    Local = DateTime.SpecifyKind(new DateTime(2014,7,4), DateTimeKind.Local)
    Utc = DateTime.SpecifyKind(new DateTime(2014,7,4), DateTimeKind.Utc)
    }

testSerialization d

出力は以下の通りです:

<DateSerTest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Local>2014-07-04T00:00:00+01:00</Local>
  <Utc>2014-07-04T00:00:00Z</Utc>
</DateSerTest>

このように、UTC時間には"Z"が使われていることがわかります。

GetEnvironmentVariableは大文字小文字を区別しますか?

これは簡単なスニペットで答えることができます:

Environment.GetEnvironmentVariable "ProgramFiles" = 
    Environment.GetEnvironmentVariable "PROGRAMFILES"
// 答え => true

したがって、答えは「大文字小文字を区別しない」です。

2. F#を使って自分のコードを対話的にテストする

このセクションのコードはgithubで入手可能です。

もちろん、.NETライブラリだけでなく、自分のコードをテストすることもできます。時には自分のコードをテストするのが非常に役立つことがあります。

これを行うには、以下に示すようにDLLを参照し、名前空間をオープンするだけです。


// 現在のディレクトリをスクリプトディレクトリと同じに設定
System.IO.Directory.SetCurrentDirectory (__SOURCE_DIRECTORY__)

// DLLへの相対パスを渡す
#r @"bin\debug\myapp.dll"

// 名前空間をオープンする
open MyApp

// 何かを実行
MyApp.DoSomething()

警告:古いバージョンのF#では、DLLへの参照を開くとロックされてコンパイルできなくなります!その場合、再コンパイル前に対話セッションをリセットしてロックを解除してください。 新しいバージョンのF#では、DLLはシャドウコピーされるため、ロックはありません。

3. F#を使ってWebサービスを対話的に操作する

このセクションのコードはgithubで入手可能です。

WebAPIとOwinライブラリを使いたい場合、実行可能ファイルを作成する必要はありません - スクリプトだけで実行できます!

これを動作させるには、いくつかのライブラリDLLが必要なので、少しセットアップが必要です。

NuGetコマンドラインのセットアップが完了していると仮定して(上記参照)、スクリプトディレクトリに移動し、以下のコマンドでセルフホスティングライブラリをインストールします。 nuget install Microsoft.AspNet.WebApi.OwinSelfHost -o Packages -ExcludeVersion

これらのライブラリが配置されたら、以下のコードを簡単なWebAPIアプリのスケルトンとして使用できます。

// 現在のディレクトリをスクリプトディレクトリと同じに設定
System.IO.Directory.SetCurrentDirectory (__SOURCE_DIRECTORY__)

// nuget install Microsoft.AspNet.WebApi.OwinSelfHostが実行されていることを前提とし、
// アセンブリが現在のディレクトリの下で利用可能
#r @"Packages\Owin\lib\net40\Owin.dll"
#r @"Packages\Microsoft.Owin\lib\net40\Microsoft.Owin.dll"
#r @"Packages\Microsoft.Owin.Host.HttpListener\lib\net40\Microsoft.Owin.Host.HttpListener.dll"
#r @"Packages\Microsoft.Owin.Hosting\lib\net40\Microsoft.Owin.Hosting.dll"
#r @"Packages\Microsoft.AspNet.WebApi.Owin\lib\net45\System.Web.Http.Owin.dll"
#r @"Packages\Microsoft.AspNet.WebApi.Core\lib\net45\System.Web.Http.dll"
#r @"Packages\Microsoft.AspNet.WebApi.Client\lib\net45\System.Net.Http.Formatting.dll"
#r @"Packages\Newtonsoft.Json\lib\net40\Newtonsoft.Json.dll"
#r "System.Net.Http.dll"

open System
open Owin 
open Microsoft.Owin
open System.Web.Http 
open System.Web.Http.Dispatcher
open System.Net.Http.Formatting

module OwinSelfhostSample =

    /// 返すレコード
    [<CLIMutable>]
    type Greeting = { Text : string }

    /// 簡単なコントローラー
    type GreetingController() =
        inherit ApiController()

        // GET api/greeting
        member this.Get()  =
            {Text="Hello!"}

    /// URIをパースする別のコントローラー
    type ValuesController() =
        inherit ApiController()

        // GET api/values 
        member this.Get()  =
            ["value1";"value2"]

        // GET api/values/5 
        member this.Get id = 
            sprintf "id is %i" id 

        // POST api/values 
        member this.Post ([<FromBody>]value:string) = 
            ()

        // PUT api/values/5 
        member this.Put(id:int, [<FromBody>]value:string) =
            ()

        // DELETE api/values/5 
        member this.Delete(id:int) =
            () 

    /// ルートなどを保存するヘルパークラス
    type ApiRoute = { id : RouteParameter }

    /// 重要: 対話的に実行する場合、コントローラーが見つからずエラーが発生します:
    /// "No type was found that matches the controller named 'XXX'."
    /// 解決策は、現在のアセンブリを使うようにControllerResolverをオーバーライドすることです
    type ControllerResolver() =
        inherit DefaultHttpControllerTypeResolver()

        override this.GetControllerTypes (assembliesResolver:IAssembliesResolver) = 
            let t = typeof<System.Web.Http.Controllers.IHttpController>
            System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
            |> Array.filter t.IsAssignableFrom
            :> Collections.Generic.ICollection<Type>    

    /// 設定を管理するクラス
    type MyHttpConfiguration() as this =
        inherit HttpConfiguration()

        let configureRoutes() = 
            this.Routes.MapHttpRoute(
                name= "DefaultApi",
                routeTemplate= "api/{controller}/{id}",
                defaults= { id = RouteParameter.Optional }
                ) |> ignore

        let configureJsonSerialization() = 
            let jsonSettings = this.Formatters.JsonFormatter.SerializerSettings
            jsonSettings.Formatting <- Newtonsoft.Json.Formatting.Indented
            jsonSettings.ContractResolver <- 
                Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()

        // ここでコントローラーが解決されます
        let configureServices() = 
            this.Services.Replace(
                typeof<IHttpControllerTypeResolver>, 
                new ControllerResolver())

        do configureRoutes()
        do configureJsonSerialization()
        do configureServices()

    /// 設定を使ってスタートアップクラスを作成    
    type Startup() = 

        // このコードはWeb APIを設定します。Startupクラスは
        // WebApp.Startメソッドの型パラメーターとして指定されます。
        member this.Configuration (appBuilder:IAppBuilder) = 
            // セルフホスト用にWeb APIを設定 
            let config = new MyHttpConfiguration() 
            appBuilder.UseWebApi(config) |> ignore


// OWINホストを開始 
do 
    // サーバーを作成
    let baseAddress = "http://localhost:9000/" 
    use app = Microsoft.Owin.Hosting.WebApp.Start<OwinSelfhostSample.Startup>(url=baseAddress) 

    // クライアントを作成し、APIにいくつかのリクエストを行う
    use client = new System.Net.Http.HttpClient() 

    let showResponse query = 
        let response = client.GetAsync(baseAddress + query).Result 
        Console.WriteLine(response) 
        Console.WriteLine(response.Content.ReadAsStringAsync().Result) 

    showResponse "api/greeting"
    showResponse "api/values"
    showResponse "api/values/42"

    // スタンドアロンスクリプトの場合、ブラウザでもテストできるように一時停止
    Console.ReadLine() |> ignore

出力は以下の通りです:

StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
  Date: Fri, 18 Apr 2014 22:29:04 GMT
  Server: Microsoft-HTTPAPI/2.0
  Content-Length: 24
  Content-Type: application/json; charset=utf-8
}
{
  "text": "Hello!"
}
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
  Date: Fri, 18 Apr 2014 22:29:04 GMT
  Server: Microsoft-HTTPAPI/2.0
  Content-Length: 29
  Content-Type: application/json; charset=utf-8
}
[
  "value1",
  "value2"
]
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
  Date: Fri, 18 Apr 2014 22:29:04 GMT
  Server: Microsoft-HTTPAPI/2.0
  Content-Length: 10
  Content-Type: application/json; charset=utf-8
}
"id is 42"

この例は、OWINとWebApiライブラリを「そのまま」使えることを示すためのものです。

F#にさらにフレンドリーなWebフレームワークについては、SuaveWebSharperをご覧ください。 fsharp.orgにはさらに多くのWeb関連の情報があります。

4. F#を使ってUIを対話的に操作する

このセクションのコードはgithubで入手可能です。

F#インタラクティブのもう一つの使用法は、UIを実行中に - ライブで - 操作することです!

以下は、WinFormsの画面を対話的に開発する例です。

open System.Windows.Forms 
open System.Drawing

let form = new Form(Width= 400, Height = 300, Visible = true, Text = "Hello World") 
form.TopMost <- true
form.Click.Add (fun _ -> 
    form.Text <- sprintf "form clicked at %i" DateTime.Now.Ticks)
form.Show()

ウィンドウは以下のようになります:

クリック後、タイトルバーが変更されたウィンドウは以下のようになります:

次に、FlowLayoutPanelとボタンを追加しましょう。

let panel = new FlowLayoutPanel()
form.Controls.Add(panel)
panel.Dock = DockStyle.Fill 
panel.WrapContents <- false 

let greenButton = new Button()
greenButton.Text <- "Make the background color green" 
greenButton.Click.Add (fun _-> form.BackColor <- Color.LightGreen)
panel.Controls.Add(greenButton)

現在のウィンドウは以下のようになります:

しかし、ボタンが小さすぎます ―― AutoSizeをtrueに設定する必要があります。

greenButton.AutoSize <- true

これでよくなりました!

黄色のボタンも追加してみましょう:

let yellowButton = new Button()
yellowButton.Text <- "Make me yellow" 
yellowButton.AutoSize <- true
yellowButton.Click.Add (fun _-> form.BackColor <- Color.Yellow)
panel.Controls.Add(yellowButton)

しかし、ボタンが切れてしまっているので、フローの方向を変更しましょう:

panel.FlowDirection <- FlowDirection.TopDown

しかし今度は、黄色のボタンが緑のボタンと同じ幅になっていません。これはDockで修正できます:

yellowButton.Dock <- DockStyle.Fill

ご覧の通り、このように対話的にレイアウトを操作するのは非常に簡単です。 レイアウトのロジックに満足したら、実際のアプリケーション用にコードをC#に変換し直すことができます。

この例はWinForms固有のものです。他のUIフレームワークでは、もちろんロジックは異なります。


以上が最初の4つの提案です。まだ終わりではありません! 次の投稿では、開発と DevOps スクリプトに F# を使う方法について説明します。

results matching ""

    No results matching ""