scala-zero-formatterのパフォーマンス改善やakka-httpサポートとか
https://github.com/pocketberserker/scala-zero-formatter/releases/tag/v0.7.0
表題の件以外にもlz4とzstdへの変換をサポートしてみたりしました。 この2つはあくまでとりあえず程度なので速度そんなにでないですが……。
速度改善の話
- 余分な型クラスを除去
- 一時期のバージョンでいれていたが今は無くても動く(もしかしたら昔も動いたのかもしれない)
- ついでに一部パッケージを統合
- unsafeモジュールでのStringのエンコード方法変更
- https://github.com/google/protobuf/blob/v3.2.0/java/core/src/main/java/com/google/protobuf/Utf8.java を借りることにした
- あまり速度に関係ないかもしれない、という噂はあるが今回は計測したら速くなったので
- case classの全フィールドのバイナリサイズがわかっている場合はmacroでバイナリサイズを算出する
- 最後のresize(arrayコピー)が減る
内部でprotobuf-javaのUnsafeを使っているので、そりゃScalaPBと速度で勝負できるよね…という。 とはいえデシリアライズがまだ若干遅いことがある問題?もあるのでなんとかできないか調査中。
akka-http
MarshallerとUnmarshallerをつかえば簡単にできそうだったので作ってみた、という。 JSON系ライブラリはspray-jsonは公式さぽーとだったり、他のライブラリも既にライブラリがあったりします。
msgpack用のライブラリもそのうち誰かがつくるのではないでしょうか?
lz4, zstd
最初に書きましたがまだお試しパッケージです。 そのため思ったほど速度がでないと思います(LZ4のデシリアライズはその特性上十分早いけど)。
あと、バイナリサイズを考慮して使うか決めないと圧縮前よりサイズが増える、なんてこともなくはない?
scala-zero-formatterのパフォーマンス改善と疑似Stage2対応
とりあえず動くものを~ということで0.1.0でリリースしていたのですが、ベンチマークをとったらさすがにJSON系ライブラリよりは速いものの、他のバイナリシリアライザに倍以上差をつけられる結果に「さすがに遅すぎる」と改善することにしました。
この記事を書いた時点では0.3.0が最新版です。
改善内容
- foldLeft,、forearchからtailrec、while loopに
- 初期は動くこと優先で高階関数を使っていた
- が、 http://d.hatena.ne.jp/xuwei/20130709/1373330529 にもあるとおりtailrecやwhileのほうが早いので差し替え
- 抽象化をそもそも想定していないのでとれる選択(読みづらいけど)
- ちなみにこれだけでそこそこ速度が改善する
- mutableなコレクションやnewBuilderの利用
var x = Hoge.empty
で変更するよりmutableコレクションから変換したほうが早い(そりゃそうだ)
- Encoder, Decoderクラスを追加
- mutableな設計に倒した
- 余計な処理が少し減る
- 一部のHListを自前のマクロに置き換え
- foldLeftは遅い
- fixed sizeなコレクションは個別にFormatterインスタンスを用意
- 汎用的なやつだと毎回
Array[Byte]
のサイズチェックやコピーが挟まるので遅い - ので、サイズがわかっているものは最初に一括でリサイズして、配列のサイズチェックなしに書き込む
- 汎用的なやつだと毎回
UnionやEnumには手を入れていないのであれらはまだ遅いままかも。
改善後のベンチマーク
以下てきとーベンチマーク。 てきとーなのであんまりフェアじゃないかも(とくにJSON系)。
sbt "jmh:run -i 10 -wi 10 -f 2 -t 1 benchmarks.EncodingBenchmark" Benchmark Mode Cnt Score Error Units EncodingBenchmark.encodeBarZ thrpt 20 1324256.775 ± 82009.746 ops/s EncodingBenchmark.encodeFooMZ thrpt 20 518515.724 ± 8547.872 ops/s EncodingBenchmark.encodeFooPB thrpt 20 1650635.690 ± 24153.038 ops/s EncodingBenchmark.encodeFooZ thrpt 20 1402245.092 ± 69284.956 ops/s EncodingBenchmark.encodeFoosA thrpt 20 1356.404 ± 80.054 ops/s EncodingBenchmark.encodeFoosC thrpt 20 2157.034 ± 219.598 ops/s EncodingBenchmark.encodeFoosMZ thrpt 20 11213.833 ± 1080.450 ops/s EncodingBenchmark.encodeFoosZ thrpt 20 15255.728 ± 1111.828 ops/s EncodingBenchmark.encodeIntsA thrpt 20 10119.779 ± 894.966 ops/s EncodingBenchmark.encodeIntsC thrpt 20 14285.633 ± 1583.352 ops/s EncodingBenchmark.encodeIntsMZ thrpt 20 83462.462 ± 1648.580 ops/s EncodingBenchmark.encodeIntsPB thrpt 20 151786.501 ± 2536.334 ops/s EncodingBenchmark.encodeIntsZ thrpt 20 219506.973 ± 6170.565 ops/s sbt "jmh:run -i 10 -wi 10 -f 2 -t 1 benchmarks.DecodingBenchmark" Benchmark Mode Cnt Score Error Units DecodingBenchmark.decodeBarZ thrpt 20 7898101.929 ± 854734.733 ops/s DecodingBenchmark.decodeFooMZ thrpt 20 769205.462 ± 60756.223 ops/s DecodingBenchmark.decodeFooPB thrpt 20 1178288.024 ± 36754.823 ops/s DecodingBenchmark.decodeFooZ thrpt 20 1377253.293 ± 234093.569 ops/s DecodingBenchmark.decodeFoosA thrpt 20 796.463 ± 111.745 ops/s DecodingBenchmark.decodeFoosC thrpt 20 1607.345 ± 79.042 ops/s DecodingBenchmark.decodeFoosMZ thrpt 20 10599.926 ± 1889.855 ops/s DecodingBenchmark.decodeFoosZ thrpt 20 17261.595 ± 260.918 ops/s DecodingBenchmark.decodeIntsA thrpt 20 6230.473 ± 659.867 ops/s DecodingBenchmark.decodeIntsC thrpt 20 12751.050 ± 921.923 ops/s DecodingBenchmark.decodeIntsMZ thrpt 20 59862.336 ± 1785.270 ops/s DecodingBenchmark.decodeIntsPB thrpt 20 125009.494 ± 9799.178 ops/s DecodingBenchmark.decodeIntsZ thrpt 20 85695.355 ± 27599.620 ops/s
Foo
が単発のcase classでFoos
がMap[String, Foo]
、ints
はVector[Int]
です。
Bar
はZeroFormatter+フィールドをcats.Eval
にしたものです(後述)。
AがArgonaut, Cがcirce、PBがScalaPB、MZがmsgapck4z、Zがscala-zero-formatterになります。
注意事項としては、元ネタのcirce-benchmarkはListだったのですが、ScalaPBのdecode用メソッド(mergeFrom
で調べよう)がVectorを使って生成してSeqをフィールドに持つようになっているので他のやつも合わせました。
あと、jmhの仕組み的にネストしたクラスが扱えないらしくScalaPBのベンチマークがとれていません。
ScalaPBは速いなぁ、というのがまず最初の感想ですね。 この中で唯一ジェネレータ系かつ生成されるコードがこれまた無駄がないので簡単なcase classであれば他を圧倒します。 sbtプラグインのおかげで10行程度(?)でプロジェクトの設定ができるし、gRPC対応してるしで、Scalaでの開発に限って言えばまず有力な候補に上がるの間違いなしです。
scala-zero-formatterはScalaPBと良い勝負をしているものの、バイナリサイズやIDLの利便性を考えるとどうなのでしょうねぇ。 後述のStage2あたりが差別化要因という話はありますが、さてはて。
msgpack4zはByteArrayOutputStream
を経由しているのがネックなのかな……あるいはベンチマークに使ったコードが微妙なのかも?
これに関してはもう少し調査してみるつもりです。
でないとフェアじゃないので……。
JSON系が遅いのはしょうがない。 目的が異なる(?)ので、そもそもバイナリシリアライザと比較するものじゃない気がする。 ではなぜ比較したかといえば、circe-benchmarkをforkしたからなのでした。
ちなみにscodec-msgpackを比較対象にいれていないのは、そもそもスタックオーバーフローが解決できなかったという致命的な問題のせいです……なんとかしないと。
ScalaPBが早い理由
推測も混ざっているのでご注意ください。
- シリアライズの最初にバイナリサイズを計算
- サイズがわかっているので余計な操作が入らない
- scala-zero-foratterは「足りなくなったら増やす」戦略をとっているため随所でサイズチェックと
Array[Byte]
のコピーが発生する - デフォルト値のときは何もしない
- 今回のベンチマークではそんなに関係ないけど一応
- ジェネレート時のインライン展開
- 生成されたコードの
writeTo
やmergeFrom
に各フィールドのシリアライズ、デシリアライズ処理が手続き的に並んでいる - 余計な呼び出しがないならそれだけ遅くならないということ
- このあたりでいくらか差が出る場合もある
- sun.misc.Unsafe?
scala-zero-formatterのStage2エミュレーション
ZeroFormatterの仕様には幾つかのStageがあります。 Stage2はObject、FixedSizeList、VariableSizeListのフィールドや要素のデシリアライズを遅延する仕様になっています。
で、これをScalaでどう実現するかですが……今回はとりあえずcats.Eval
を使うことにしました。
ユーザが自前で定義する系なのでObjectや遅延リスト以外に適用して事故を起こす可能性もある……とはいえ、がんばらずにStage2の挙動を手に入れられるのでこれでいいかなーと。
equalsとかがまともに使えなくなる問題もありますが、比較したい時点で全部正格評価すべきだと思います。
バイナリのキャッシュ
C#のZeroFormatterにはバイナリをキャッシュして書き換えた部分以外はdeep copyする機構があって、これでシリアライズも省電力化することができます。
ZeroFormatterの仕様に存在しない仕組みとはいえ、こういう機能もできればscala-zero-formatterに欲しい……ということで、まずは単純にキャッシュするだけのAccessor
というものを用意してみました。
case classの値が書き変わらないのであれば内部で溜め込んでいたバイナリをそのままシリアライズの結果として返します。
完全に横流し専用ですね。
そのうち部分更新の機能もサポートしたいのですが、ネストしたフィールドのcase classの変更をどうやって最上位のcase classにまで伝播させれば良いのかで詰まっていて当分先になりそうです……。 Lensを参考にしようと思っていましたがなかなかうまくいかないものですね。
まとめ
バイナリシリアライザのパフォーマンスチューニングは無限大に時間を消費するなーと。
とはいえ、もう少し何かできそうではあるので他言語版ともとも頑張ると思います。
ScalaとRust用のZeroFormatterライブラリを作り始めた
あけましておめでとうございます。 進捗どうですか? 私はダメです。
新年早々バイナリフォーマッターの話をします。
ZeroFormatterは、昨年の11月頃にneueccさんが公開したバイナリシリアライザーです。 詳しい話?は実装者の記事を読んでください。
ここで重要なのは上記記事の一文です。
と、いうわけで、言語がC#のみってのはさすがに普通に欠点なんですが、整備してみたんで多言語サポートよろしくお願いします、みたいな(?)。
おおなるほどな、と思いながら当時はスルーしていたのですが、年末に改めてREADMEを眺めていたら気力が湧きあがったので作ることに決めました。 まぁ、一番の決め手はCross PlatformでStageが明確に分かれていることですね。 なるほど、最初はeager-evaluationで良いなら徐々に育てれば良いかみたいな。
Scala版
https://github.com/pocketberserker/scala-zero-formatter
現時点ではshapelessにべったりです。 あとなんとなくScala.js対応してます。 コードは汚いです(おい)。 ScalaのバージョンやJVMかScala.jsかどうかで動かないものがあったりもするのですが、まぁ、そのうちどうにかします(願望形)。
以下簡単な説明
- Object、Union、EnumをそれっぽくC#に似せて作った
- ADTをフォーマットしたいならUnionを使うしかないという制限がありますが、まぁ、UnionKeyを見つける必要があるのでしょうがない
- Formatterの自動導出
- shapelessによるゴリ押し
- そのため、生成に失敗した際のエラーメッセージが"could not find implicit parameter"という情報量のなさに…
- 使い方はシンプル
そして結構詰まったりもしました。
- オブジェクトのデシリアライズ時にHListの各要素のFormatter型クラスが取得できない
- Coproductの各型のFormatter取得に苦戦
- HListと同じ問題だが、CoproductにfillWithはない(あったら困る)のでさらに苦戦
- 結局以下の順序で無理やり取得した
- Coproduct.lengthを取得
- nat.RangeでHListを生成
- HList.foldRightで各要素の操作時にCoproduct.Atを使って型を導出
- 取得した型にたいしてGenericやHListを駆使してFormatterを取得する
- Annotationやら継承ベースやらでScalaらしくない?
- Indexというメタデータを持たせる方法がAnnotation以外に思いつかなかった…
- case objectがCoproductでの生成対象にならない?のでEnumは仕方なくcase classのみに固定
- UnionでKey用のFormatterインスタンスが取得できない
StructやUnionを継承ベースにしたのは成功か失敗かいまいち判断ついてないですね…。 あと、自動生成にこだわらずにやれば年末年始休暇を全部溶かすことはなかったでしょう。
Scala版の今後の展望。
- 型レベルIndex
スタック溢れを防ぐ特定の書き方をした時だけStackOverflowになる…同じ要素数、型定義でも別の書き方をすれば回避できているのが謎TailRec版を実装している途中だがimplicit parameterが解決できずに困っている- 同じような話を発見したのでcachedImplicitを試したら動いた https://github.com/milessabin/shapeless/issues/345
- ただし2.10.6ではコンパイルエラーになる…Unionもサポートできないのでそのうちdropする
- Stage2サポート
- 劣化Lensみたいなものを作る想定
- Unsafe?
Rust版
Rustはαくらいのときに少し触って、むーりぃーと投げ出して以降ほったらかしていたのですが、さすがになんかもう一つぐらい言語の手札を増やしたい感があったのでやるやる詐欺状態に…。
と、そんなときによく考えたらZeroFormatterというお題があるじゃないですか。 かつてmsgpackを言語入門のお題にしてきた身としては格好の題材ですね?
というわけで作り始めました。
https://github.com/pocketberserker/zero-formatter.rs
まだprimitive typeとstringくらいしかないですが…そのうち頑張る、ということで。
いまのところの感想としては、Scalaのshapelessとは違う意味でコンパイルが全然通らない…といったところでしょうか。 Rustのお作法がわからない状態で作っているのでそのうちRust強者に良い書き方を教わりたい…。
ZeroFormatterの所感
うまい感じにまとまっているなーというのが実装してみた現時点での感想ですね。 nullの扱いがNullableとその他の型で異なるのが困りどころではありますが、そこさえなんとかなればStage1の実装は容易です。 いやまぁ、最速を目指すなら(そしてStage2対応するなら)それだけではもちろんだめですが、初期実装が比較的簡単に作れることは重要なんじゃないかと思います。 ないなら作る、の敷居が低いのは良いことです。
というわけで所感列挙。
- nullの規定が明確なこと
- has valueか最初が-1かどうかで判断できるのでOptionを標準にもつ言語ならそっちにマッピングさせることができる
- フォーマットによってはこのあたりが面倒な場合があるので…
- Union
- 昔欲しかった(切実)
- バイナリサイズが富豪っぽく感じるけどトレードオフだし仕方なさそう
- Indexは人によって好き嫌いあるかも?
- とはいえそれはprotobufも同じだし、そういう時代の流れなのかも?
msgpackやprotobufなどとと張り合うならJSONとの相互変換もドキュメント化されているほうがいいんじゃないかとか、まぁいろいろありますが、そこはコミュニティができるかどうか次第なんじゃないでしょうか。
それ以外だとC#以外の実装がどこまで最速の座を奪えるかどうか、もあるのかな?
あと別言語での通信に使うとなればジェネレータが必要になりそうですが、あのSchemaなら誰かがえいやでプラグイン機構付きのものを作れってくれるでしょう…という楽観もあったりなかったり。
おまけ
F#拡張?知らない子ですね。
…いや、決して知らないわけでもissueを眺めるにとどめているわけでもないのですが…。 なんというかこう、ILとにらめっこすれば作れることはわかっているし技術的な問題はおそらくないはずだけど、そこに面白さはないんですよねぇ…判別共用体に対応させてパターンマッチできるようにするくらい? うーん、それだったら欲しい人がそのうち作るでしょう、みたいな。
…とか言ったら未来の自分にブーメランになりそうなのでここまでにします。
なお”気が向いたらErlangかElixirでも作ってみたいかも?”という気持ちはある。
おわりに
いろいろ書きましたが、ZeroFormatterはまだ全然実装が揃っていないみたいで、あなたがいますぐ実装すればその言語でのZeroFormatter第一人者になれるのでおすすめです。 最適化の勉強にもなりますからね。
scalaでmuscle assert的なライブラリの試作
Scalaでdogというテスティングフレームワークを以前から作ってみているわけですが、こいつでPersimmon.MuscleAssertみたいなことはできるのかなと思い作……ろうとして放置していたものを少し手直しして動くようにしました。
https://github.com/scala-kennel/hound
Diffの実装自体はただのshapelessのようだ。
初期はhttps://github.com/xdotai/diffがよさそうに見えたのでこのライブラリを使っていたのですが、所々使いづらかったりあまり更新頻度高くなさそうだったりScala.js対応されそうになかったりしたので「Diffくらい作れるでしょ」という軽い気持ちで自作に走りました。
なお実装が雑なのでcase classの型が異なる時に型名しか表示されないです。 あと、shapelessを使いサボった関係で、そもそもprimitive type + case classくらいしか表示できない問題があります……Scalaでリフレクションを頑張る体力がないです……。
Stringの個別diffはライブラリがぱっと探せなかったので非対応です。 よさげなライブラリがあれば教えて下さい(Javaのものでも構いません)。
assertion時にオブジェクトのdiffを取得するのは、たぶん有名どころのテスティングフレームワークでも実装できると思います。 が、まぁ需要はなさそうですし私は作る予定はないです。 ScalaTestにはpower assertあるので、基本はそっちで満足する人が多いんじゃないかなぁと。
Scala.jsに対応したscodec-msgpackをリリースしました
この記事は予約投稿です。
id:xuwei さんが ささっと作業してくださったのでリリースしました。
https://github.com/pocketberserker/scodec-msgpack/tree/v0.4.1
v0.4.0はプロジェクトの設定ミスにより空のjarが降って来るだけなのでスルーしてください…。
なんでscala-jsに対応できたのか
- shapeless は Scala.js をサポートしている
- scodec-bits も Scala.js をサポートしている
- 依存している二つのライブラリが Scala.js をサポートしたので、 scodec-core も v1.8.3 から Scala.js をサポート開始
- scodec-msgpack は scodec-core のみに依存する、かつそんなに変なことはしていない
- よってプロジェクト設定とテスト部分のみコストをかければ問題なく対応可能
といった形です。 shapeless が Scala.js をサポートしているのはなんというか、すごいですね。 マクロ的とか楽に対応できるものなのだろうか…?
TypeClassを使うようにした
ライブラリを作っておいてアレですが、私自身がscodec-msgpackを使っているというわけではないのでテキトーでいいかなと思ってメンテナンスをさぼり気味でした。
しかし、下記issueがきたので
Make primitive `Serialize`s implicit · Issue #8 · pocketberserker/scodec-msgpack · GitHub
重い腰をあげてimplicitな形式に変更しました。
変更ついでにshapeless.TypeClass
も使うようにしたのでそれなりに自動生成できます。
インスタンス増やさないといけないのは…ぼちぼちやろうと思います。
その他
dog用のautodocライブラリを作った
Rubyにはautodocというライブラリがあります。
これのdog用のライブラリを作りました。
pocketberserker/dog-autodoc · GitHub
dog-autodoc用のsbtプラグインは実装中です…最低限部分できたので一旦リリースしました。
pocketberserker/sbt-dog-autodoc · GitHub
モチベーション
Scalaにはplay-autodocというものが存在する。
でもこれ、Play Framework用なんですよね。ということで
- Playに依存しないものにしたい
- どうせだからクライアントは切り替えたい -> httpzを使おう
というのがモチベーションです。
苦労した点
- sbtから出力ディレクトリを設定しつつ
- テストを一回だけ実行し
- 実行後に出力する
この流れをどうやってみたすかかなり悩んだ。 というか、当初の想定より随分と変わっている(テストの実装ミスしてて当初想定していた残骸がゴミコミットとして残ってしまった…orz)。
最終的には、
- Autodoc用のTestFrameworkを登録しておく
- AutodocMarker型の値が結果となっているケースのみ特別な処理をしてからイベントを登録する
- Autodocは実行したテスト結果とテストケース名から文字列をジェネレートできる
Event#fullyQualifiedName
にクラス名を入れるので、あとは出力結果(文字列)をどうにかしてEventに持たせれば保存に必要な情報が揃う- ところで、出力が可能なのはテストに成功したときのみ
- テスト成功時には通常
Event#throwable.isDefined
はfalse
を返すはずである。なぜなら、テスト成功なのに例外が投げられている状態は矛盾しているからだ。 - 実際JUnit Reporterはこの組み合わせを無視している https://github.com/sbt/sbt/blob/v0.13.9/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala#L84
- つまり、この使われないであろう
Event#throwable
に出力を押し込んでおくことで、ファイル書き出しをsbt内だけで完結させることができる
- https://github.com/sbt/sbt/blob/v0.13.9/testing/src/main/scala/sbt/TestReportListener.scala#L9 を使ってイベントを取得してテスト終了時に保存
という形に。ひどい実装だ…。
こういう形になった最大の要因は型パラメータが邪魔をしてリフレクションでの取得 or 型の変換が難しかったからですね。以下そのときのxuwei-kさんのアドバイス。
@pocketberserker Scalaのリフレクション、(少なくとも2.10だと)スレッドセーフじゃないけど、2.10サポート諦める(もしくは頑張ってロックとって頑張る)んですか?
(さらに、2.11でもまだ直ってないという噂もある)
— Kenji Yoshida (@xuwei_k) September 3, 2015
@pocketberserker TestCaseをtype aliasやめて
case class TestCase[A](value: Kleisli[TestResult, Endo[Param], A])(implicit A: ClassTag[A])
とか?
— Kenji Yoshida (@xuwei_k) September 3, 2015
こういうときはType erasureが辛く感じます…。 結局、autodocのためにdog自体をいじるのはなんとなくいやだったので採用しませんでした。
まとめ
dogの作りが従来のユニットテスティングフレームワークと異なるからこういうときつらい…でもがんばる。
今後は安定かとかconsole用のWriterを復活させるかどうかあたりですかね。 あ、あとrename…ひどい命名ばかりしているので良い名前母種中です。
dogというScala用テストフレームワーク(?)を作ってみた
https://github.com/pocketberserker/dog
このフレームワークは Persimmon の移植というか Persimmon に存在していた型をよりMonadとかそういう方面に倒してみようというものです。
テストという存在の型について考えてみる - pocketberserkerの爆走
上記に途中経過は書きましたがそれから数日と経たずにいくつか変更が入ってます。
特徴
type TestCase[A] = Kleisli[TestResult, Endo[Param], A]
type AssertionResult[A] = NotPassedCause \/ A
- scalapropsフレンドリー
- イミュータブル、ミュータブルを意識しましょうてきな
モジュールとかその他リポジトリについて
- dog-core
- 基本となる型と関数をまとめたもの
- TestCase単体でタイムアウト設定して実行できるように scalaz.concurrent に依存している
- 上記の通り TestCase は Kleisli なので合成できる
- AssertionResult を合成することでいわゆる Soft Assertion を実現している
- dog
- sbtのtest-interfaceに依存
- scalapropsの実装をfork(実際にはコピーしたけど)して改修
- scalapropsとは異なり非同期実行しても問題ないつくりになっているがさてはてどうしたものやら
- gen
- scalaprops-genに依存
- scalaprops.Gen を使って入力値だけいい感じに生成してもらう
- 入力と期待値の集合は作れるけど性質までかけそうにない場合とかに…ぶっちゃけパラメタライズテストのかわり
- 固定値で回すくらいなら数撃ってあてようの方針
- props
- scalaprops-coreに依存
- PropertyやPropertiesとTestCaseの変換を目的としている
- sbt-dog 9割方sbt-scalapropsのコピー。scalaprops様様である(MITライセンスで助かった…)
- sbt-scalapropsに比べてぴーきーな挙動になりやすい
- dog-examplesサンプル集。雰囲気がつかめるかも?
ここまでで実装期間6日です。 正直ScalazとScalapropsがなければここまで楽に実装はできなかったと思うので xuwei-kさん ++
作った理由を改めて
- テストの合成とか型とかについてちゃんと検証したかった(某交流会の資料作成絡み)
- PersimmonのVS拡張や某ライブラリの移植に難航しているので息抜きしたかった
- 深夜のテンション
名前はリポジトリ作るときに"ドッグフーディングしたい"というのと"Co猫"という謎ワードがふってきて悩んだ結果であり、特に猫に張り合っているわけではないです。
今後の計画
Property#forAll
などをラップして性質を満たす時にGen#sample
をテスト結果として束縛できるようにしたい- Scala.js対応
- とはいえこれはリフレクションを使って実装している現状うまくいかない
- なので、scalaprops共々forkして別リポジトリで実装するつもり
- やりたいことあればてきとーに
こういうものがほしい、とかあればissueに登録していtだけるとうれしいです。
ScalaMatsuriにこれで応募してみるという手もあるが、さてはて。