Scalacheck の Arbitrary.scala を scalaz の Applicative を使って書き換えてみる

これは Scalaz Advent Calendar 2012 の16日目です。
kazu yamamoto さんの Applicativeのススメ
より引用

簡潔に結論を述べると、

foo = do
a <- m1
b <- m2
return (f a b)
のようなコードを書きたくなったら

foo = f <$> m1 <*> m2
と書きましょうということ。

合い言葉は、「do と return をなくせ!」です。

Scala(z) でも大体同じですよね!というわけで(?)

これ↓

Arbitrary(for {
  t1 <- arbitrary[T1]
  t2 <- arbitrary[T2]
  t3 <- arbitrary[T3]
  t4 <- arbitrary[T4]
  t5 <- arbitrary[T5]
} yield (t1,t2,t3,t4,t5))

が、こう書き換えられますよね↓

 Arbitrary(Apply[Gen].apply5(
  arbitrary[T1],
  arbitrary[T2],
  arbitrary[T3],
  arbitrary[T4],
  arbitrary[T5]
)(Tuple5.apply))

と、Scalacheck の コードみてたら思いついたので、書き換えてみただけです。

https://github.com/xuwei-k/scalacheck/commit/75c818a8419389e66aff61325b157021f9e7b9b2

変数が5つも減ってます。*1 ちなみに、ApplicativeBuilder を使った他の書き方もあります。*2

(arbitrary[T1] |@| arbitrary[T2] |@| arbitrary[T3] |@| arbitrary[T4] |@| arbitrary[T5])(Tuple5.apply)

もしくは

(arbitrary[T1] |@| arbitrary[T2] |@| arbitrary[T3] |@| arbitrary[T4] |@| arbitrary[T5]).tupled

追記:
さらに他の書き方もあります

 ^^^^(arbitrary[T1],arbitrary[T2],arbitrary[T3],arbitrary[T4],arbitrary[T5])(Tuple5.apply)

そして上記の書き換えをしたあと、さらに tuple2 から tuple5 というメソッドが Apply にあることに気づき

https://github.com/scalaz/scalaz/blob/v7.0.0-M6/core/src/main/scala/scalaz/Apply.scala#L92-L99

tuple2 から tuple5 を使って書き換えたのが以下

https://github.com/xuwei-k/scalacheck/commit/7bec59f396611f2bc22ffe4abf7d57351fdd1656

Arbitrary(Apply[Gen].tuple5(
  arbitrary[T1],
  arbitrary[T2],
  arbitrary[T3],
  arbitrary[T4],
  arbitrary[T5]
))

で、applyNやliftNは現状( scalaz 7.0.0-M6 ) 12まであるのに、tupleNは5までしかなくて全部書き換えられない・・・(´・ω・`)

ちなみに、もちろんこれをScalacheckにpull requestはしません。*3


そして、上記の書き換えを行うには、本当は org.scalacheck.Genの Applicative *4インスタンスを定義しなければいけないんですが、実は Scalaz には scalacheck-binding というモジュールがあって(ry
という話を書こうかと思ったけど、長くなったし、別の話なので、またあとで書きます

*1: 全体では (2 to 9).sum で、44個減ってる

*2: ApplicativeBuilderを使った場合、型引数を一回分書かなくてよくなるかわりに、中間オブジェクトを大量に生成するので、それぞれの書き方に、メリットとデメリットがある

*3: これだけのために依存増えるのは、明らかにデメリットのほうが多いのと、そもそもScalazとScalacheckで循環して依存することになるのでたぶん無理

*4: 上記のコードなら厳密にはApply