Scalaz で Ordering モノイド

書籍「 すごいHaskellたのしく学ぼう! 」 の Ordering モノイドを使った lengthCompare 関数を Scalaz で実装してみました。

  • Scalaz 7.0.0-M3
  • sbt 0.12.0

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20120828/

すごいHaskellたのしく学ぼう!


lengthCompare 関数は以下のように文字列を比較する前に文字列長を比較するというもので、文字列長が等しかった場合のみ (= EQ) 文字列の比較 (`mappend` の右辺) を実施します。(処理内容は本のままです)

Haskell版 length_compare.hs
import Data.Monoid

lengthCompare :: String -> String -> Ordering
lengthCompare x y = (length x `compare` length y) `mappend` (x `compare` y)

main = do
    putStrLn $ show $ lengthCompare "zen" "ants"
    putStrLn $ show $ lengthCompare "zen" "ant"
実行結果
> runghc length_compare.hs
LT
GT

Scalaz で実装

まず、sbt 用のビルドファイルを作成しておきます。
今回は Scala 2.10.0-M7 と Scalaz Core 7.0.0-M3 を使うように設定しました。

build.sbt
scalaVersion := "2.10.0-M7"

libraryDependencies += "org.scalaz" % "scalaz-core" % "7.0.0-M3" cross CrossVersion.full

mainClass in (Compile, run) := Some("fits.sample.LengthCompare")


Scalaz を使った lengthCompare の実装は Haskell と大きな違いは無く、比較の箇所に ?|? を使って |+| (mappend でも可) で繋ぐだけです。

処理的には、OrderOps の ?|? で Ordering を得て、SemigroupOps の |+| で Ordering を Monoid として連結しています。*1

Scalaz版 LengthCompare.scala
import scalaz._
import Scalaz._

object LengthCompare extends App {

    val lengthCompare = (x: String, y: String) => (x.length ?|? y.length) |+| (x ?|? y)
    //以下でも可
    //val lengthCompare = (x: String, y: String) => (x.length ?|? y.length) mappend (x ?|? y)

    println(lengthCompare("zen", "ants"))
    println(lengthCompare("zen", "ant"))
}

sbt run で実行すると Haskell と同様の結果が出力されます。

実行結果
> sbt run
・・・
[info] Running fits.sample.LengthCompare
LT
GT
・・・

なお、?|? の代わりに compare の実行結果を Ordering.fromInt() で Ordering 化する等の実装方法もありますが、?|? を使った方がシンプルだと思います。(ただし、他に良い方法があるかもしれません)

他の実装方法 (結果は同じ)
val lengthCompare2 = (x: String, y: String) => Ordering.fromInt(x.length compare y.length) mappend Ordering.fromInt(x compare y)

println(lengthCompare2("zen", "ants"))
println(lengthCompare2("zen", "ant"))

println("-------------")

val lengthCompare3 = (x: String, y: String) => Order[Int].order(x.length, y.length) mappend Order[String].order(x, y)

println(lengthCompare3("zen", "ants"))
println(lengthCompare3("zen", "ant"))

println("-------------")

val lengthCompare4 = (x: String, y: String) => intInstance(x.length, y.length) mappend stringInstance(x, y)

println(lengthCompare4("zen", "ants"))
println(lengthCompare4("zen", "ant"))

*1:最終的に Ordering.orderingInstance の append が実行されます (実装は OrderingInstances トレイトの orderingInstance)