2つのOptionalで分岐する

僕のOptional<>の使い方がカッコワルイ。 - 谷本 心 in せろ部屋にある最後の例ですが、 2つのOptionalの組み合わせによって処理を分岐させるコードが幾つか載っています。 ifで書いた例を見てみましょう。

public String hello(Optional<String> name1, Optional<String> name2) {
    if (name1.isPresent()) {
        if (name2.isPresent()) {
            return call(name1.get(), name2.get());
        } else {
            return call1(name1.get());
        }
    } else {
        if (name2.isPresent()) {
            return call2(name2.get());
        } else {
            return call();
        }
    }
}

まとめると、こんな感じでしょうか。

name1 name2 呼び出すメソッド
値有り 値有り 2引数call
値有り Empty call1
Empty 値有り call2
Empty Empty 0引数call

これ、F#だったらmatch式を使えば分かりやすく書けます。

let hello name1 name2 =
  match name1, name2 with
  | Some name1, Some name2 -> call(name1, name2)
  | Some name1, None -> call1(name1)
  | None, Some name2 -> call2(name2)
  | None, None -> call()

JavaOptionalにもしmatchメソッドが実装されていれば、こんな感じに書けるんですけどね。

public String hello(Optional<String> name1, Optional<String> name2) {
    return name1.match(
        n1 -> name2.match(n2 -> call(n1, n2), () -> call1(n1)),
        () -> name2.match(n2 -> call2(n2), () -> call())
    );
}

Optionalのような型を導入するのであれば、やはりmatch式のようなもの*1が欲しくなりますね、という話でした。

*1:あとついでにモナド用の構文も・・・

Effective Visual F# Power Tools

さてさて、またもや他人のAdvent Calendarに乗っかったネタです。 今日は、Visual F# Power Tools の紹介というF# Advent Calendar 2014の11日目の記事です。

VFPTの基本的な便利な機能は元記事を参照いただくとして、このエントリでは更に進んだ使い方等を紹介します。

シンタックスのカラーリングのカスタマイズ

シンタックスのカラーリングはカスタマイズできます。 カスタマイズは、VS自体のオプションの左のペインから環境、フォントおよび色と選択し、 表示項目のF#から始まる項目です。

f:id:bleis-tift:20141213235647p:plain

カスタマイズできる項目は以下の通りです。

  • F# Escaped Characters
  • F# Functions / Methods
  • F# Modules
  • F# Mutable Variables / Reference Cells
  • F# Patterns
  • F# Printf Format
  • F# Quotations
  • F# Types
  • F# Value Types

この中でおすすめなのが、「F# Mutable Variables / Reference Cells」です。 これを設定することで、mutableな変数やref型の変数に色が付き、どこで再代入を使っているかが一目瞭然になります。

f:id:bleis-tift:20141214000302p:plain

スコープもちゃんと認識していますね。 ちなみに、setterがあるプロパティなんかも、Mutable Variablesとみなされて色が付きます。

他には、「F# Escaped Characters」と「F# Printf Format」も便利です。 上のコードでも、printf系の関数のフォーマットに色が付いているのが分かります。 「F# Quotations」も人によっては便利でしょう。 「F# Patterns」は、便利かどうかは微妙ですが、設定してあります。

f:id:bleis-tift:20141214085050p:plain

SomeNoneに色がついているのや、自動実装プロパティのFailedに色がついているのが分かります。

自動実装とタスクリストコメント

元記事ではインターフェイスの自動実装と判別共用体ケースの自動実装に触れられていますが、もう一つ、レコードのスタブ生成機能もあります。 これら自動実装系の機能と、タスクリストコメント*1を合わせる技をadacolaさんが呟いています。

これは是非設定しておくといいでしょう。

failwith "oops!" (* TODO : Not implemented yet *)

このように設定しておけば、(Uncompilable Codeと言いつつ)コンパイル可能で、かつタスク一覧に「TODO : Not implemented yet」と表示されるようになります。

f:id:bleis-tift:20141214091608p:plain

便利!

その他の機能

元記事では触れられていない機能をざっくり紹介します。

ドキュメンテーションコメントの自動生成

///<までタイプするとドキュメンテーションコメントのひな形を生成してくれます。 地味ですが、ドキュメンテーションコメントを書くときに便利です。

VFPTとは関係ありませんが、F#ではドキュメンテーションコメントの中身がsummaryだけであれば、summaryを省略できるという便利機能があります。

/// とても素晴らしい変数です。
let x = 42

これも知っていると便利なので使っていきましょう。

ソースコードの整形

整形してくれます。あんまり使ったことはありません。

もちろん、VSの整形のショートカットで呼び出せます。 使う人は使う機能かも?

ナビゲーションバー

便利機能です。この機能を有効にするには、VSを管理者権限で立ち上げてから「Navigation bar」のチェックボックスをチェックし、VSを再起動する必要がありますので注意してください*2

この機能を有効にすると、エディタの上にナビゲーションバーが表れて、選択した型やメンバに簡単にジャンプできるようになります。 上の画像で、「Program」とか「g」とか表示されているのが分かります。

参照箇所のハイライト

便利機能です。カーソルを置いている変数や関数と同じものを参照している箇所をハイライトしてくれます。

