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

Twitterで流れてきたListReaderコンピュテーション式の解説

F#

先日、Twitterid:n7shi さんが面白いコードを投下していた。

これに対しての私のreplyが以下。

これらのコンピュテーション式はちょっとわかりづらいのでちょっとした解説を試みる。

7shiさん版ListReaderBuilder

7shiさんのコードに少し手を加えたものが以下のコードである。 元コードとの挙動に差はない。

type ListReaderBuilder() =
  member __.Bind(_, f) = function
    | x::xs -> f x xs
    | [] -> ()
  member __.Zero() = fun _ -> ()
let listReader = ListReaderBuilder()
 
let test = listReader {
  let! a = () in printfn "%d" a
  let! a = () in printfn "%d" a
}
 
test [1;2;3]
printfn "---"
test [4]

このコードの出力は以下のようになる。

1
2
---
4

このビルダーで使われる変換規則は以下の通り。

  1. T(let! p = e in ce, V, C, q) = T(ce, V ⊕ var(p), λv.C(b.Bind(src(e),fun p -> v), q)
  2. T(do e in ce, V, C, q) = T(ce, V, v.C(e; v), q)
  3. T(e;, V, C, q) = C(e;b.Zero())

testを展開すると以下のようになるだろう(実際はもっと異なるかもしれないが、私はコンパイラではないのでわからない)。

// val test: int list -> unit
let test =
  // let! a = () in ... の展開
  listReader.Bind(
    (),
    fun a ->
      // do printfn "%d" a in ... の展開
      printfn "%d" a
      // let! a = () in ... の展開
      listReader.Bind(
        (),
        fun a ->
          // printfn "%d" a; の展開
          printfn "%d" a
          listReader.Zero()
      )
  )
  • Bindメソッドのシグネチャ'T * ('U -> 'U list -> unit) -> ('U list -> unit)
  • Zeroメソッドのシグネチャunit -> ('a -> unit) ((Bindの型パラメータと異なることを強調したかったのであえて'a表記にしている))
    • つまり、分解され残ったlistはZeroで返されるλ式によって捨てられる
    • testでいうと、2回しかBindを呼んでいないため、残った[3]がZeroで返るλ式に渡される
  • printfn部分でaはintに固定される

ことを考えれば、型に問題はないことに気づくだろう。

考察

面白いコードだが、個人的に気になったことがあった。

let! a = () in ...がなんだか冗長に見えるのだ。 do! ...のほうが短くて見やすい気がする。

いじってみた版

type ListReaderBuilder() =
  member __.Bind(g, f) = function
    | x::xs -> f (g x) xs
    | [] -> ()
  member __.Return(_) = fun _ -> ()
let listReader = ListReaderBuilder()
 
let test = listReader {
  do! printfn "%d"
  do! printfn "%d"
}

