uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

GroovyでMaybe Monadを書いてみた。

モナドなんて難解なものを含んだ言語は業務で使えねー!!」とScalaの悪口を言おうとして調べてたらモナドが何となく分かった気がしたのでMaybe Monadをgroovyで書いてみました。面白いわこれはw。

ただ、HaskellScalaもちょっと読んだぐらいの経験しかないし、「JavascriptでMaybe」とか「RubyでMaybe」とかPerlでとかも参考にしてませんので、間違ってる可能性も大*1

例としては「モナドの全て」のクローン羊の話を実行しています。

なお、Maybeモナドモナドの中でも簡単なものだそうなので、わかったというのはおこがましい,ってのはその通りです。

さらに、Monadは、純関数型言語で使って最も効果を発揮するものだし、ライブラリや言語仕様やイディオムを含む、総体的な「モナディックプログラミング文化」としてでも多分捉えるべきなので、1個Maybeを定義したからと言って、利点や意義がピンとくる、というものではないですね。以下の例ならセーフナビゲーション(?.)で書くのと比べて何がうれしいの、とかいわれても、Groovyの範囲内で比較してもしょうがないというしかない。でも前に作ったGroovyの遅延評価と組み合わせたり、MonadPlusを定義したらもうちょっとおもしろいかも。

今回関数型言語のパワーの片鱗を見た気がしました。結局Scalaの悪口はやめておきますが、改めて思ったことはあるのでまた別途。

以下、とても参考になったページ:

class Monad<T> {
  T value;
  Monad() {}
  Monad(T a) {
    value = a
  }
  Monad<T> rightShiftUnsigned(Closure c) { // Haskell's >>=
    return c(this.value)
  }
  static Monad<T> Return(x) { new Monad<T>(x) }
}

abstract class Maybe<T> extends Monad<T> {
  abstract boolean isNothing();
  Maybe() {}
  Maybe(T a) {
    super(a)
  }
}

class Just<T> extends Maybe<T> {
  Just(T a) {
    super(a)
  }
  boolean isNothing() { false }
}

class Nothing<T> extends Maybe<T> {
  Nothing() {}
  boolean isNothing() { true }
}

class Sheep {
  Maybe<Sheep> mom
  Maybe<Sheep> dad
  String name
}

def Nothing = new Nothing<Sheep>();

father = { Sheep s ->
  if (s.dad.isNothing()) return Nothing;
  return new Just<Sheep>(s.dad.value);
}

mother = { Sheep s ->
  if (s.mom.isNothing()) return Nothing;
  return new Just<Sheep>(s.mom.value);
}

maternalGrandfather = { Sheep s ->
  if (mother(s).isNothing()) {
    return Nothing
  }
  Maybe<Sheep> m = mother(s)
  if (m.isNothing()) {
    return Nothing
  }
  return father(m.value)
}

mothersPaternalGrandfather = { Sheep s ->
  if (mother(s).isNothing()) {
    return Nothing
  }
  Maybe<Sheep> m = mother(s)
  if (m.isNothing()) {
    return Nothing
  }
  Maybe<Sheep> f = father(m.value)
  if (f.isNothing()) {
    return Nothing
  }
  return father(f.value)
}

Sheep s0 = new Sheep(name:'s0', dad:Nothing, mom:Nothing)
Sheep s1 = new Sheep(name:'s1', dad:new Just<Sheep>(s0), mom:Nothing)
Sheep s2 = new Sheep(name:'s2', dad:new Just<Sheep>(s1), mom:Nothing)
Sheep s3 = new Sheep(name:'s3', dad:Nothing, mom:new Just<Sheep>(s2))

assert s0.name == 's0'
assert s0.dad.isNothing()
assert s0.mom.isNothing()
assert s1.name == 's1'
assert s1.dad.value == s0


assert s1.mom.isNothing()
assert s2.name == 's2'
assert s2.dad.value == s1
assert s2.mom.isNothing()
assert s3.name == 's3'
assert s3.dad.isNothing()
assert s3.mom.value == s2

assert mother(s0).isNothing()
assert father(s0).isNothing()
assert mother(s1).isNothing()
assert father(s1).value.name == 's0'
assert father(s2).value.name == 's1'
assert mother(s2).isNothing()
assert maternalGrandfather(s2).isNothing()
assert maternalGrandfather(s3).value.name == 's1'
assert mothersPaternalGrandfather(s3).value.name == 's0'

assert (Monad.Return(s3) >>> mother).value.name == 's2'
assert (Monad.Return(s3) >>> father).isNothing()
assert (Monad.Return(s3) >>> mother >>> father).value.name == 's1'
assert (Monad.Return(s3) >>> mother >>> father >>> father).value.name == 's0'

*1:vlaueはMaybeの方に持つべきなんですかね。bind(Haskellの>>=,以下では>>>)もあえてMonadの方のデフォルト実装として書いてます。