インデントガイド/Depth colorizer

便利機能です。F#はインデントに意味のある言語なので、インデントガイドを出してくれると色々と捗ります。

上の画像でも、インデントガイドが確認できます。

NavigateTo

便利機能です。Ctrlキーを押しながらカンマキーを押すと起動できます。 起動したら、型名とか関数名とか適当にタイプしてみてください。そういうことです。

フォルダ機能

最近使ってません。 プロジェクトが巨大になればお世話になることでしょう。

openしていない名前空間の解決

便利機能です。Hogeを含むモジュールがあった時に、Hoge.piyoとかやってHogeまでカーソルを持っていくと、スマートタグが表れるのでCtrl+.で起動してEnterです。

未使用宣言のグレーアウト

使っていない変数や関数などをグレーアウトしてくれます。 不安定なので現状ではOffにしておくといいでしょう。 次のバージョンではもうちょっと改善される予定です。

未使用openのグレーアウト

使っていないopenをグレーアウトしてくれます。 不安定なので現状ではOffにしておくといいでしょう。 次のバージョンではもうちょっと改善される予定です。

メタデータに移動

便利機能です。ソースを持っていないコードのシグネチャ等を確認する際、この機能がない時代はオブジェクトブラウザを使ったりしていましたが、F12で確認できるようになりました。

タスクリストコメント

便利機能です。この機能は実装にも関わったので、思い入れもある機能です。 タスク一覧ウィンドウを開いて、(ユーザー タスクではなく)「コメント」を選ぶと、TODOコメントなどが一覧できます。

オプションからカスタマイズ可能で、「環境」、「タスク一覧」と辿ると設定画面になります。 「名前」の欄に「XXX」とか入れて、追加ボタンを押すとトークンリストに追加されます。 このUIは非常にわかりにくいと思うんですが、まぁVS標準の機能だし仕方ない。

VFPTの今後

次のバージョン

次のバージョンでは、細かい点が修正され、目立った新機能は追加されないようです。

  • New folderダイアログがリストアされない問題を修正
  • 大きなソリューションでの「全ての参照の検索」機能のパフォーマンスを改善
  • Goto Metadataで表示されるコードにエラーが表示される問題のいくつかを修正
  • これ以上自動生成するものがないスマートタグを表示しないように変更
  • printf系関数のフォーマットの色付けをユーザ定義のものまで拡張
  • ソースが存在する構造体への移動がメタデータへの移動になる問題を修正

今後載るかもしれない機能

コメントの折り畳み機能がもしかしたら載るかもしれないです。

機能追加の要望方法

GithubのIssuesではなく、VFPTのUser Voiceに投稿してください。 Voteもできるので、気になった機能があればVoteするといいでしょう。 幾つかピックアップして紹介します。

クラスビューでのコードナビゲーション

Feaure Request: Code Navigation Using Class View で提案されています。

ナビゲーションバーは一つのファイルであれば便利だけど、クラスビューであればプロジェクト全体をナビゲートできて便利だよね、という提案です。

ローカルで閉じたIDisposableでletを使っていた場合にuseを使うよう提案

Suggest to use use instead of let for local IDisposable valuesで提案されています。

IDisposableを実装した型にletを使っていた場合、それをuseを使うように提案してくれるような機能の提案です。 実装されれば有用そうですが、ローカルで閉じているかどうかのチェックって簡単にできるんでしょうか・・・

式の型の表示

Show type annotation on expressionで提案されています。

Scala-plugin for Intellij ideaで実装されている機能のようですね。 あると便利なのは間違いないでしょう。

推論された型の表示

Display inferred types above declarationで提案されています。

コンパイラによって推論された型を、定義の上に(CodeLensのように)表示する機能の提案です。 コメントでは結構散々な言われようですが、これ、実装されればめちゃくちゃ便利だと思うんですよ。

CodeLens APIが公開されていないようなので、CodeLensっぽくできるかどうかわからないかつ、公開されたとしても関数内関数は結局自作になりそうなので、全部自作してしまえばいいじゃん、と思うんですが果たして。 エディタ上にCodeLensのように行じゃない行(?)を差し込めるのかどうかが問題だと思うんですが、教えてVS拡張に詳しい人!

まとめ

VFPTを使いこなして楽しくF#でプログラミングしましょう!

*1:これも実はVFPTの機能で、生のVSだとF#でタスクリストすら使えません。

*2:再起動自体はほかの機能も必要ですが、管理者権限でVSを立ち上げる必要があるのはこの機能のみです

コンピュテーション式の変形後を覗き見るを改良する

さてさて、気になるネタがまた増えたようです。 コンピュテーション式の変形後を覗き見るというエントリがF# Advent Calendar 2014の10日目の記事として公開されました。 Quoteを差し込んで、Runで表示する、というのは面白いアイディアですね。

だが俺の前でコンピュテーション式の変換を扱ったのが運の尽きだ! 添削をくらうがいい!

・・・同僚だからってやりたい放題ですね。

変換に必要なメソッド

変換対象が

let ret = simple {
  let x = 10
  return x
}

程度であれば、必要なメソッドReturnだけです。 BindReturnFromも不要です。

