uehaj's blog

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

Groovyでリストモナド(orリストアプリカティブ)を書いてみる

Groovyでモナドを書くシリーズその3、「Groovyでリストモナド(orリストアプリカティブ)を書いてみる」です。(参考: GroovyでMaybeモナドを書いてみたGroovyでStateモナドを書いてみる)。

Haskellではリストは、「要素が非決定計算のそれぞれの可能性を表す」モナドとして見做すことができます(そういう風にみなすためのbindが定義されている)。

なのでおもしろいことができるのですが、それをGroovyで真似て見ようというわけです。

List.metaClass.rightShiftUnsigned = { Closure c -> // Haskell's >>=
    delegate.collect(c).inject([], {acc,elem->acc+elem}) // concatMap
}
// instance Applicative [] where
//   fs <*> xs = [f x | f <- fs, x <- xs]
List.metaClass.multiply = { List xs -> // Haskell's <*>
    List fs = delegate
    fs >>> {f->
    xs >>> {x->
        assert f instanceof Closure
        if (f.parameterTypes.size() > 1) {
            // Haskellの様に引数が足り無ければ自動的に
            // 部分適用になるということは無いので、明示的に部分適用。
            return [f.curry(x)]
        }
        else {
            return [f(x)]
        }
    }}
}

// リストモナドは非決定計算(可能性の計算)を表す。
// つまり「1 or 2 or 3」と「4 or 5 or 6」を掛け算した結果は、
// 「4 or 5 or 6 or 8 or 10 or 12 or 12 or 15 or 18」となる。
listManip1 = [1,2,3] >>> {x->      // listManip1 = do x <- [1,2,3]
             [4,5,6] >>> {y->      //                 y <- [4,5,6]
             [x*y]                 //                 pure x*y
             }}
assert listManip1 == [1*4, 1*5, 1*6, 2*4, 2*5, 2*6, 3*4, 3*5, 3*6]
println listManip1

// アプリカティブは、箱の中に対する透過的な演算を可能とする。
// モナドはアプリカティブでもある。
// リストモナド(非決定計算)に対して通常の関数(1引数関数(x+1))を適用する。
listManip2 = [{it+1}] * [1,2,3]   // listManip2 = pure (+1) <*> [1,2,3]
assert listManip2 == [2,3,4]
println listManip2

// アプリカティブは、箱の中に対する透過的な演算を可能とする。
// モナドはアプリカティブでもある。
// リストモナド(非決定計算)に対して通常の関数(2引数関数x+y)を適用する。
listManip3 = [{x,y->x+y}] * [1,2,3] * [4,5,6]
// listManip3 = pure (+) <*> [1,2,3] <*> [4,5,6]
assert listManip3 == [1+4, 1+5, 1+6, 2+4, 2+5, 2+6, 3+4, 3+5, 3+6]
// 「1 or 2 or 3」と「4 or 5 or 6」を足し算した結果は、
// 「5 or 6 or 7 or 6 or 7 or 8 or 7 or 8 or 9」となる。
println listManip3

ちなみに上では、Haskellでは「型クラス」であるApplicativeやMonadを、Groovyのクラスとして表現できていません。「データ.操作()」というパターンしか継承の対象にできない、GroovyやJavaなどのクラスベースの型システムの限界があるのですが、リストにメタクラスでオペレーションを付け足しして強引に突破してます。ある意味わかりやすい。

ちなみに上のコード中に出てくる

listManip1 = [1,2,3] >>> {x->      // listManip1 = do x <- [1,2,3]
             [4,5,6] >>> {y->      //                 y <- [4,5,6]
             [x*y]                 //                 pure x*y
             }}

は、[ x*y | x<- [1,2,3], y<-[4,5,6] ]というリスト内包表記というのがHaskellにあって、それはこのようなリストモナドに対する操作のシンタックスシュガーとして実現されています。コメントにあるのが、もう一つ別のdo記法というやつでこれもモナド操作のシンタックスシュガーです。Scalaだとfor{…}というモナド内包表記になるのかな(良く知らない)。


お勧めの本「すごいHaskellたのしく学ぼう!」Kindle版

すごいHaskellたのしく学ぼう!
オーム社 (2012-09-21)
売り上げランキング: 2,943

お勧めの本「すごいHaskellたのしく学ぼう!」Kindleじゃない版。

すごいHaskellたのしく学ぼう!
Miran Lipovača
オーム社
売り上げランキング: 15,643

GroovyでStateモナドを書いてみる

4年前に、GroovyでMaybe Monadを書いてみた。という記事を書きましたが、続編としてStateモナドをGroovyで書いてみます。
いかに当時わかってなかったかが判りました。

abstract class Monad {
    abstract Monad bind(Closure c);
    Monad rightShiftUnsigned(Closure c) { // Haskell's >>=
        bind(c)
    }
    abstract Monad bind0(Monad m);
    Monad rightShift(Monad m) { // Haskell's >>
        bind0(m)
    }
}

