QuickCheck系統のGenのfilter(suchThat)実装比較

Gen#filter · Issue #5 · xuwei-k/scalaprops · GitHub

こういうissueを投げたあとで、オープンではない場所で色々話していた際にちょっと調べてみたのでメモ。

filter(suchThat)の挙動

あるジェネレータから条件を見たす値のみを出力したい場合に使う

-- こんなシグネチャ
Gen a -> (a -> Bool) -> Gen a

処理的には

  1. 値をランダムに生成して
  2. 生成した値が述語を満たすかチェックし
  3. 満たすならその値をGenで包んで返す
  4. そうでない場合はseedを変えてやり直し

問題点

  • 条件を満たさなければ再生性を行わなければならない都合上、再帰関数にするかwhileでぶん回す必要がある
  • 実装次第で無限ループに陥る
  • 無限ループしない場合でも処理の遅延につながる

対策

  • 生成回数の上限を決める
  • 生成失敗を組み込む
  • 別の機能で置き換える
  • 覚悟を決めてぶん回す

lazyかどうかは関係ないはず。

上限を決める

https://github.com/clojure/test.check/blob/7ed9f927047366d14f160a3a23fde5e6bab629f2/src/main/clojure/clojure/test/check/generators.clj#L299

思い処理でなければ現実的な時間に収まる。 ただし、生成方法によってはかならずExhaustedになる。 また、テストがエラーとして扱われる。

生成失敗を組み込む

https://github.com/rickynils/scalacheck/blob/333307dc1b7dd022641191c5085222583cfc3994/src/main/scala/org/scalacheck/Gen.scala#L33

ただし、この方針を採用すると他の機能にかなりの影響を与える。

  • Genの生成結果を取得して新たなGenを作る際に失敗処理を組み込む必要がある
  • とくにCoArbitraryとの相性が悪い

別機能で置き換える

https://github.com/BurntSushi/quickcheck/blob/d282c811a4c419ae4cd997b1093636d88d5c573a/examples/reverse_single.rs#L15

定型処理になるしテスト失敗になる確率が増えるが、安定はする。

気合いでぶん回す

https://github.com/fsharp/FsCheck/blob/91ebaf7b7c2453212e80b9d193f343e26975c356/src/FsCheck/Gen.fs#L317

  • タイムアウト機能必須(なかったら無限ループに陥る)
  • 条件を満たす値をみつけるまで繰り返すため遅い
  • ユーザは気楽にフィルタリングできる

まとめ

妥協できるかどうかはユーザ次第。

以下追記

F# User Goup Japan用のgitter roomができました

タイトルの通りです。

fsugjp/public - Gitter

yukitosさん++

何する場所?

F#についてゆるふわ喋る場所です。

以前から scalajp/public - Gitter みたいなのがF#にもあるといいなぁ、と思っていたので、嬉しい限りです。

  • どう書けばいいのか
  • こういうライブラリないかな
  • 俺のモナーが日を噴くぜ!
  • 仕様書関連
  • 「hogefuga進捗どうですか?」

など、なんでもありだと思います。

Quoteメソッドでコンピュテーション式に介入する(実践編?)

全国コンピュテーション式ユーザの皆様こんにちは。

GWの進捗どうですか?

私は駄目です。 ネプテューヌVⅡを1周しようと思っていたのにまだできていません…。

さて、今回はQuoteメソッドを使ってコンピュテーション式への介入しようという話の実践編(?)です。 疑問符つきなのは、プロトタイプしかできてないからという話ですね。

題材は例によってPersimmonです。

読んでおいたほうが良い資料

詳説コンピュテーション式 - ぐるぐる~ コンピュテーション式の変形後を覗き見る #FsAdvent - 眠気と戦う日々 コンピュテーション式の変形後を覗き見るを改良する - ぐるぐる~

コード全貌

persimmon-projects/Persimmon.Pudding at f3789f7cf6682bcdcab0a8ae1a90532bdcdda4fe · GitHub

これを見ればなんとなくわかると思います。

実装の話

というわけでここから本編です。

前提とゴール

今回のゴールは次のようになります。

元のテストコードにモジュールの open を加えるだけで、テスト失敗時にパラメータと簡約結果の一部が出力されるようになる

また、前提条件として

  • Persimmonには一切手を入れない

があります。 まぁ、手を入れないからこその拡張ライブラリですよね、的な。

下ごしらえ

Quoteメソッドを追加することで得られる Expr<'T> を自前で解析するのは手間なので、FSharp.Quotations.Evaluatorを fork して改造するため、同じリポジトリに同梱したりライブラリ名が衝突しないように名前を変えます。