ですが、消してしまってはあまりにも単純なので、 Bindを使う例を扱えるようにしましょう!

変換対象はこうなります。

let res = simple {
  let! x = 10
  return x
}

このコードがコンパイルできるようにするために必要なコンピュテーションビルダーの定義はこれです。

type SimpleBuilder () =
  member __.Return(x) = x
  member __.Bind(x, f) = f x

let simple = SimpleBuilder ()

手動で変換する

では、このコードを変換していきます。

let res = simple {
  let! x = 10
  return x
}

一番外側はいいでしょう。

let res =
  let b = simple in
  {| let! x = 10 in return x |}

let!の変換

{||}に挟まれた部分(cexpr)がlet! ...という形をしているので、 let!の変換規則を最初に適用します。

let b = simple in
T(return x, fun v -> C(b.Bind(10, fun x -> v)))

Bindの呼び出しが出てきました。

returnの変換

Tの最初の引数(変換対象)がreturn xという形をしているので、 returnの変換規則を適用します。

let b = simple in
b.Bind(10, fun x -> b.Return(x))

Returnの呼び出しが出てきました。 これ以上変換すべき個所はありませんので、手動変換はここまでです。 ビルダーにはBindReturnも適切に定義されているため、コンパイル可能です。

変換を出力する

さて、元の記事ではSimpleBuilderを弄って変換の様子を出力するようにしていました。 同じ方法だと面白くないので、この記事では別の方法を取ってみることにします。 その前に、Quoteについて突っ込んでおきます。

Quote変換

Quoteは元記事でも使っていますが、その定義は

member this.Quote(x) = x

と、あたかも式木がxとして渡されてそれをそのまま返しているように見えます。 しかし、実際はQuoteメソッドは呼び出されることはありません。 ただ単に、Quoteという名前で定義されてさえいればいいのです。 そのため、紛らわしさを無くす意味でもQuoteの実装は、

member __.Quote() = ()

としておいた方がいいと思います。

コンピュテーションビルダーに機能を後付けする

さて、コンピュテーションビルダーに対する機能の追加ですが、方法としては以下のものがあるでしょう。

  1. コンピュテーションビルダーの書き換え
  2. 既存のコンピュテーションビルダーを継承して機能を追加
  3. 型拡張として機能を追加
  4. 拡張メソッドとして機能を追加

1つ目の方法が元記事のやり方です。 一番シンプルですが、コードがない場合にはこの方法は選べません。

2つ目の方法は、面倒ですしそれほど説明すべき点もないので省略します。

3つ目と4つ目の方法を今回紹介します。

型拡張として機能を追加

コンピュテーション式の変換は、コードの表現しか見ておらず、 実現方法は問題にしていません*1。 そのため、ビルダーに対してメソッドを呼び出せるようになっていれば、 コンピュテーションビルダーにメソッドが定義されている必要はありません。

型拡張でQuote機能を追加する例を見てみましょう。

open FSharp.Quotations.Evaluator

type SimpleBuilder with
  member __.Quote () = ()
  member __.Run(expr: Quotations.Expr<_>) =
    // ここにexprを出力するコードを書く

    // 式木を実行するために、FSharp.Quotations.Evaluatorを使用
    expr.Evaluate()

この型拡張を適当なモジュールに入れ、 そのモジュールをopenするかどうかでexprを出力するかどうかを切り替えれるようになります。 しかし、ビルダーにQuoteメソッドを追加してRunの引数の型をExpr<_>型にしてしまい、 Runメソッドの中で引数をそのまま返してしまうと、 コンピュテーション式利用側はExpr<_>を受け取ることになってしまいます。 これでは、元のコードをコンパイルできなくなってしまいます。

そこで、FSharp.Quotations.Evaluatorというライブラリを使います。 このライブラリは、F#のコードクォートを実行するためのライブラリです。 Runメソッドの最後でEvaluateを呼び出し、 QuoteExpr<_>にしたものをまた戻します*2

拡張メソッドとして機能を追加

型拡張ではなく、もちろん拡張メソッドとして定義することもできます。

open System.Runtime.CompilerServices
open FSharp.Quotations.Evaluator

[<Extension>]
type SimpleBuilderExtension private () =
  [<Extension>]
  static member Quote (_: SimpleBuilder) = ()
  [<Extension>]
  static member Run(_: SimpleBuilder, expr: Quotations.Expr<_>) =
    // ここにexprを出力するコードを書く

    // 式木を実行するために、FSharp.Quotations.Evaluatorを使用
    expr.Evaluate()

少々面倒なので、型拡張でいいですね。 一応、できるということで。

exprを出力するコード

少し改良していますが、基本的には元の記事と同じです。

open Microsoft.FSharp.Quotations.Patterns
open System.Collections.Generic

