Monad がさっぱりなので試しになんか書いてみた

Monad むずいです。いつまでたってもいまいちちゃんと理解できないので、試しになんか書いてみることにしました。今回の題材は以下。

Meet the Monads

このページで使われているクローン羊さんの例を書いてみようと思います。…Vimで。

" Maybe Monad
let Maybe = {}

function! Maybe.return(m)
  return Just(a:m)
endfunction

" m(self) >>= f
function! Maybe.bind(f)
  let Func = type(a:f) == type('') ? function(a:f) : a:f
  return self['bind_' . self.type](Func)
endfunction
function! Maybe.bind_Just(f)
  return a:f(self.m)
endfunction
function! Maybe.bind_Nothing(_)
  return Nothing()
endfunction

function! Maybe.show()
  return self['show_' . self.type](self)
endfunction
function! Maybe.show_Just(m)
  return 'Maybe "' . a:m.m.show() . '"'
endfunction
function! Maybe.show_Nothing(_)
  return 'Nothing'
endfunction

function! Maybe.fail(_)
  return Nothing()
endfunction

function! Just(m)
  return extend(copy(g:Maybe), {'type': 'Just', 'm': a:m})
endfunction

function! Nothing()
  return extend(copy(g:Maybe), {'type': 'Nothing'})
endfunction



" -- Sheep
function! Sheep(name, mother, father)
  let sheep = {'name': a:name, 'mother': a:mother, 'father': a:father}
  function! sheep.show()  " Helper function
    return self.name
  endfunction
  return sheep
endfunction
function! Name(sheep)
  return a:sheep.name
endfunction
function! Mother(sheep)
  return a:sheep.mother
endfunction
function! Father(sheep)
  return a:sheep.father
endfunction

前半で Maybe モナドのような何かを定義しているつもりです。なお関数に渡すモナドは self になってます。
後半で羊さんを定義してます。Haskell なら型の要素を取り出す関数は自動で定義してくれるんですが、さすがに Vim がそんなことしてくれるわけないので全部自前です。

function! FmaternalGrandfather(sheep)
  return g:Maybe.return(a:sheep).bind('Mother').bind('Father')
endfunction
function! FfathersMaternalGrandmother(sheep)
  return g:Maybe.return(a:sheep).bind('Father').bind('Mother').bind('Mother')
endfunction
function! FmothersPaternalGrandfather(sheep)
  return g:Maybe.return(a:sheep).bind('Mother').bind('Father').bind('Father')
endfunction


let breedSheep = {}
let breedSheep.adam   = Sheep("Adam", Nothing(), Nothing())
let breedSheep.eve    = Sheep("Eve", Nothing(), Nothing())
let breedSheep.uranus = Sheep("Uranus", Nothing(), Nothing())
let breedSheep.gaea   = Sheep("Gaea", Nothing(), Nothing())
let breedSheep.kronos = Sheep("Kronos", Just(breedSheep.gaea), Just(breedSheep.uranus))
let breedSheep.holly  = Sheep("Holly", Just(breedSheep.eve), Just(breedSheep.adam))
let breedSheep.roger  = Sheep("Roger", Just(breedSheep.eve), Just(breedSheep.kronos))
let breedSheep.molly  = Sheep("Molly", Just(breedSheep.holly), Just(breedSheep.roger))

let dolly = Sheep("Dolly", Just(breedSheep.molly), Nothing())


echo FmaternalGrandfather(dolly).show()
echo FfathersMaternalGrandmother(dolly).show()
echo FmothersPaternalGrandfather(dolly).show()

前半でお母さんのお父さんや、お父さんの母方の祖母や、お母さんの父方の祖父を探す関数を定義しています。
後半で世代関係のデータを作り、試しに探してみています。

以上のを合わせたのをここに貼っておきます。

で、これを実行すると以下のようになります。

Maybe "Roger"
Nothing
Maybe "Kronos"

おーちゃんと動いているようです。

比較

Vim:

function! FfathersMaternalGrandmother(sheep)
  return g:Maybe.return(a:sheep).bind('Father').bind('Mother').bind('Mother')
endfunction

Haskell:

fathersMaternalGrandmother :: Sheep -> Maybe Sheep
fathersMaternalGrandmother s = (return s) >>= father >>= mother >>= mother

形はだいぶ似てますね。処理自体も遅延評価じゃないところを除けば大体同じ…のはず、です。
今回の場合、モナドを使う事で、普通にやったら親を辿る度に親がいるかのチェックが必要なところをそのままバシバシ書けました。途中で親がいなくても返ってくるのは同じモナドなので同じように処理が続けられるってことなのかな。でもそれはMaybeモナドの話でモナド自体とはまた話が違うような…。うーーん、書いてみたけどやっぱりよくわかんない。

結論

とりあえず何でも Vim で書こうとするのはやめましょう。