class State extends Monad {
    Closure runState // runState :: s -> [a,s]
    State(Closure runState) {
        this.runState = runState
    }
    @Override
    State bind(Closure funcReturnsState) { // bind :: State s a -> (a -> State s b) -> State s b
        return new State({ oldState ->
            def (returnValue, newState) = runState(oldState)
            funcReturnsState(returnValue).runState(newState)} )
    }
    @Override
    State bind0(Monad/*State*/ state) { // bind0 :: State s a -> State s b -> State s b
        return new State({ oldState ->
            def (_, newState) = runState(oldState)
            state.runState(newState)})
    }
    static State Return(x) { // Return :: a -> State s a
        new State({ s -> [x, s] })
    }
}

def push(n) { new State({stack-> stack.push(n); [null, stack]})}
pop = new State({stack-> [stack.pop(), stack]})

stackManip = push(3) >>
             push(4) >>
             pop >>> { x ->
             pop >>> { y ->
             push(x+y) >>
             pop
             }}

(value, stack) = stackManip.runState([])
assert value == 7
assert stack == []

モナドは別言語で作ってみないと結局理解できませんね〜。少なくとも私はそうでした。
次は「IOモナドをGroovyで書いてみる」行きます。

c.f.
uehaj.hatenablog.com


(追記)id:kmizushimaさんがScalaでStateモナドを書いているのを発見(笑

Haksellでwhileを定義する

Haskell WIkiの「IO Inside」に、

Haskell's ability to work with IO actions as with any other (functional and non-functional) values allows us to define control structures of arbitrary complexity. Try, for example, to define a control structure that repeats an action until it returns the 'False' result:

while :: IO Bool -> IO ()
while action = ???

Most programming languages don't allow you to define control structures at all, and those that do often require you to use a macro-expansion system. In Haskell, control structures are just trivial functions anyone can write.

(私訳)

HaskellのIOアクションは、(関数型か否かに関わらず、)他のどんな値とも同様に扱うことができ、任意の複雑な制御構造を定義することができます。試しに、値がFalseを返すまで繰り返す制御構造を定義しみてごらんなさい。

while :: IO Bool -> IO ()
while action = ???

多くのプログラミング言語では、制御構造を定義することが全くできないか、マクロ拡張システムを必要とします。Haskellでは、制御構造は誰もが書けるただの関数にすぎません。

とあるのでやってみた。conditionとactionを分けてみました。

import Data.IORef

while :: IO Bool -> IO a -> IO ()
while cond action = do
  c <- cond
  if (c) then do action
                 while cond action
  else return()

main :: IO ()
main = do
  i <- newIORef 0
  while (do { n<-readIORef i; return (n<10) }) (do
              n<-readIORef i
              modifyIORef i (+1)
              print $ "hello"++show n)


「制御構造」に見えるかな。
whileに与えるところでdoを使うのが違和感があるので、使わないようにするとこうなる。

import Data.IORef

while :: IO Bool -> IO a -> IO ()
while cond action = do
  c <- cond
  if (c) then do action
                 while cond action
  else return()

main :: IO ()
main = do
  i <- newIORef 0
  while (readIORef i >>= (\n -> return (n<10) )) (do
              n <- readIORef i
              print $ "hello"++show n
              modifyIORef i (+1))

Groovyならクロージャを渡すようなことで実現できますが、上ではクロージャを全く使ってないところが興味深いです。(lambda式は>>=に渡すために使っているだけ)。

whileに渡したりwhileから返されたりするIOの値に含まれるRealWorld値(モナドによって隠蔽されている)は、それを導くのに使った計算に紐付いて来ていて、そのRealWorld値がmainのdoの地でも受け渡されていることで、mainの結果の計算に関与することになり、mainから始まっている命令的実行に組み込まれるようになるのだな…というイメージがないと黒魔術としか思えないです。


RealWorldによる値の制御は、プログラム上の表記位置とは全く独立なGotoみたいなもの。
グレッグ・イーガンの「順列都市」に出てきた「塵理論」を思い出させる、、って誰か指摘してるかな。

IOはいかにして命令書(アクション)であるのか:(たぶん)解決編

えー、先日「IOはいかにして命令書(アクション)であるのか」という記事を書きましたが、その疑問は「IO inside」を読んだところすべて氷解したのでご報告します*1

要するに、

type IO a  =  RealWorld -> (a, RealWorld)

です。おしまい*2id:nobsunさんやid:kazu-yamamotoさんが指摘していてくれたことは今おそえばおそらくこれでした。IO aは値ではなく関数なわけです。代数型データ構造(Hoge Int)みたいな表記に惑わされてコンテナだと見誤ったのが、誤解の元。

関数型がファーストクラスな言語では任意の計算が任意に遅延評価できるのだから「遅延評価仮説」は成り立たないし、この意味での副作用の除去*3の理由として持ち出す必要はありません。すみません。

純粋だから何なのさ!

ではいかなる意味で、副作用除去(というより無いと言い張るためにトリック)なのかというと、引数を追加した上でその引数を隠蔽するという一休さんレベルのウルトラトリックなのですがInside IOを読んでもらうのが良いでしょう。

あとは余談です。もう引っこんでろ!という気もしますが。自分の感想メモです。

Inside IOの説明から受ける印象は、「Haskellに副作用が無い*4こと」というのは、「特別・特殊な工夫によって初めて実現された、すばらしい機能」ではないということです。むしろ「命令的に実行する機能の欠落」であり、もっと正確に言うと「命令的に実行しようとすると整合性が破壊される」という欠陥のようなものです。このことが所与で、そのような整合性破壊を防ぐ工夫が、IOアクション=命令書であると。安定性を意図的に低減した上で、コンピュータ制御で安定させて飛ぶ「運動能力向上機」みたいな。

「...(命令書か何か)…によって副作用の除去が実現された」のではなく、もともと無いのが前提で所与。コンパイラをそう作っちゃったんだけど、困ったねえ、というところから始まる*5。マイナスから始まって普通の命令型言語程度までなんとか並ぶとこまで戻すのが命令書。

以下、例え話です。

架空言語Hの話

某H言語のコンパイラは、オプション「-OsuperAgressive」を付けると、すべての関数呼び出しをmemoize(キャッシュ)する。それだけでなく「-OsuperAgressive」の凄いところは、値の使用を精密に検出して、不要な呼び出しを徹底的に削除してしまうところだ。最終結果の算出に「関数」の字義通りの意味で寄与しない関数の呼び出しを軒並削除してしまう、というより、計算をリオーダーして本当に必要になるときまで計算を引き延ばすことによって(実行時の大域的依存性解析に相当)、本当に必要な計算しかしない。本当にしないのだ。不要な計算がなされることは絶対にない。

しかし、副作用を伴う処理をしようとしたとき、問題が生じる。副作用を伴う関数を呼び出すことは何なくできるとはいえ、その制御が全くできないのだ。同じ引数だと二度目の呼び出しは無視される(値は同じはずだ、と仮定されてmemoizeされたキャッシュ値で代替されてしまう)わ、値を返さなかったり、結果の値を使わないなら不要コードとして呼び出し自体が削除されてしまう。リオーダーも激しく、この結果、副作用を任意の順序で発生させることができないし、そもそも確実に発生させるということができない。

問題は、オプション「-OsuperAgressive」は、とある事情で外すことができないということだ。(外した状態のソースコードがHDDのクラッシュで失なわれてしまったか、外すためにはものすごい金額の有償版へのアップグレードが必要!とか。なんかそんな理由だ)。また、「aの次にbを呼ぶ」という単純構文がエラーとなるというバグもある。ヤレヤレ。

このクソな状態を「純粋だ」とか言う人もいるらしいいってね。ものは言いようだ。Hahaha.

さて、あなたのミッションだが、この某H言語のコンパイラを使って、副作用を持つ関数を呼び出してなんとか動作するプログラムを書く必要があるということだ。なにしろその基本性能はすばらしいのだから…。

ということで編み出しましたのがこの方法です。

〜スクロールしながら現われる: Haskell IO 〜


Inside IOを読んでの印象は「おまえは何と戦っているんだ」です。コンパイラの制限との戦いで何を得ているんだと。「unpureという予約語を導入する」とか「副作用あり指定プラグマ」じゃ駄目なんですか??!!!

「参照透過性」という名目のため?でも、RealWorldを導入することで死守できた参照透過性というのは絵に書いた餅でしかない。お前は今まで呼んだIO関数のRealWorld引数の値を覚えてるのか?ってことです(覚えてませんしそもそも知りません)*6

おそらく、命令型という性質を外付け・客体化・操作対象としていることが意義なのでしょう。

Hskellの純粋性は、むしろ原始的なもの。命令型言語処理系では不可分な総体だと思われていたものを「純粋部」と「命令部」に分解し、命令部をライブラリレベルで実装しオープン化している。Cで入出力がそうされたように。

結局Haskellに副作用はあるのかないのか?

ないという前提のコンパイラ上に、あるように見せかける機構が作り込まれている。「本当はないのだがあるかのように作り込まれている」。その作り込みがあまりにも巧妙で、デメリットも含めて「副作用の有り状態」が再現されているので「本当は副作用が無い」と言うことの意義が良くわからなくなるぐらいである。

*1:この文書はHaskellの初歩的な理解のために必読だと思われます。

*2:GHCなどの実装で本当に文字通り上記かの通り定義されているかというと、たしか違う。ST s aが介してるんだっけかな。

*3:命令書と実行系を分けて考えるという意味での副作用の除去の意味で。

*4:引数によってのみ関数結果が定まる、という意味で。

*5:もちろん本当はそうではないでしょうけど。

*6:もしそれを覚えていて引数とmemoizeされた返り値も併せて利用できるなら、「バックステップ実行」とかができて楽しいかも。スタックトレースなんかよりはるかにデバッグには有効でしょう。

IOはいかにして命令書(アクション)であるのか

2013.10.14追記、IOはいかにして命令書(アクション)であるのか:(たぶん)解決編を書きました。
2013.10.9 AM6:21「追記その2」として「おぼろに全容として思いついた理解」を追記しました。

Haskellおもろいで

この1ヶ月ぐらいHaskellをかじってます。Haskellはこれ以上ないってぐらいエキサイティングな言語で楽しいものですね。まだ初心者ですが40半ばの手習いです。どうでもいいですが、この年齢だと新パラダイム言語に挑戦するのはもう最後のチャンスぐらいかなと思ってますが、そんなことはどうでもいい。

この果てしなく遠いモナド

ところで、Haskellには「IOモナド」という概念があるのですが、なかなか判るまで判りにくいです。

IOモナドは入出力というごく基本的な機能にかかわるものであり、初心者でも否応なく直面せざるを得ない機能であるにもかかわらず、「モナド」なる概念操作をベースに作られているものなので「モナドの壁」にまずぶちあたることになります。IOモナドモナドの一種です。モナドっていうのはご存知の人も多いと思うのですが、いろんな人がいろんなことをブログに書いているので初学者にとって混乱しやすい概念です。このブログ記事もその一つです。

でも、本記事はモナドが何かを説明することを目的とはしていません。ご安心ください。

基本的にはモナドは明確な概念ですが、高階操作と多相型操作に強く基づいており、そのような操作に頭だけではなく手を動かして体得した上ではないと理解できないような、そういう脳トレを経た上で「体で(脳細胞の連接を作る)」覚える部分もある概念だと思います。ちなみにわたしはまだまだその域には達することができてはいません。

さてそのような学習者にとって、id:kazu-yamamoto さんが書かれた
QAで学ぶMonad」という記事はモナドをたいへんわかりやすく整理されており、珠玉の良記事です。

この記事の趣旨は

Monad とは、単なる型クラスの一つです。難しいという風評もありますが、それ以上でもそれ以下でもありません。

につきると思っています。名記事です。モナドなんて怖くないぞ!

「命令書」に例えられるIO

しかし、この記事を読んで行くと

しかし、実際 getChar が返すのは「一文字読み込め」という命令書です。どんな状況においても、同じ命令書を返します。ですから、副作用はないのです。副作用を発生させるのは、命令書を実行するランタイムです。

IO は命令書であり、命令ではありません。>>= を使えば、小さな命令書を合成して、大きな命令書を作成できます。最終的な命令書には main という名前が付きます。これを実行するのはランタイムです。

ただ、IO が命令書であることを活かせるようになるには、相当修行を積む必要があります。そこで最初のうちは、「IO は副作用を起こす関数を表している」と理解しても問題はありません。IO が付いていなければ純粋で、IO が付いていれば副作用があって、それらを明確に区別できるから Haskell は素晴らしと理解しても結構です。

ただ、Haskell を使っていくどこかの時点で、正しい理解をして下さい。

モナドがわかったような気持になったのが、この時点で見事に打ち砕かれます。モナドは単なる「単なる型クラスの一つ」だったはずが、重要なモナドであるIOに限って(?)は「命令書」なる未知の概念の体現であり、その活用には相当修行が必要であり、「まあ、せいぜい初心者は便宜的な理解をしておいてください。それは結局はくつがえされるであろう理解なのですが、せいぜいHaskell は素晴らしいと理解しておいてください。いつか正しい理解をしたときにまた御会いしましょう、でわ!!」なのです。

ずこーっ、と思いますよね。じゃあその命令書って何だ? って思いますよね。
先の記事を揶揄するつもりはありません。「IOは命令書」の意味がわからないと、モナドとしてのIOに限っては、元記事の趣旨をくつがえしてしまうかのように見える、ということです。

なお、IOの結果は「命令書」だから、Haskellそのものは純粋だ、と言うことの核心的根拠としても使われる場合があるようです。

例えば「Haskellには副作用がないのか?」という記事では、

評価器までがHaskell
この立場の説明では、Haskell には副作用はない。なぜなら、Haskell が作るのは命令書のみで、それが実行されるのは Haskell の外での話だからだ。
たとえば、getChar :: IO Char は、実行されるごとに(同じこともあるが)別の文字を返す。それでも Haskell には副作用はない。Haskell が作り出すのは、「標準入力から文字を読み込め」という命令書だけであって、実行はしないからだ。

などです。

しかしながら、上の立場を検討するための前提条件として、「なんでIOの結果は命令書なのか」がやはり問われる必要があります。

なぜ実際のIO処理の起動(評価)が、評価器ではなく実行器側でなされるのか? それができるという前提だからこそ、上の主張が可能なわけです。「IOの結果がいかなる意味で命令書であるのか、いかにしてそれは命令書なのか、この場合の命令書とはどういう振舞いを差して命令書と言っているのか」を明確にしないなら、なんでもありの議論になりかねません。なんとなくな比喩としてなら判るのですが、それは理解ではありません。

命令書遅延評価仮説

「命令書」とは何かも、その機構も明確ではないので、とりあえず自分が推測した限りでつじつまの合う、もしくは比喩として説明のつきやすい機構を考えてみましょう。

まあ初心者の言うことなので、あてにはならんのですが、あがきです。仮説は以下のようなものです:

命令書であることの必要条件は、何の変哲もない多相型(IO a)を実体型(IO Charなど)にしたコンテナであるはずのIOを返す関数の結果である純粋データ=値を、実行器(ランタイム)が解釈実行するにあたっては、実際に副作用を持った操作を含む操作列として正しい順序で実行でき、元のHaskellプログラムの必要性に従い値と副作用を得られることです。また、mainに設定できる値はmainへの代入の右辺を評価器が評価した結果であるところのIO aを実体化した型のデータのみですから、命令書はその型のデータのみで表現できる必要があります*1

上から想像できるのは、これを実現できそうなのは、遅延評価の枠組みそのものだ、ってことです。

元の「命令書」という表現をした人が、「命令書」という言葉で何を想定していたかは置いておいたとしても、上記は上記で命令書として機能しそうな仮説ですよね。なお、ややこしくなりそうなのでこの仮説「遅延評価で実現される命令書」を「命令書X(エックス)」と以降呼ぶことにします。

この仮説が正しいとするなら、「命令書X」としての動作を実現しているのは遅延評価の仕組みであり、IOともモナドとも関係がありません。Haskellの評価機の純粋性を担保しているのも、遅延評価ということになります。評価機の中でIOを返す関数が何度呼び出しても同じ結果を返すのは、異なる結果が発生するのは実行時なので、それが実行器(ランタイム)に先伸ばしにされているからに他なりません。IOはうっかり屋さんのために、副作用コードと純粋コードを分けて、その混合をコンパイルエラーとして排除するためだけの機構で、便利なのでモナドを使って実装しているだけ、ということになります。

またもしそうならば、先の「評価器までがHaskellだ」という主張について、同感するかどうかはともかく全く疑問がありません。「命令書」というわけのわからない未定義語が説明に含まれなくなり、意味がわかるようになるからです。

先の仮説が、「命令書の定義」とは別に機構的には成立しそうに感じられる話が「本物のプログラマはHaskellを使うITpro第7回 入出力と遅延評価の間を取り持つIOモナド (2/3)」という記事にありました。

モナドによって複数のI/Oアクションがきちんと順序づけられるとして、I/Oアクションの呼び出し自体はどうなっているのでしょうか? Haskellでの値が遅延評価されるのに対し、Cの標準ライブラリやシステムコール(system call)などのAPIは、遅延評価を行わない一般的な言語から呼び出すことを前提に提供されています。そのままではI/OアクションをHaskellから利用することはできません。
実はHaskellの値は、遅延評価を可能にするために、マシン表現(ビット・パターン(bit-pattern)、非ボックス化型)を直接扱うのではなく、「未評価の中断(suspension)状態、あるいはデータ構成子に格納された値のマシン表現のいずれか」を表現するヒープ上のオブジェクト(heap-allocated object)へのポインタ(ボックス化(boxed)された型)として扱われるようになっています。だとすれば、I/Oアクションの呼び出しを行う前にHaskellの値を評価し、APIにマシン表現を渡してI/Oアクションを実行し、返値のビット・パターンを改めて(IO型というコンテナに包んだ)Haskellの値として格納すればよいのではないでしょうか?
これができれば、あとは実行環境で外のAPIを呼び出すようにすることで、I/Oアクションを実現できます。

残る問題1

上の仮説が正しいかどうかは正直今の自分では判りませんが、もし正しいとしても疑問なのは、世の中で、なぜかIOだけが命令書として説明されているように感じられることです。上の記事にも、すごいHaskellたのしく学ぼう!、とかこちらの記事にも「IOは命令書(アクション)」と書いてあります。遅延評価仮説が正しければ、IOだけではなく、遅延評価される式はすべてが命令書Xの性質を満しているはずなのに…。

もちろん「IOが命令書Xは正しい」のですが、言及がIOに関してだけだと「それ以外はどうなの?命令書Xじゃないの?書いてないってことは違いそうだな。ならIOだけが命令書になる合理的な理由がわからないし、うーんじゃあ結局わからない」という人がいそうです←私。

それはそうと、「命令書概念の成立の根本には遅延評価が寄与している」とどこを見てもひとことも書いてないし。ていうことはやっぱり違うんだろうか?? それともあたりまえすぎて、言うまでもないからだろうか?

IO限定なら、「命令書」はHaskellにだけ通用する説明になってしまいます。そうでないなら、言語を越えて、「命令書概念」が共通に成立するための条件を知りたいのです。

残る問題2

「QAで学ぶMonad」 の記事には、再掲ですが

IO は命令書であり、命令ではありません。>>= を使えば、小さな命令書を合成して、大きな命令書を作成できます。最終的な命令書には main という名前が付きます。これを実行するのはランタイムです。

と書いてあります。上の仮説によれば、>>=を使う使わないに関わらず、命令書Xは合成されるので、ここで言う命令書と命令書Xとは、やっぱり異なるものなのかもしれません。

まとめ

おしまいです。初学者としての疑問と見解をまとめてみました。ご存知の方がいらっしゃいましたら、ご教示頂けますと幸いです。

追記その1

適切に定義された>>=により、IOが実行順序を制御する存在となっているというのは理解しているつもりです。「QAで学ぶMonad」にはまさに

IO が記述順に実行されるのも、同じ理屈です。たとえば、IO の実現方法として、Worldという正格データ、すなわち実行環境を渡していく方法があります。
(略)
Monad が実行順を決めるのではなく、Monad のあるインスタンスの >>= が、実行順を守るように定義されているだけです。

とありますしね。でも、このことをもってして「命令書」と言っているということなのでしょうか。例えば、

f(g(h))

h,g,fの順に評価されることを期待できると思いますが、これは命令書なんでしょうか。
あるいはこれを巧妙に例えば

do
h
g
f

のように表記できるシンタックスシュガーがあるから、命令書になるんでしょうか?
ここまでならIOやモナドだけに閉じた話ではないですしね。

もっと複雑な操作、非モナド値との相互作用を含めて記述できるから、命令書なんでしょうか。
ならば、その境界がわからないな…。
そしてそれが純粋性を生じさせることに寄与しているとしたら、そう思おうとすることの動機が理解できません。

追記その2

おぼろに全容として思いついた理解は、純粋関数型言語Haskellの上に、命令型言語のエミュレーション層を作ったよ(ドヤッ!)、ということかなと。その命令型言語のコード列を「命令書」と呼んでいると。

その命令型言語のエミュレーション層のフロントエンド(構文解析側)はモナドを駆使した内部DSLとして実装されておりそれをIOモナドと呼ぶ。コンパイラが生成する中間コードは遅延評価により評価途中の値を含む、Haskellのサンクから構成される評価値の木構造。その言語のバックエンド側(ランタイム、インタプリタ?)の実行は遅延評価が担保していると*2

利点はおそらく、Java VM上で、C言語からトランスレートされたコードを実行するようなもんで、底が抜けない、と。

「命令書」という名称は、目的的、用途的につけられた名称であるため、機構的な意味での言語的な差別化要因はない*3。機構的には、IOを返す関数の結果と他のモナドやリストを返す関数と違いはない。遅延評価はいずれに対しても本質的な実装機構。

今はこれが精一杯。



すごいHaskellたのしく学ぼう!
Miran Lipovača
オーム社
売り上げランキング: 16,143

*1:特別扱いはされていないという仮定はそんなに不自然ではないでしょう。

*2:もっとも、バックエンドはhaskellのそれそのものです。

*3:あえていえば、haskellの処理系の実装者は入出力処理を行う組み込み関数は細心の注意を払ってIOを返すようにしているであろう。

Bytecode DSL & Indy & Brainf*ck、副題「Indyを世界一簡単に扱う方法」です。

G*Workshop Z「JGGUG名物・ライトじゃないLT大会 - JGGUG G*ワークショップZ Sep 2013」でLTをしました。

http://www.slideshare.net/uehaj/gws-lt-20130920key

Bytecode DSL & Indy & Brainf*ck、副題「Indyを世界一簡単に扱う方法」です。

この資料は、以前の記事「indyでBrainfuckを実装してみよう!」の元ネタ情報であり、作っているのは同じBrainf*ckコンパイラですが、背景とか説明を加えたものになっています。

JVMの手軽なアセンブラが使えるようになったってことで、今度は他の言語のパーサやコードジェネレータを作ってみたいものですよね。本件のさらに詳しい情報は、そのうち公開予定の[JGGUG G*Magazine:title=http://grails.jp/g_mag_jp/] Vol7に解説記事を書きましたので、そちらを参照のこと。(追記: こちらこちらで読めます)

「Groovyスクリプト100本斬り」回顧編

2010年のJGGUG合宿で、

企画Groovy: 「Groovyスクリプト100本斬り」
お題(公募中!)を事前に100個用意しておき、みんなでスクリプトを書きまくります。
詳細はこちら:http://www.jggug.org/Home/ibento-shiryoushuu/g100pon.pdf

というのがあったのですが(良い企画でしたね!)、結果がGoogle Docsでしか公開されておらず、HTMLの記事になってると良いかと思い再掲します。


番号お題提案者(Twitter ID)回答者(Twitter ID)回答へのリンク(GistのURL)
1HelloWorld(Java完全互換版)kazuchikakazuchikahttp://gist.github.com/618307
2リスト操作(基礎編:CRUD系操作とイテレーションkazuchikaKentaro_Araihttp://gist.github.com/619102
3リスト操作(上級編)kazuchika
4マップ操作(基礎編:CRUD系操作とイテレーションkazuchikaKentaro_Araihttp://gist.github.com/619116
5マップ操作(上級編)kazuchika
6範囲(Range)操作(基礎編:CRUD系操作とイテレーションkazuchikaKentaro_Araihttp://gist.github.com/619233
7範囲(Range)操作(上級編)kazuchika
8Groovy Ustream APInemo_kazikikkohttp://gist.github.com/619452
9Jakarta POI のGroovyサンプルnemo_kaznobeanshttp://gist.github.com/619324,
http://gist.github.com/619325

10Google Calendar API の Calendar ObjectのGroovy Wrapperサンプルnemo_kaz
11Twitter: stream APIを有効活用したサンプルnemo_kaz
12Twitter: OAuthの認証サンプルnemo_kaztomoakioshimahttp://gist.github.com/619818
13gmailからメールを受信するkompironbqxhttp://gist.github.com/619274
14twitterにつぶやくkompirokazuchikahttp://gist.github.com/619087
15Markdownの実装kompirotomoakioshimahttp://gist.github.com/619068
16wikiの実装kompiro
17テンプレートエンジンの実装kompiro
18単位変換kompirokijukyhttps://gist.github.com/1145165
19為替情報の取得kompironemo_kazhttp://gist.github.com/619226
20topコマンドの実装kompironemo_kazhttps://gist.github.com/711401
21正規表現:ファイルの拡張子を置換するmasanobuimaitomoakioshimahttp://gist.github.com/619042
22正規表現FQCNからクラス名を抜出すmasanobuimaimasanobuimaihttp://gist.github.com/621826
23ディレクトリを走査して特定のファイルを抽出masanobuimaitomoakioshimahttp://gist.github.com/619059
24grepmasanobuimaimasanobuimaihttp://gist.github.com/621842
25tail -fmasanobuimaikazuchikahttp://gist.github.com/619953
26文字列を分割してListに変換masanobuimainobusuehttp://gist.github.com/619000
27ファイル名の一括置換masanobuimaimasanobuimaihttp://gist.github.com/619348
28ファイル内の特定の文字列を一括置換masanobuimaimasanobuimaihttp://gist.github.com/619303
29growlみたいなの(あんのか?)masanobuimainobusuehttp://gist.github.com/619816
30ファイルのコピーmasanobuimaikazuchikahttp://gist.github.com/619010
31アプリケーションランチャ(quicksilverみたいやつ)nbqx
32文字コード判定nbqx, kanemukanemuhttp://gist.github.com/619125
33掲示板kanemukiy0takahttp://gist.github.com/713740
34メール送信kazuchika
35インライン要素を含むXMLの処理kanemu
36ファイルをドラッグ&ドロップできるウィンドウkanemumasanobuimaihttp://gist.github.com/619202
37GDKのコマンドラインビューアnobeansuehajhttp://gist.github.com/619360
38(定期的な)メールチェッカ(POP)nobeans
39JSONの読み書き。Grails無しでtoby55kijkanemuhttp://gist.github.com/619030
40XMLの読み書きを色んな方法でtoby55kijnobusuehttp://gist.github.com/619066
41スクリプトを実行するスクリプトtoby55kijnbqxhttp://gist.github.com/619331
42お手軽JMXビューアbikisukenobusuehttp://gist.github.com/619284
43zip圧縮・解凍bikisuketomoakioshimahttp://gist.github.com/619011
44DBとテーブル名を引数に実行すると、標準入力から読み取ったCSV/TSVデータを指定したテーブルにinsertするスクリプトuehajnobusuehttp://gist.github.com/619054
45日時の演算処理。例えば「現在時刻の3日と4時間後を求める」「特定の時刻間の時間数を求める」nobusuekazuchikahttp://gist.github.com/619359
46wc コマンドtouchez_du_boiskiy0takahttp://gist.github.com/619085
47クイックソートtouchez_du_boisfumokmmhttp://gist.github.com/211632
48find コマンドtouchez_du_bois
49閏年touchez_du_boisnobusuehttp://gist.github.com/619200
50回帰分析touchez_du_boisnobusuehttp://gist.github.com/619868
51KVSの操作(保存、取得)kimukou_26nobusuehttp://gist.github.com/619988
52XMLDB(existが一番簡単かな?)の操作kimukou_26kimukou_26http://gist.github.com/622392
53いろいろな型変換(toメソッド、キャスト、as)kazuchikatoby55kijhttp://gist.github.com/619996
54duck typingkazuchikakazuchikahttp://gist.github.com/619806
55マルチメソッド(動的ディスパッチ)kazuchikafumokmmhttp://gist.github.com/619899
56数値型(Javaとの相違点など)kazuchikatoby55kijhttp://gist.github.com/619061
57GStringが遅延評価ぽくなるケースkazuchikanobeanshttp://gist.github.com/619334
58StringクラスのGDKメソッドkazuchikatoby55kijhttp://gist.github.com/619851
59正規表現(基本編)kazuchikaikikkohttp://gist.github.com/619363
60fizzbuzzkazuchikanemo_kazhttp://gist.github.com/618993
61GroovyTestCaseと各種拡張assertメソッドkazuchikafumokmmhttp://gist.github.com/622492
62Groovy Truthのカスタマイズkazuchikauehajhttp://gist.github.com/619374
63演算子オーバーロードkazuchikaikikkohttp://gist.github.com/619990
64クロージャとdelegatekazuchikatouchez_du_boishttp://gist.github.com/620045
65何でも使えるぜGroovyなswitch-casekazuchikatoby55kijhttp://gist.github.com/619088
66Expando使用例kazuchikatoby55kijhttp://gist.github.com/619889
67実行中のクラス/メソッド名を取得kazuchikanemo_kazhttp://gist.github.com/619034
68curry/rcurry/ncurrykazuchikafumokmmhttp://gist.github.com/619285
69動的なメソッド追加kazuchikanbqxhttp://gist.github.com/619342
70存在しないメソッド/プロパティアクセスをフックkazuchikatoby55kijhttp://gist.github.com/619113
71ConfigSlurperkazuchikakimukou_26http://gist.github.com/622497
72テキストファイルの中身を行番号付きで表示kazuchikatomoakioshimahttp://gist.github.com/619026
73AntBuilderkazuchikakazuchikahttp://gist.github.com/619985
74日付の解析とフォーマットkazuchikanobusuehttp://gist.github.com/619111
75外部コマンドの起動と出力の取得等kazuchikamasanobuimaihttp://gist.github.com/619231
76環境変数取得kazuchikatomoakioshimahttp://gist.github.com/619036
77CliBuilderkazuchikanobusuehttp://gist.github.com/619094
78HTTPファイルアップロードkazuchikauehajhttp://gist.github.com/620451
79HTTP GET/POSTkazuchikamasanobuimaihttp://gist.github.com/619282
80webサーバ(Jetty)kazuchikakazuchikahttp://gist.github.com/619882
81HTTPBuilderkazuchika
82Base64 エンコード/デコードkazuchikakanemuhttp://gist.github.com/619064
83webスクレイピング(nekohtml)kazuchikauehajhttp://gist.github.com/619366
84HTML生成kazuchikafumokmmhttp://gist.github.com/619371
85ブラウザで任意のURLを表示(OS非依存)kazuchikatoby55kijhttp://gist.github.com/619019
86YAMLkazuchikamasanobuimaihttp://gist.github.com/619292
87SwingBuilderと@Bindableでモデル・ビュー連携kazuchikakiy0takahttp://gist.github.com/619208
88grocessing (groovy+processing.org)kazuchikakimukou_26http://gist.github.com/619434
89ExpectJkazuchikakazuchika
90JFreeChartでグラフ生成kazuchikakimukou_26http://gist.github.com/619747
91MongoDBkazuchikakimukou_26http://gist.github.com/619971
92TimeCategorykazuchikanobusuehttp://gist.github.com/619108
93トランプのシャッフルkazuchikafumokmmhttp://gist.github.com/619250
94超シンプルRSS/Atomリーダーkazuchikakiy0takahttp://gist.github.com/619381
95クラスパスの動的な変更kazuchikaikikkohttp://gist.github.com/619968
96jlineで行編集/補完/ヒストリ付きコマンドラインkazuchikakazuchikahttp://gist.github.com/619117
97形態素解析kazuchikatomoakioshimahttp://gist.github.com/619176
98DerbyでGroovySQLkazuchikanobusuehttp://gist.github.com/619005
99JavaとしてもGroovyとしても正当だが結果が異なるコードkazuchikatoby55kijhttp://gist.github.com/618998
100邪悪なGroovyコード(IOCCCのGroovy版)kazuchika