module Printer =
  let mutable private i = 0
  let private valueMap = Dictionary<obj, string>()
  let printKnownVar obj =
    if obj = box simple then
      "simple"
    else
      "unknown_var"
  let printValues () =
    valueMap
    |> Seq.sortBy (fun kv -> kv.Value.Substring(2) |> int)
    |> Seq.map (fun kv -> sprintf "let %s: %s = %s" kv.Value (kv.Key.GetType().Name) (printKnownVar kv.Key))
  let rec print (expr: Quotations.Expr) =
    match expr with
    | Call (Some receiver, m, args) ->
        (* インスタンスメソッド呼び出しを文字列化 *)
        let receiver = print receiver
        let args = List.map print args |> String.concat ", " (* reduceじゃなくてconcatでOK *)
        sprintf "%s.%s(%s)" receiver m.Name args
    | Value (obj, typ) ->
        (* 値を文字列化 *)
        if typ = typeof<SimpleBuilder> then
          (* 値が辞書にあったらそれを変数として扱うように変更 *)
          match valueMap.TryGetValue(obj) with
          | true, name -> name
          | false, _ ->
              let name = sprintf "$b%d" i
              i <- i + 1
              valueMap.Add(obj, name)
              name
        else
          string obj
    | Lambda (arg, Let(var, value, body)) when arg.Name = print value ->
        (* 単純なラムダ式を文字列化 *)
        (* Expr<_>の表現力の問題によって、Lambdaではラムダ式すべてを表現できない。
           例えば、ラムダ式の引数部分でパターンマッチできるが、
           Expr<_>のLambdaはargがVarなので、Letと組み合わせることでこれを再現している模様。 *)
        let arg = var.Name
        let body = print body
        sprintf "(fun %s -> %s)" arg body
    | Var v ->
        (* 変数を文字列化 *)
        v.Name
    | _ ->
        (* 対応できない式木はエラーにするように変更 *)
        failwithf "%A is not supported." expr

type SimpleBuilder with
  member __.Quote () = ()
  member __.Run(expr: Quotations.Expr<_>) =
    let exprStr = Printer.print expr
    let values = Printer.printValues ()
    values |> Seq.iter (printfn "%s")
    printfn "%s" exprStr
    expr.Evaluate()

改良した点としては、

  • 変数を出力するようにした
  • 実装を洗練
  • Runの結果を変えないように、式木を実行するようにした

というところです。

実行してみる

拡張を施したモジュールをopenすると、既存のコンピュテーション式の挙動が変わり、 Printer.printの結果が出力されるようになります。

let res = simple {
  let! x = 10
  return x
}

このコードを実行してみると、このような出力が得られます。

let $b0: SimpleBuilder = simple
$b0.Bind(10, (fun x -> $b0.Return(x)))

結果比較

手動変換した結果はこうでした。

let b = simple in
b.Bind(10, fun x -> b.Return(x))

そして、QuoteによってExpr<_>にしたものを文字列化したのがこれです。

let $b0: SimpleBuilder = simple
$b0.Bind(10, (fun x -> $b0.Return(x)))

完全に一致!!!

まとめ

コンピュテーション式がどんな風に変換されるのか見るために、 Quoteで式木化してF#コードに文字列化するのはいいアイディアでした。 この記事では、以下のことをやりました。

  • Bindを必要な例に変更
  • 既存のコンピュテーションビルダーに機能を追加する方法
  • 出力されるコードに何も手を加えなくても手動変換の結果とほとんど同じになるようなExpr<_>の出力

Expr<_>の限界*3はありますが、 これを推し進めれば、かなり良い感じに色々なコンピュテーション式の変換結果を出力できるようになりそうです。

今年もF# Advent Calendarは豊作ですね!素晴らしいです。

*1:ダックタイピング的、とも言えるでしょう。

*2:Delayを定義している場合は、更にユニットを渡す必要があります。

*3:match式がIfThenElseに落ちるなど

クラスのコンストラクタ呼び出し時にプロパティも同時に初期化する

これはF# Advent Calendar 2014の9日目の記事(ピンチヒッター)です。 昨日の記事はねのさんによるFSharperならこう書く(A Poker Game Program) #FsAdventでした。 この記事を勝手に添削したエントリも、よろしければ合わせてどうぞ。 何度も言いますが、コードが読みやすくて書き換えが大変楽でした。

さて、今日はF# のネタリストから、クラスのコンストラクタ呼び出し時にプロパティも同時に初期化する方法です。 Advent Calendarのネタに困っている人は、参考にしてみるといいかもしれません。

F#のプロパティ

F#のプロパティの定義は、複数の書き方があります。 C#の自動実装プロパティに初期値を持たせた書き方もサポートしていますので、今回はそれを使いましょう。

type Class(x: int) =
  member val Name = "" with get, set

これで、C#での

public class Class
{
    readonly int x;
    public string Name { get; set; }

    public Class(int x)
    {
        this.x = x;
        this.Name = "";
    }
}

に大体対応します。ヤバい短さですね。

コンストラクタ呼び出し時にプロパティも同時に初期化したい

上のように作ったクラスを生成する際に、C#ではオブジェクト初期化子という構文を使って、プロパティも同時に設定できます。

var c = new Class(10) { Name = "hoge" };

便利ですね! 実は(?)、これと同じことがF#でもできます。

let c = Class(10, Name="hoge")

これだけです!

これは、F#での名前付き引数とまったく同じ構文ですね。 なので、当然(?)これらは混ぜることができます。

let c1 = Class(x=10, Name="hoge")
let c2 = Class(Name="hoge", x=10)

素晴らしい!

本日は以上です。 明日は、ざっきーさんの番ですね!

