シェルスクリプトでMaybeモナド(その1)

by @dekokun on 2012/08/05 13:46

Tagged as: ShellScript, Advent Calender.

まえがき

どうも、以前使ってたブログは開始1ヶ月たたないうちにこのブログに移設しました@dekokunです。

変態アドベントカレンダー in Summer 22日目となります。

21日目は@megascusさん“変態”を色々な文字集合で表したものをHexDampしてみるでした。

注意書き

ツッコミ歓迎(まだこのブログ、コメント欄がないですが…→コメント欄できました!!)

やること

シェルスクリプトでモナド作成します。今回、中途半端なのですが、まぁ、このアドベントカレンダー、3周目も回ってくると思いますのでそこまでに完全版を提出できたらと。

やろうと思った経緯

せっかく、前回のアドベントカレンダーの記事をシェルスクリプトにしたわけだし(シェルスクリプトでクロージャ(的な何か))、変態アドベントカレンダー発案者(?)のdaiksyさんはScalaを愛用している様子だったので、では関数型言語の人が好きそうなモナドをシェルスクリプトで作成してみようかしらと思いまして…

あと、以前、以下のようなツイートをしていたのですが、 そうしたら以下のような反応がありまして、

というわけで、作りたいなぁと思っていたというのもあります。

Maybeモナドとは

現在、かの有名なすごいHaskellたのしく学ぼう!でHaskellを学んでいるのですが、なんとまだモナドのところに達していません(現在アプリカティブファンクター)。 それにも関わらず、Maybeモナドを作ろうなどとひたすら恐れ多い感じではありますが、まぁいろんなところからまさかり飛んできたら楽しいなぁと思っているところです。

それはいいとして、Maybeモナドとは、失敗する可能性があることを表すMaybe型を返す関数の連鎖をうまくやってくれるように色々してくれるものです。 詳しくはこちらを見るといいのではないでしょうか。 http://www.sampou.org/haskell/a-a-monads/html/maybemonad.html

もうちょっと具体的には、失敗する可能性のある関数の連鎖において途中で関数が失敗したらその後の関数は実施せず、関数失敗の結果を返すような機構を提供するものという理解。

Haskellでの具体例は以下の通り(参考:RubyでMabyeモナドを実装してみた。 - このブログは証明できない。)

途中で計算が失敗する場合は以下の通り

簡単に言うとすべての計算が成功したら“Just 計算結果”が返ってきて、途中の計算が一つでも失敗すれば“Nothing”が返って来るわけですね

戦略

シェルスクリプトには、既にMaybeモナドの>>=に似たようなものとしてパイプが存在します。しかし、パイプは、前の関数が失敗した際も次の関数を実施してくれてしまいますので、Maybeモナドの>>=とは異なりますよね。

というわけで、自作します。

また、そもそもシェルスクリプトでMaybe型をどう表すかという点ですが、シェルスクリプトにおいては返り値として0を返すか1を返すかでJustかNothingかを表し、標準出力の部分でJust hogeのhoge部分を得ることができます。そうすると、既存のコマンドをそのままMaybe型を返す関数として扱えるため極めて都合がよくその方向で考えていたのですが、ちょっと色々あって一旦「標準出力が文字列のNothingであればNothing値、他の値であれば、Just値」という方向でこの日記は進みます。ひどい戦略ですね。

Just “Nothing”という値が返せないですしね(両方とも文字列のNothingですので)…

まぁ、次回の変態アドベントカレンダーまでにちゃんとしたものを作りたいなと思いながら、今日はこのひどい戦略に沿っていきます。

方針

こんな感じで使えるようなものを目指す

ret monad.sh | bind "ls" | bind "cat"

この場合、monad.shが存在すればそのファイルのcatまで行なってくれますが、存在しなければNothingが標準出力に出力されます。

Haskellのreturn, >>=がそれぞれret, パイプ+bindとなっております。bindされる関数が文字列で渡されるのが非常に辛い感じありますが、まぁシェルスクリプトだとしょうがないよね…

Nothingを標準出力で表現することにした理由

上記でパイプを使うようにしたおかげで、次の関数で前の関数の返り値(returnで返って来る値)が取得できなくなり、しかたなく標準出力が文字列NothingかどうかでJustかNothingを判定することにしたのでした…誰か、パイプの前の返り値を取得する方法しりませんか…

ls hoge | echo $?(hogeは存在しないファイル)は1ではなく0が出力されてしまうのです…ls hoge; echo $?と同様に1が出力されるとよかったのですが…

実装

前回のシェルスクリプトでクロージャ(的な何か)とは異なり、ちゃんとbashで動きます。

テスト

まずはMaybe型を返す(というか標準出力に出力する)関数を自作する必要があります。

実際に使ってみる

ret "hoge" | bind "maybe_add_line hogehoge" | bind "maybe_cat"
# -> hogehoge
ret "fuga:存在しない" | bind "maybe_ls" | bind "maybe_cat"
# -> Nothing

ちゃんと動いているように見えますね

モナド則のテスト

モナドには満たすべき法則があるようで、モナドを作る際はちゃんとそれを満たしてあげましょう。

Haskellでモナド則を表すとこんな感じらしい

今回のシェルスクリプト版ではこうなるんですかね。 標準出力を返り値とみなしているため、標準出力を比較する形となります、

モナド則1つ目

モナド則2つ目

モナド則3つ目

ご存知のように、Linuxにおいて0は正常ステータスを表しますので、この例においては無事全てのモナド則が満たされていることが分かるかと思います。

感想

本当にこれで正しいのかどうか全くわからんが、まぁそれっぽいのができたので比較的満足している。 よく見ると、標準出力を出力する関数は全てMaybe型を返す関数となっているのですね。なんかおかしくないかこれ…よくわからん…

次回への課題

上のほうにも書いていますが、終了ステータスが0で標準出力がhogeの場合にJust hoge, 0以外の場合をNothingとなるようにJust,Nothingを定義することにより、Linuxが提供している全てのコマンド/関数が自動的にMaybe型を返す関数になり非常に便利ですので、今度はそういうMaybeモナドを作成したいです。 誰か、パイプでつないだ前の関数の終了ステータスを得る方法を教えて下さい…もしくは、And演算子(&&)でつないだ前の関数の標準出力を得る方法でもいいです…

あとで気付いたこと

これ、わざわざこんなもん作らずとも&&で繋いで最後の標準出力と最後の終了ステータス見るだけでよかったんじゃねってなってるところ

シェルスクリプトにははじめからMaybeモナドが実装されてたんや!!!!!!

更にあとで気付いたこと

違う違う、&&では前の標準出力を使用できないからダメなんだよ!!!自分でそうやって書いてるじゃないか!!

誰か、パイプでつないだ前の関数の終了ステータスを得る方法を教えて下さい…もしくは、And演算子(&&)でつないだ前の関数の標準出力を得る方法でもいいです…

comments powered by Disqus