また、 FSharp.Quotations.Evaluatorから Compile 以外の public な関数、メソッドを削除します。 他のことは一切できないよということを明示したいとかなんとか。

なぜFSharp.Quotations.Compilerを使わなかったの?

面倒くさかったLetRecursiveがまだ未実装だったからです。 あと、今回はスタックオーバーフローは気にしなくてもいいだろうという慢心もあります。

ただ、パフォーマンス調査次第では乗り換える可能性も残っています。

引数、戻り値をDictionaryに登録する

このあたりの関数を呼び出すことで、Expr<'T> が eval されたときに Dictionary に値が登録されるようになります。

λ式の引数は戻り値が unit な登録関数を手続き的にλ式のbodyにくっつければよいだけです。

値の適用や戻り値の結果を登録するには、Dictionaryに登録したあとで元の値を返す必要があります。

仕上げとして、 こんな感じ でDictionaryと Expr<'T>コンパイル結果を返します。 これで、 eval 後に 実行中の変数や簡約結果を取得できるようになります。

コンピュテーション式を拡張する

QuoteとQuote用のRunメソッドを型拡張で追加します。 これで、モジュールをopenすれば Quote が存在することをトリガーに `Expr TestCase<'T>> を引数にとる Run メソッドを呼び出すように変換が行われます。 あとは、Dictionaryから情報を引っ張ってきてこねくり回すだけです。

今回はDelayが邪魔だったので除去してからEvaluatorに渡していますが、Delayが仮に何らかの処理を行っている場合は除去するべきではないでしょう。

実行結果

open Persimmon
open Persimmon.Pudding.Quotations // 既存のテストコードにこれを追加するだけ
open UseTestNameByReflection

let ``return int`` = test {
  return 1
}

let ``fail test`` = test {
  let! a = ``return int``
  do! assertEquals 2 a
  return a
}

このようなテストを実行すると

.x
Assertion Violated: fail test
1. [parameter]
     _arg1: System.Int32 -> 1
     _arg2: Microsoft.FSharp.Core.Unit -> <null>
     a: System.Int32 -> 1
   [method call]
     Persimmon.TestBuilder.Return(1) -> TestCase<Int32>({Name = "";
    Parameters = [];})
     assertEquals(2, 1) -> NotPassed (Violated "Expect: 2
   Actual: 1")

2. Expect: 2
   Actual: 1
============================== summary ===============================
run: 2, error: 0, violated: 1, skipped: 0, duration: 00:00:00.3012002

なんとなくパラメータと簡約結果の一部が取得できているのがわかると思います。

_arg1let! aのBindの変換で現れるλ式の引数、_arg2do! の Bindの変換で現れるλ式の引数です。 きちんと実装すればこの辺りを除去できますが、まぁそれは今後の課題ということで…。

まとめ

Quoteメソッドを追加し、式木をいじることでコンピュテーション式に介入することができました。

これをがんばっていくと"コンピュテーション式内の途中経過をいい感じにdump"できるんじゃないかなと思っています。 そういう拡張ライブラリがほしかったので、今回のこれは Persimmon.Pudding として育ててみようと思っています。

Microsoft MVP for .NET を受賞しました

2015年1月からC#, VB.NET, F#の枠が統合され「.NET」となっていたので、正確には再受賞です。

.NETに関してはこれからもF#に偏ったことしかしない可能性が高いですが、何かしら成果を残せたらいいな…と思っております。

あと、せっかく東京にきたのでF#で定期的にドンチャン騒ぎしたいです。

入社経緯など

先日記載した通り、株式会社ドワンゴに入社しました。 ただ、前記事では入社までの経緯を書いていなかったので、本記事にてまとめておきます。

きっかけとか

前職は誘われる形での転職だったので、今回はWebページからの応募でやってみようと考えました。 ただ、一から探すのは時間がかかるので、ひとまずScalaMatsuriのスポンサー求人情報から、以下の条件で探してみることにしました。

  • 東京勤務
  • Scala以外の言語も詳しそうな人がいそう
  • 出社が10時以降
  • 私の経歴で書類審査を通過できそう

スポンサー求人情報が公開されていて当日に話を聞けるイベント、イベント当日時点では転職するつもりがなかったとしても便利ですね。

あとはWebページから応募して色々あって今に至ります。

やること?

多くの方から「Scalaですかー」という反応をいただいたのですが…正解ではない、という感じでしょうか。 そう思っていた方々、なかなか言い出せずすみませんでした。 Scalaを使う可能性もありますが、「Scalaがやりたい!」と強く希望したわけではないのでどうなることやら。

これに関しては「Scalaも確かに仕事で使ってみたいけど、他の言語も面白そうだし…明らかに向いてなさそうな言語でなければ」とかそういう感じです。 あとはカオスっぷりを楽しみたいとか?

FSharpどうするの?

当分は趣味で触り続ける予定です。 というかPersimmonシリーズをなんとかしなければ…。

まとめ

ScalaMatsuriもきっかけの一つだったよ、というのが書きたかっただけだったり…。

あとは最後に例の文章でも書いておこうかと思いましたが、やっぱりやめておきます。

退職のご報告

3月31日に、某社を退職することになりました。 本日が最終出社日で、来週から有給消化期間です。

忙しい人のための3行まとめ

現職は良い環境だと思う

でも東京遠いから

移住する

お前誰よ?

2013年11月中旬から、愛知県のとある会社で C# や F# を使った開発に携わっていました。

そういえば社会人になってから今月で丸2年。

退職理由

現職が良い環境だという感想は今でも変わりません。 ではなぜ退職という行動に至ったのかというと、「東京が遠いと感じた」ためです。

昨年のとあるタイミングからわりと東京へ足を運ぶ機会が増えました。 その際、宿探し、電車の時間調整、時間を潰す場所確保etc...がそろそろつらくなってきたな、と。

あとは

  • 個人的に朝がつらい
  • スーツを毎日着たい性分ではない

という気持ちがないわけではなかったというやつです。

現職で得たもの?

色々ありますが、

  • F# を仕事で使った
  • コンピュテーション式のあれそれ
  • Persimmonシリーズ

などは間違いなく現職にいたからこそでしょう。

てきとーなQ&A

現職の不満とかは?

多少はあったはずですが、やめると決めた途端に忘れました。 オフラインで会話したら思い出せるかもしれないので、ご飯に誘ってください。

次どうするの?

4月1日から東京で働く予定です。 社名は現段階で言っていいのかわからないので、伏せておきます。

ちなみにフォークリフトとか重機系ではないです。

早くない?

新卒8か月半、現職1年4か月半が早いと思うなら、早いのでしょう。

愛知の思い出

第1回TDDBC名古屋が5年前弱という事実に驚きを隠せない…。

もみあげは?

次の職場ではもみあげマンとして活動したい…厳しいか?

ここから追記

次の会社はドワンゴです。 経緯に関しては別記事で書こうと思っています。

そして例のリストはこちら

HaskellのlogictをScalaに移植してみた

Haskell に logict というライブラリがあります。

logict: A backtracking logic-programming monad. | Hackage

MonadLogicやLogicTが何者なのかはそのうち解説記事を書くとして、今回はこのライブラリをScalaに移植してみました。

pocketberserker/scala-logic · GitHub

そのうちファーストリリースを出す予定です。

このライブラリを作ったのは、smallcheckをscalaで実装してみたいと思ってコードをみたらLogicTが必要だとわかったためです。

Haskellのlogictとの違い

若干ですが差異があります。

  • 型パラメータの順序
  • 引数の順序
  • logic関数は未実装(RankNTypesに阻まれた)
  • いくつか制約を緩めている

制約を緩めているというのは、例えばMonadPlusではなくApplicativePlusでよい、とかですね。

これに関しては私もpull requestをもらうまで気がついていなかったのですが、確かに緩められるものは緩めてもよいですね。

functional dependenciesについて

LogicTのMonadReaderインスタンス実装は@xuwei_kさん、@halcat0x15aさん、@gakuzzzzさんに手伝ってもらいました、ありがとうございます*1

functional dependenciesをScalaで実現できるのか自体知らなかったので、とても助かりました。 みなさんScala函数型なことでわからないことがあったら以下のgitter部屋に突撃するといいと思います。

scalajp/functional - Gitter

なぜScalaz?

某猫kitsの利用も考えたのですが、まだリリースされていなかったり今回の実装に必要そうな機能がなかったりしたので見送りました。 あとはまぁ、Scalazを長らく触っていなかったのでリハビリしたかったとか(某本もでることですし)。

Scalazは長い間開発が続いているだけあって機能が充実していて良いですね。 そういう意味では当分使われ続けるのではないでしょうか。

まとめ

小さいライブラリですがscalazの機能を色々と使っているので、ちょっとした勉強にはなるかもしれません。

*1:よく考えたら全員去年のScalaz勉強会の発表者だ