Re: FSharperならこう書く(A Poker Game Program) #FsAdvent

FSharperならこう書く(A Poker Game Program) #FsAdventというエントリがF# Advent Calendar 2014の8日目の記事として公開されました。 この記事自体は、主に設計よりな話をしていますが、ここでは実装寄りの話を中心に、 勝手に添削してみました。 また、記事の中に書かれていた疑問にも回答したりしています。

添削その1(全体)

型とモジュールの名前

元のコードでは、モジュールの中にTという型を入れるスタイルのようです。

(* 柄 *)
module Suit =
    (* 柄 *)
    type T = | Spade | Heart | Diamond | Club
    (* 全ての柄 *)
    let values = [Spade; Heart; Diamond; Club]

自分も昔はこんな感じにしていたのですが、現在のところこのスタイルはやめ、モジュール名と型名に同じ名前を付けるスタイルを採用しています。

type Suit = Spade | Heart | Diamond | Club

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Suit =
  let values = [Spade; Heart; Diamond; Club]

なんでかというと、

  • F#には高度なモジュールシステムがないので、あまり意味がない
  • 同じ型名が(モジュールが別だとしても)複数あると、複数型推論が微妙になる

あたりが大きな理由です。 CompilationRepresentationとかCompilationRepresentationFlags.ModuleSuffixとか面倒ですが、覚えてしまえばどうということはないですよ。

回答その1(Card)

レコード生成の制限

こんな疑問が書かれていました。

番号は、1から13に含まれない値は生成できないようにしたいので、classになりそうです。 classにするとinterfaceの実装が煩わしいところですが、constructorを制限しようと思うと、レコードや判別共用体では厳しそうです。 もし他に良い方法をご存知の方がいらっしゃったら教えてください。

これに対する回答としては、2通りあります。

  • シグネチャファイルで型の実装を隠蔽する
  • privateで型の実装を隠蔽する

前者はfsiファイルを書かなければならず、面倒というのと、型の実装を隠蔽してしまうとcomparison制約とかも自分でやる必要が出てきてそれも面倒なので、今回はお手軽にprivateで隠蔽する方法を紹介します。 元のコードはこんな感じです。

(* 番号 *)
module Number =
    type T(value:int)=
        do
            (* 数値範囲チェック *)
            if not (min <= value && value <= max)
                then failwith "The Number is required 1 to 13."

これを、型名を変更しつつレコードで実現してみましょう。

type Number = private {
  Value: int
}

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Number =
  let min = 1
  let max = 13
  let value x = x.Value
  let create x =
    (* 数値範囲チェックをこちらに書く *)
    if not (min <= x && x <= max) then
      failwith "The Number is required 1 to 13."
    { Value = x }

こうすることによって、それを含むモジュール(ここではファイルと思ってください)の外からは{ Value = 42 }のようには書けなくなりますし、当然let f (x: Number) = x.Valueというコードもコンパイルエラーになります。 パターンマッチも(それを含むモジュールの外では)できなくなるので注意してくださいね。

IComparable

レコードにしたため、IComparable等の実装が不要になりましたが、元のバージョンではこれらを実装していました。 さて、これらを実装しているのはいいのですが、実は大小比較だけしたいだけであれば実装するインターフェイスはかなり減らせます。

type T(value: int) =
  (* snip *)
  member this.Value = value
  interface System.IComparable with
    member this.CompareTo(other) =
      match other with
      | :? T as x -> compare value x.Value
      | _ -> failwith "other is not T"
  (* snip *)

これだけで、警告は出るものの=<も他の比較演算子も使えるクラスになります。 このあたりは、言語仕様(PDF)の8.15.3を参照してください。 ざっくり説明すると、System.IComparableを明示的に実装している場合、Equalsメソッド

override x.Equals(y : obj) =
  ((x :> System.IComparable).CompareTo(y) = 0)

という実装が生成されるという感じです。 CompareToジェネリック版ではなく非ジェネリック版が呼び出されるので、System.IComparableだけ実装すればよい、ということですね。 また、compare関数を使えばCompareToメソッドは簡単に実装できます。

ただし、これに頼る場合は警告が出るみたいです。言語仕様にはその記述はなかったように思いますが・・・

添削その2(Deck)

単純なfor式

山札を表すDeckモジュールのdeck_without_joker関数ですが、一番内側のfor式はyieldしか含みません。

(* デッキ(ジョーカー抜き) *)
let deck_without_joker = List.ofSeq(seq{
        (* シーケンス式の二重ループでNumberとSuitの組み合わせを列挙する *)
        for num in Card.Number.min .. Card.Number.max do
        for suit in Card.Suit.values do
            yield { Card.Number = Card.Number.create num; Card.Suit = suit }
    })

このような場合、do yieldの代わりに->が使えます。 名前もlikeThis形式に変えておきましょう。

let deckWithoutJoker =
  List.ofSeq (seq {
    for num in Card.Number.min .. Card.Number.max do
    for suit in Card.Suit.values ->
      { Card.Number = Card.Number.create num; Card.Suit = suit }
  })

添削その3(List)

take関数の名前

既存のListモジュールに足りない処理を追加するために、独自のListモジュールを作っています。 その中で、takeという関数を追加しているんですが、これは標準のtakeを隠してしまううえ、やっていることもtakeではない気がします。