このビルダーで使われる変換規則は以下の通り。

  1. T(let! p = e in ce, V, C, q) = T(ce, V ⊕ var(p), λv.C(b.Bind(src(e),fun p -> v), q)
  2. T(do! e in ce, V, C, q) = T(let! () = e in ce, V, C, q)
  3. T(do! e;, V, C, q) = T(let! () = src(e) in b.Return(), V, C, q)

同じようにtestを展開してみる。

// val test: int list -> unit
let test =
  // do! printfn "%d" in ... の展開
  listReader.Bind(
    (printfn "%d"),
    fun () ->
      // do! printfn "%d"; の展開
      listReader.Bind(
        (printfn "%d"),
        fun () -> listReader.Return(())
      )
  )
  • Bindメソッドのシグネチャ('T -> 'U) * ('U -> 'T list -> unit) -> ('T list -> unit)
  • Returnメソッドのシグネチャ'a -> ('b -> unit)
    • 最終的に残ったリストはReturnが返すλ式で捨てられる
  • g xprintfn "%d" xが実行され、その戻り値unitfに渡る

こちらも型は問題ないことがわかるだろう。

やりたいことができるか試す

その後の7shiさんとのやり取りで、以下のようなことがやりたかったというコメントを頂いた。

不揃いなデータをコンピュテーション式で処理 - Qiita

改変後のコードでも同様の挙動にできるか試してみよう。

ビルダーとaddPersonシグネチャの順序、コンピュテーション式を書き換える。

// 変更したコードのみ載せています
type ListReaderBuilder() =
  member __.Bind(g, f) = function
    | x::xs -> f (g x) xs
    | [] -> ()
  member __.Return(_) = fun _ -> ()

...

let addPerson data name = if name <> "" then persons.[name] <- data

...

        row |> listReader {
            let! company = id
            do! addPerson (company, "会長")
            do! addPerson (company, "社長")
            do! addPerson (company, "副社長")
            do! addPerson (company, "専務")
        }

...

最初の値は他の場所で使いたいのでidで取得する。 残りはprintfnの時と同じ要領だ。

実際に試してみると同じ結果を返した。

おわりに

結果は同じでも、ビルダーの定義次第でコンピュテーション式の書き方が変わる。 他言語のマクロのように自由ではない限られた変換で何が表現できるのか、疑問は尽きない。

F# API検索サービス"FSDN"を作りました

F#

公開から少し間が空きましたが、改めて周知ということで。

http://fsdn.azurewebsites.net/

このサービスは現在Azureで運用しています。 運用にあたり、株式会社オンザロード様にスポンサーについていただきました。

私がメンテナンスしていく限りはUNIXLinux環境でも動くようにしておくつもりです。 ただ、メンテナが変われば方針が変わるかもしれません。

検索対象ライブラリ、アセンブリ追加要望は https://github.com/fsdn-projects/FSDN/issues でお願いします。

FSharpApiSearchにべったり依存しているので、機能によってはそちらを先に拡張しないとFSDN側ではどうにもならない可能性が大きいのです。

技術的なお話はまた今度。

Babelの勉強と称してmuscle-assertを作ってみる

JavaScript

私は型がないと死んでしまう(コンパイラと相談しないと考慮漏れが多発して死ぬ)ので、JS系を触る場合は主にTypeScriptなのですが、とはいえ昨今の事情的にBabelを食わず嫌いするのなぁ…と思ったのでライブラリとそれ用のpuluginを作ってみることにした。

勉強前のスキル

  • Babel系の情報は追っていたが使うのは(ほぼ)始めて
    • 既に別の人が構築したものをちょろっと触ったことならある
  • TypeScriptは使える(詳しいわけではない)

方針

こういうのは強い方々の力を借りるに限る。

https://github.com/azu/power-assert-as-tool-demo

Babel pluginの知識は https://github.com/thejameskyle/babel-handbook を斜め読みしつつ、https://github.com/power-assert-js 下にあるbabel pluginを読み漁った。

作るついでに類似品チェック

Persimmon用アサーションライブラリMuscleAssertを作った - pocketberserkerの爆走

この記事の記憶がまだ自分の脳内に残っているうちに移植してみたかったので、今回はこれを題材にした。 一応類似品を紹介しておく。

https://www.npmjs.com/package/assert-diff

やっていることはだいたい同じだが、標準assertをベースにしたかったので自作しようという心持ちである。

あと、似たようなdiff表示はmochaであれば行ってくれる。 だからmochaであれば必要ない…のだが、eaterなどではそういうわけにもいかないと思う。

成果物?

https://github.com/pocketberserker/muscle-assert https://github.com/pocketberserker/babel-plugin-muscle-assert https://github.com/pocketberserker/muscle-assert-demo

Babel初で一日少々にしては私的には頑張ったほうじゃないですね…まぁ、参考にできるものがおおかったからだろうけど。

muscle-assertは標準のassert.deepStrictEqualのみを置き換える方針にした。 こうしたほうがシンプルだろうし、他のライブラリとも干渉しなくなるだろうと思った。 そういうわけでBabel plugin側も最低限の置き換えにとどめている。

使い方はデモプロジェクトをみればわかるはず。 表示がしょぼいのはプロトタイプなので勘弁してください。 バグっていたらそのうち直します。

ドキュメントがなさすぎるのは…本気で実装する気力が起きれば書く。

課題

なるべくpower-assertと干渉しないように作りたい…と思っていたのだが、以下の問題にぶつかった。

babel-plugin-espowerが先に変更してしまうのでテストがおかしくなる。 回避方法は今のところbabel-plugin-espowerのpatternsassert.deepStrictEqualをいれないという場当たり的なもの…なんとかしたいが、なんとかできるのかなぁ。

というわけで、今のところbabel-preset-power-assertとの共存は難しいのではないかと考えている。

おわりに

Babelは入門するだけならそんなに時間がかからないようだ。 power-assertの導入は記事が乱立しすぎているし古い情報がノイズになってTypeScriptのときはつらい気持ちになっていたが、Babel前提だとbabel-preset-power-assertがよろしくやってくれるので良いと思う。 とはいえ、楽になったのはあくまでBabelマンだけなのでTypeScriptの時にもう少しどうにかならないか考えてみたくもある。

babel-plugin-espowerのあれはやりたいことを考えると仕方がないのだと思う。 こういうのは後発ライブラリが頭をひねれば良い問題だと思うので、babel-plugin-espwerさんはそのままの強さで居てほしい。

Persimmon用アサーションライブラリMuscleAssertを作った

F#

正確には「作っていたライブラリをPersimmon.MuscleAssertにrenameした」です。

注意

この記事はあくまで私の考えでありpersimmon-projectsの総意であるわけではありません。

前提

そんなにF#寄りの話はしないはずなのでこれくらいの情報量で大丈夫なはず…?

Persimmonはpower assertの夢を見るか?

昨今はpower assertの認知度が格段に上がっているようですね。 これはJavaScript向けのpower-assertによるものが大きいのではないかと思われます。

ちょっと脱線しつつPersimmonにpower assertが必要か考えてみましょう。

どの意図でのpowert assert?

assertという言葉は二つの用途で使われいると思います。

  1. DbCとしての表明(assertion)
  2. checkingを目的としたassert関数

DbC向けの用途でpower assertが利用されることはまだ少ないようです。 たぶんGroovyとJavaScript*1くらいなのではという認識です。 私が知らないだけかもしれないのですが…。

一方、ユニットテストフレームワークの文脈上で登場するpower assertは様々な言語に存在します。 .NETにもhttps://github.com/PowerAssert/PowerAssert.Netが存在します。 使い勝手は触ったことがないのでよく知りません…。

今回は後者のほうでのpower assertについての言及です。

我々が欲しかったものはpower assertなのか

かつて、下記の記事が界隈をにぎわせました。

そろそろPower Assertについてひとこと言っておくか - ぐるぐる~

状況が少し変わっているのは

  • Groovy(こっちは当時から?)やJavaScriptのpower assertでは文字列の比較で差分を表示する

あたりでしょうか。 あとは下記も参照すると良いかもしれません。

https://github.com/persimmon-projects/Persimmon/issues/4#issuecomment-60347183

これにF#的な補足をしておくと

  • オブジェクトの一致を確認することが多い
    • 判別共用体やレコードなど型を作りやすい環境にあるため、文字列や数値などではなくプロパティを持ったオブジェクトを比較することになりやすい
  • パイプライン演算子でつなげるスタイルで結果をassert関数に渡す場合もある
    • Elixirのように強力なマクロがあるわけではないので途中の式が解析対象にできない
    • power assertのためにスタイルを変えるのか?的な

みたいな話があるようなないような感じです。

プロパティを幾つも持つオブジェクト(例えばJSONオブジェクトをマッピングしたレコード)をpower assertにかけると無駄に情報過多になりやすく、しかしinlineに式を書かないことのほうが多いため必要な情報が増えるかどうかは微妙なところです。 情報が増えないのにグラフィカルな失敗結果が表示されても嬉しいとは思えません。

はたしてこの状況で、苦労してpower assertを実装する必要があるのでしょうか?

少なくとも私には手を出す気力がおきませんでした*2

力技で不要な情報を握りつぶす

しかし、やはり単純なassert関数は力不足です。

// https://github.com/persimmon-projects/Persimmon/blob/cdba2509e5d48e4cdd47f292f3578b00371c85fc/src/Persimmon/Assertions.fs#L9
let assertEquals expected actual =
  if expected = actual then pass ()
  else fail (sprintf "Expect: %A\nActual: %A" expected actual)

上記はPersimmon 1.1.0のassertEqualsですが、とにかく貧弱です。 情報量がsprintfで出力可能な範囲に絞られるわけですが、こいつの表示はあまりあてにできないと思ったほうが良いです。 ひどいときには型名しか表示されません(それでも言語組み込みのassertよりはマシですが)。

これではさすがにつらい(らしい)のでもうちょっと情報量増やせないか、という話になるのは必然です。 しかしpower assertには懐疑的…どうしようという話になります。

さて、id:bleis-tiftさんの記事ではこう述べられていました。

ユニットテストにおいて、最も欲しいのは「どこがどうなっているか」ではなく、 「どこがどう違っているか」じゃないですかね。

これをPersimmon開発チームでもう少し協議した結果、「オブジェクトのdiffをだそう」という流れになりました。 というわけで出来上がった試作ライブラリがPersimmon.MuscleAssertです。

https://github.com/persimmon-projects/Persimmon.MuscleAssert

このライブラリができることは「等値比較し、不一致だったらオブジェクトの差分をひたすら表示する」ただ一つです。 とはいえ、実際には以下の制約がつきます。

  • System.Typeはアクセスした瞬間に例外が飛ぶプロパティもリフレクションで取れてしまうためFullNameプロパティのみ対象にする
  • IEnumerableはdiffの対象にしないで「無視したよ」と警告をだす
    • 無限リストのである可能性や、iterateする際に副作用が発生しオブジェクトが操作されてしまう可能性があるため
  • stringも未加工で表示
    • 今後の方針次第ではdiff-match-patchを導入するかもしれません

コード例と実行結果を見てみましょう。

open Persimmon
open UseTestNameByReflection
open Persimmon.MuscleAssert

type TestRecord = {
  X: string list
  Y: int
}

type TestDU =
  | A
  | B of TestRecord
  | C

let ``dump diff list includeing DU`` = parameterize {
  source [
    ([A; A], [A; C])
    ([A], [B { X = []; Y = 2 }])
    ([B { X = []; Y = 1 }], [B {X = []; Y = 2 }])
  ]
  run (fun (expected, actual) -> test {
    do! expected === actual
  })
}

テスト結果では以下のような表示が垣間見えるはずです。

Assertion violated ...
  1. .[1]
    left  TestDU.A
    right TestDU.C
Assertion violated ...
  1. .[0]
    left  TestDU.A
    right TestDU.B
Assertion violated ...
  1. .[0].Item.Y
    left  1
    right 2

値が異なる部分のみに表示を握り潰す、実に脳筋ですね。

命名理由

「普通のアサーションと区別しにくいから名前をつけよう」という話になった際に

これがなぜかそのまま採用されました。

power assertと対立する存在なのか?

不明です。

正直なところ、言語によってはpower assertのほうが使い勝手が良い可能性はもありますし、各種power assertライブラリがオブジェクトdiff機能を取り込めば話は変わってくるかもしれません。

とはいえ、一つだけ明確に言える違いがあります。 それはdiffを取得するだけであればASTの操作は必要ないという点です。 世の中にはテストでASTを操作することに懐疑的な派閥もあるらしいですが、そういった派閥にも受け入れられる可能性は残されています。

どんな言語でも実装できるのか

オブジェクト用diffライブラリはJavaから移植しました。 そのライブラリではgetプレフィックスのついたメソッドを対象としていたので、何かしらの制約(getterのような慣習的なもの)を設ければ実装できるのではないでしょうか。

おわりに

ユニットテストフレームワークには以下の方針や理想(?)があると思っています。

  • 成功時は沈黙し、失敗時はやかましく
  • 「なぜ失敗したのか」を的確に伝える
    • もしくは容易に推論可能な形で表示する

power assertがこのまま勢いを増すのか、筋肉式assertの流れが来るのか、第三の概念がかっさらうのかはわかりません。 ただ日々考え、実装し、議論を交わさなければ停滞するだけだと思います。

これを機会に一度、「テスト失敗時に欲しかった情報とは何か」を考えてみるのも良いかもしれません。

*1:https://github.com/power-assert-js にあるツール群を駆使すれば可能という話を聞きかじっただけなので、詳細はその方面の方に尋ねてください

*2:テストケースの構文自体を解析対象にしようと試みたことはありますが、これも情報過多になりそうだったので一旦開発を止めています https://github.com/persimmon-projects/Persimmon.Pudding

diff-match-patchを F# に移植してみた

F# diff

世の中にはdiff-match-patchと呼ばれる、Google製の便利な文字列diffライブラリが存在します。

そんなdiff-match-patchですが、C#版はコードは存在するもののnugetにpublishされていません。 また、コードは.NET Framework 3.5以上である必要があるため、諸事情*1.NET Framework 2.0系である必要がある場合には使えません。 具体的にはLINQとかHashSetとかですね。

幸いなことに、F# を使えば.NET Framework 2.0をターゲットにしつつLINQにあるような関数やSetが使えます。 というわけで移植しました。

https://github.com/pocketberserker/Diff.Match.Patch

immutableに改変するのも大変つらそうだったのでmutableのままです。 どうしてこうなった…的なコードもたくさん出てきます。 実装に関してはそっ閉じすべき事案でしょう。

使い方は大体C#版と同じように使えるのではないでしょうか。

*1:実際に使いたかったPersimmon.Assertionでは.NET 2.0を最低ラインにしている

java-object-diff を F# に移植

F# diff

久しぶりの移植芸です。

移植理由

Persimmonの開発に必要みたいな話になり、かつ移植する時間のありそうな人間が他にいなかったとかそういう。

先達

https://github.com/SQiShER/java-object-diff

さすがJavaさん、探せばだいたいでてくる。

成果物

https://github.com/pocketberserker/FSharp.Object.Diff

3月中旬に始めたので、まともに動かせるようになるまで二か月かかった計算に。 まぁ、FSDNやら業務のあれそれやら遊びといったことを考えると妥当な数値っぽい感じがしますね?

あれそれ

移植元はJavaなので、Javaを前提に実装されているわけですね。 なので、当然仕様の差が出てきます。

  • Javaにプロパティはないが、.NETにはある
    • Javaメソッドから値を取得する形になっていたので、そこをだいたいプロパティに置き換え
  • フィールドのdiff取得機能はomit
    • F#では必要ないと思うんだ

このあたりは仕様の話。

ここから実装の時に遭遇した(または現在進行形)の問題。

  • Javaの一部のMapだとkeyにnullが突っ込める
    • .NETのDictionaryは突っ込めないんだよねーとなってちょっと悩んだ
  • Map.getでnullが返ってくる
    • そういえばそんなんだったなーとか
  • null nullしている
    • Javaだもんね、仕方ないね
    • 移植を優先した関係で F# 版もnull nullしてます…
    • Some nullさん…
  • F#のレコードがほぼ使えない
    • 移植元がmutable前提なのでしょうがない
    • abstract classが出てくるとレコード使えないしね
  • .NETのコレクションの基底となるインターフェースは?
    • read onlyで良いならIEnumerable(ジェネリックじゃないやつ)
    • じゃあ、書き込みしたい場合は?
    • 今回は仕方なくIList(ジェネリックじゃないやつ)をwrite部分での基底に採用している
  • .NETの連想配列の基底となるインターフェースは?
    • IDictionary(ジェネリックじゃないやつ)はあまり役に立たない。けど楽だったのでいったんこれを採用している。
    • IDictinary<'T, 'U>を基底にしたいところだが、type eraseされないのとkeyやvalueの型情報がその場にない関係でパターンマッチで型チェックみたいなことはできない。
    • もしかして:ひたすらリフレクションで頑張るしかない…?
    • JavaさんはCollection<?>とかMap<?>とかやってて「ワイルドカードええ子や」みたいな心境になった
  • 比較演算子とか
    • Java==Object.ReferenceEqualに、equals=に読み替えるの、わりと難しい
    • これをミスして何度か嵌った
  • equality制約を回避したい
    • F#だと=を使うと型パラメータがequality制約を要求する
    • が、diffライブラリなのでそんな制約は外したい
    • ので、比較する部分でbox xを挟んでobjで比較するバッドノウハウを駆使して制約を回避している

まとめ

nullよりもコレクションの仕様の差やtype eraseかそうでないかなど、もっと別な場所で精神が削れました。

Microsoft MVP for Visual Studio and Development Technologies(F#)を受賞しました

その他

気がついたらまたカテゴリが変わっていましたが、今年で4回目の受賞となります。 Visual Studio and Development Technologiesとカテゴリ名が長すぎて未だに覚えられません。 覚えられないのでF#と言い張っておきます。

仕事でF#を使わなくなって久しいですが、これからも何かしら発信していけたらと思います。

特にXamarinとかUnityとか、F#の情報が少ない部分に手を入れられたらいいなとは思っていますが、こればかりは調べる体力があるか(そして気が向くかどうか)次第なのでなんとも……。

え、仕事で使わないのかって? 禁則事項です。

というわけで、今年度もよろしくお願いします。