やっていることは、countでのリストの分割ですので、splitAtがいいでしょう。 名前は、Hoogleで探しました。 また、optionを返す関数はF#ではtryプレフィックスを付けるのが慣習ですので、trySplitAtとしてみました。

let trySplitAt count list =
  let rec loop i acc tail =
    if i = count then Some ((List.rev acc), tail)
    else match tail with
         | [] -> None
         | h::t -> loop (i + 1) (h::acc) t
  loop 0 [] list

tail |> functionとしている部分は、好みもありますが、個人的にはmatch tail withの方が好きなのでその部分も変更してあります。

また、Deck.pickoptionを返すので、Deck.tryPickにしました。

回答その2(Hand)

単一ラベルの判別共用体

こんな疑問がありました。

単一ラベルの判別共用体ってどうなのでしょうか。

これは、ありだと思います。 FsControlを使うと、単一ラベルの判別共用体を多用するようになります(そうじゃない)。

添削その4(Hand)

System.String.Join

.NETの便利関数にSystem.String.Joinというのがあるのですが、 これはBasis.CoreのStr.joinを使いましょう。 嘘です。Stringモジュールのconcat関数を使いましょう。

ToString

ToStringの呼び出しは面倒なので、string関数を使いましょう。 コードは2つ下を見てください。

List.toArray呼び出し

String.concat'T seqを受け取り、'T list'T seqなのでtoArrayする必要はありません。 コードは1つ下を見てください。

逆向きパイプライン演算子

ToStringの実装で、逆向きのパイプライン演算子を使っています。

(* CUIの選択肢表示用ToString *)
member this.ToString indexes = this |> function
    | Hand xs -> 
        xs
            |> List.map2 (fun x y ->
                    sprintf "[%s]:%s" y (x.ToString())
                ) <| indexes (* 逆向きパイプライン演算子<|はロマン *)
            |> List.toArray
            |> curry System.String.Join "/"

ここは、||>の出番です。

member this.ToString indexes =
  match this with
  | Hand xs ->
      (xs, indexes)
      ||> List.map2 (fun x y -> sprintf "[%s]:%s" y (string x))
      |> String.concat "/"

あ、curry関数は使わなくなったので削除しました。

引数の位置でのパターンマッチ

createBy関数やchange関数で、レコードや判別共用体のパターンマッチをしている部分が何か所かあります。

(* 山札から最初のcount枚を取得して手札を作り、残りの山札とペアにして返す。
  山札の枚数が足りない場合はNone
*)
let createBy deck =
    Deck.pick count deck
        |> Option.map (fun pickResult -> 
            (* let束縛のレコードパターン *)
            let { Deck.Picked = picked; Deck.Deck = d } = pickResult
            ((Hand picked), d)
        )

(* 手札のうちchangeCardsで指定されたカードを捨て、山札から同じ枚数を加え、残りの山札とペアにして返す。 *)
let change hand deck changeCards =
    Deck.pick (Set.count changeCards) deck
        |> Option.map (fun pickResult ->
            let { Deck.Picked = picked; Deck.Deck = d } = pickResult
            let hand = hand |> function
                | Hand xs -> xs |> List.filter (changeCards.Contains>>not)
            Hand (List.append picked hand), d
        )

これらは、引数の位置でパターンマッチすればいいので、こう書き直せます。

let tryCreateBy deck =
  Deck.tryPick count deck
  |> Option.map (fun { Deck.Picked = picked; Deck.Deck = d } -> ((Hand picked), d))
                  (* ↑ラムダ式の引数の位置でレコードを分解 *)

        (* ↓関数の引数の位置で判別共用体を分解 *)
let change (Hand xs) deck changeCards =
  Deck.tryPick (Set.count changeCards) deck
  |> Option.map (fun { Deck.Picked = picked; Deck.Deck = d } ->
                  (* ↑ラムダ式の引数の位置でレコードを分解 *)
      let hand = xs |> List.filter (changeCards.Contains >> not)
      Hand (List.append picked hand), d
  )

添削その5(Rank of Hands)

役の強弱

F#の話ではないですが、役の強弱がおかしい気がしますね。 一般的なルールに従って、修正します。

type Rank =
  | OnePair | TwoPair | ThreeCards | Straight
  | Flush | FullHouse | FourCards | StraightFlush | RoyalStraightFlush

型の別名

役判定関数モジュールで、Card.NormalCard arrayという型名を大量に使っています。 こういう場合、型略称を定義してしまうと楽でしょう。

type private Cards = Card.NormalCard array

pairwise

straightflushの判定に、前のカードと今のカードをペアにしたシーケンスを得るために、

xs
    |> Seq.ofArray
    |> (fun xs -> Seq.zip xs (Seq.skip 1 xs))
    |> Seq.forall (fun t -> (fst t).Number ++ 1 = (snd t).Number)

というコードが出てきます。 これは、Seq.pairwiseというそのままな関数があるので置き換えてしまいましょう。

cards
|> Seq.pairwise
|> Seq.forall (fun t -> (fst t).Number ++ 1 = (snd t).Number)

タプルを引数の位置で分解する

上のコードはラムダ式の中でfstsndを使っているので、これを引数の位置でのパターンマッチにしてしまいましょう。

cards
|> Seq.pairwise
|> Seq.forall (fun (x, y) -> x.Number ++ 1 = y.Number)

配列 vs リスト

型略称を用意したCardsは現在、配列です。 リストではなく配列にしたのは、

引数の型を配列にしたのは、インデックスでのアクセスが多そうだったから、という程度の理由です。

とありました。 しかし、リストでもインデックスでアクセスできるうえ、引数として渡される配列もしくはリストの要素数は、5です。 この部分がクリティカルに効いてくるとは思えないので、リストに変更しました。

type private Cards = Card.NormalCard list

もし問題が起こったら、この型略称の部分を戻して、呼び出し部分の型を変えればいいでしょう。

SequenceEqual

royalStraightFlushの判定で、System.Linq.Enumerable.SequenceEqualを使って判定していますが、 Listをそのまま扱えば=で比較できるので、変更してしまいましょう。

let royalStraightFlush (cards: Cards) =
  (straightFlush cards) && (
    let royal = [10..13]@[1]
    (cards |> List.map (fun x -> x.Number |> Card.Number.value)) = royal
  )

List.filter Option.isSomeしてList.map Option.get

Evaluator.evaluate等の関数で、List.filter Option.isSomeしてからList.map Option.getしている箇所があります。

evals
    |> List.map (fun f -> f x)
    |> List.filter Option.isSome
    |> List.map Option.get

こういった処理には、List.chooseという関数が使えます。

evals
|> List.map (fun f -> f x)
|> List.choose id

回答その3(Rank of Hands)

判別共用体の大小

判別共用体の大小に関して、

大小比較が定義順に依存するのはちょっとしたポイントです。ソースは忘れました。

とありました。 言語仕様を参照してみると、8.15.4に、

If T is a union type, invoke Microsoft.FSharp.Core.Operators.compare first on the index of the union cases for the two values, and then on each corresponding field pair of x and y for the data carried by the union case. Return the first non-zero result.

とあります。

  • 2つの値の共用体ケースのインデックスがMicrosoft.FSharp.Core.Operators.compareに渡される
  • 付随するデータがある場合、それらがMicrosoft.FSharp.Core.Operators.compareに渡される
  • 0ではない最初の値が返される

ということのようですね。 共用体ケースのインデックスは、定義順に振られる*1ため、大小比較が定義順に依存するのです。

添削その6(メイン処理)

リストの生成

input_choicesの生成に、リストリテラルを使っています。

let input_choices = ["a"; "b"; "c"; "d"; "e"]

好みはわかれるかもしれませんが、ここは

let inputChoices = ['a'..'e'] |> List.map string

のように、文字のリストを..で作ってから文字列化したほうが楽な気がします。

まとめ

テストとかしてない*2ので動くかどうかわからないですが、コード全体はGistに上げました。

元のコードがとても読みやすく素晴らしかったです。 引き続き、F# Advent Calendarをお楽しみください!

*1:言語仕様の8.5.4にTagsというネストした型が作られ、0から始まる整数定数が振られる、というようなことが書いてあります

*2:元のコードを書き換えていっただけ。変換の過程でバグが入った可能性は大。

NGK2014Bで発表してきた

NGK2014Bで、FCellという製品紹介の発表をしてきました。 詳細は、発表資料よりもその中で紹介しているデモを参考にしてください*1

FCellと類似した(?)ものとしては以下のようなものがあります。

COMは置いといて、そのほかのものとFCellがどう違うかということを軽く説明しますね。

NPOIやEPPlusとの違い

NPOIやEPPlusは、基本的に「Excelを外から操る」ためのライブラリです。 こいつらはこいつらで便利なんですが、「Excelを中から操る」ことはできないので、こいつらで置き換え可能なVBAというのはほんの一部なんですね。

それに対してFCellは、ユーザ定義関数(UDF)をF#*2で定義できるのです。 そして定義したUDFは当然のようにExcel内で使えます。

Excel DNA

Excel DNAも、F#*3でUDFを定義できます。 しかし、Excel DNAは「Excelの外部」で動くものであり、Excelファイルにコードを埋め込めないです。

それに対してFCellは、Excelファイルにコードを埋め込めます。 埋め込むという選択肢のみであればVBAと変わらないのですが、埋め込まずに外に出す、ということもできます。

VSTO

VSTOは、UDFを定義する程度のものにはフォーカスしておらず、アドイン開発に特化したものという認識です。 そのため、小回りが利かないという欠点を持ちます。 また、Excelファイルにコードを埋め込むのもできないようです。

それに対してFCellは、UDFを定義する部分から、UIをカスタマイズする部分までをターゲットにしているため、非常に小回りが利きます。 F#を利用する場合であれば、Excelに統合されたF#エディタが使える*4ため、VBAと同じ感覚で使えるのが大きな利点です。

この辺りは、棲み分けの話だと思うのですが、VSTOは中規模から大規模なものに、FCellは小規模から中規模なものに使うのがいいのでは、と思います。

VSTOには詳しくないので間違ったことを言っているかもしれないです。間違っていたら指摘ください。

FCellに足りない部分

VBAの最大のメリットは、ユーザの操作を記録してそれをマクロとして使いまわしたり、生成されたコードをベースに開発したりできる点にあると思っています。 FCellは現状それができませんので、Excel利用者のものというよりはやはり開発者のためのものになっています。 今後、これが改善されてExcel利用者も気軽にFCellが使えるようになれば、と願っています。 お金の話は置いといて。

*1:ちなみに、当日はでも動画を流したのではなく、自分で作ったデモを実際に動かしました

*2:2.0からは、C#VBといった言語もサポートするようになりました

*3:もちろんC#VBも使えます

*4:補完機能やエラー表示機能も持っています

実例に見るSource変換活用術

これはF# Advent Calendar 2014の5日目の記事です。 昨日の記事はyukitosさんによるF# Project Scaffoldを使ってプロジェクトを作成するでした。 F# Project Scaffold、便利そうですね。 Chocolateyみたいに導入が簡単だと、もっといい感じになるような気がします。

さて、コンピュテーション式おじさんです、こんにちは。 最近はPersimmonというF#用のテスティングフレームワークを作っています。 Persimmon自体はまだベータ版ですが、今回はこのプロジェクトで得た知見の一つを紹介したいと思います。 誰得エントリですね。

続行可能なアサーション

Persimmonでのアサーションは、続行可能なアサーションと続行不可能なアサーションに分類されます。 例えば、

test "sample test" {
  do! assertEquals x y
  do! assertEquals a b
}

とあった時、最初のアサーションに通らなくても次のアサーションは実行できるとうれしいです。

それに対して、

let originalAssert x y = test "" {
  do! assertEquals x y
  return x
}

test "sample test" {
  let! b = originalAssert x y
  do! assertEquals a b
}

とあった時、二番目のアサーションは一番目のアサーションの結果を使っていますから、次のアサーションは実行しようがありません。

これを実現するためにはどうすればいいでしょうか?

Bindのオーバーロード

最初に考えたのは、Bindオーバーロードしてしまうというものでした。 その時に書いたのが以下のコードです。

アイディアとしては、Bindの第二引数として渡される関数(後続の処理)が必要としている型がunitかそれ以外かでBindを分けてしまう、というものです。 しかしこれは、コメントにもあるようにコンパイルエラーになってしまいます。

Sourceメソッドオーバーロードによる疑似的なBindのオーバーロードの実現

let!(やdo!)は、コンパイラによってBindメソッドに変換されますが、 ビルダーにSourceメソッドが定義されている場合、第一引数がSourceに渡されます。

Sourceがある場合のlet!の変換

T(let! p = e in ce, C) = T(ce, fun v -> C(b.Bind(b.Source(e), fun p -> v)))

Sourceオーバーロードし、中で同じ型にしてしまえばBindオーバーロードを間接的に実現できそうです。

やってみた

以下、単純化した例です。

type BindingValue<'T> =
  | UnitValue of 'T (* always unit *)
  | NonUnitValue of 'T

type SomeBuilder() =
  member __.Source(x: unit) = UnitValue x
  member __.Source(x: _) = NonUnitValue x
  member __.Bind(x: BindingValue<'T>, f) =
    match x with
    | UnitValue x ->
        (* unitの場合の処理を書く
           Persimmonの場合、アサーションに通らなくても以降の処理を続行するようなコードになっている
           アサーションの結果は、BindingValueが直接'Tを保持するのではなく、
           AssertionResult<'T>を保持することで持ちまわすようにしている *)
        assert (typeof<'T> = typeof<unit>)
        (* ここで直接()を渡してしまうと、fがunit -> 'Uのように推論されてしまうので、
           Unchecked.defaultof<'T>を使うことで回避 *)
        f Unchecked.defaultof<'T>
    | NonUnitValue x ->
        (* unit以外の場合の処理を書く
           Persimmonの場合、アサーションに通らない場合は以降の処理を続行せずに結果を返し、
           アサーションを通っていた場合は後続の処理を続行するようなコードになっている *)
        f x

このように、Sourceメソッドの引数で分岐し、戻り値をBindingValueという同じ型にするようなオーバーロードを用意することで、 Bindメソッドの中で処理を分岐できるようになります。 ちなみにreturn!を提供する場合、ReturnFromでもSourceメソッドが呼び出されるため、ReturnFromメソッドの中でBindingValueを剥いでやる必要があります。

このように、Sourceメソッドを使うとBindオーバーロードが疑似的に実現できます。

Persimmonのほかのコンピュテーション式

Persimmonでは、他にもパラメタライズテスト用のコンピュテーション式と、例外のテスト用のコンピュテーション式を用意しています。 パラメタライズドテスト用のコンピュテーション式は、シンプルなカスタムオペレーションの実装例となっています。 例外のテスト用のコンピュテーション式は、シンプルな割に便利なものになっています。 興味のある人は見てみるといいでしょう。

みなさんも、PersimmonやBasis.Coreのコンピュテーション式を参考に、 色々なコンピュテーション式を作ってみましょう!

参考URL

明日のF# Advent Calendar

日本語版はkos59125さん、英語版はSteve Shogrenさんです。 Steve Shogrenさんは「F# Polymorphism」とありますね。楽しみです!