この記号は、xmonadと一緒に使われることの多いxmobarとい言うステータスバーのセットアップの解説で、 xmonad.hsの設定例の中にだいたい出てきます。
この>>=という演算子(方向が逆向きもあります)は、 haskell以外のプログラミング言語しか使ったことのない人にとっては、 「えっ!haskellってそんなことができるんだ!!」という、 驚きの世界を教えてくれるきっかけになる記号の一つなのです。 そこで、せっかくですから、この記号をきっかけにして、他のプログラミング言語にはない、 おもしろいhaskellの世界に親しんでみましょう。
python等、あなたの親しみのあるプログラム言語で文字列を表示するコードを想像して下さい。いわゆる、hello world プログラムです。
インタラクティブ環境で実行すれば、コンソールにすぐさま hello world という文字列が表示されるでしょう。
このprintというものは、プログラム言語の世界では一般的に「関数」と呼ばれるものです。 なぜ関数と呼ばれるかといえば、「何かの入力を与えると、それに応じた答えを返す」という、数学で習うy = f(x)の様な働きをしてくれるからです。
では、実際のprint「関数」の入力と結果を見てみましょう。 上記のコードのprint関数への入力は"hello world"ということに異論はないでしょうが、それに対するprint関数の結果(答え)とは、なんでしょうか?自分なりに、想像して見て下さい。
では、その想像を検証するために次のコードを考えてみます。
print関数の戻り値をaに代入する形です。y = f(x)のように、 下のコードを使えばprint関数の「結果」がaに入るはずです。もう一度、どんな事が起こりそうか想像してみて下さい。
さて、上記のコードを実行すると、a=という代入がなかった場合のコードと同様に、コンソールには hello world と言う文字が表示されます。 そして、aの中身を確認してみると、特に何も入っていないはずです。
ここで少し混乱した人がいるかもしれません。 まず、代入式のコードを見る前にprint関数の結果を想像した時には、print関数の結果は、「画面にhello worldを表示すること」と考えた人もいたと思います。
しかし、代入式を見た後は、aに入りそうなものを想像することになったと思います。 つまり、皆の常識的にaに入りそうなものとしては、文字列、数字、若しくは、少しプログラムになれている人ならば何もないという結果だったかもしれません。
いずれにせよ、「画面にhello worldを表示すること」がaに入るという選択肢は、常識的に除外されたはずで、 若しくは、画面に表示することと、文字列の区別が曖昧だったり、そのあたりがモヤモヤした人は、無理やり、aには"hello world"という文字列が入ると考えた人もいたかと思います。
さて、pythonでの上記のコード、print関数の結果は、aに何も入っていないことが示している通り、何も返さないという結果を返しています。 では、画面にhello worldと表示された事自体は、関数とどういう関係があるのでしょうか?
この>>=という演算子(方向が逆向きもあります)は、 haskell以外のプログラミング言語しか使ったことのない人にとっては、 「えっ!haskellってそんなことができるんだ!!」という、 驚きの世界を教えてくれるきっかけになる記号の一つなのです。 そこで、せっかくですから、この記号をきっかけにして、他のプログラミング言語にはない、 おもしろいhaskellの世界に親しんでみましょう。
関数は何を返しているのか
python等、あなたの親しみのあるプログラム言語で文字列を表示するコードを想像して下さい。いわゆる、hello world プログラムです。
>>> print("hello world")
インタラクティブ環境で実行すれば、コンソールにすぐさま hello world という文字列が表示されるでしょう。
このprintというものは、プログラム言語の世界では一般的に「関数」と呼ばれるものです。 なぜ関数と呼ばれるかといえば、「何かの入力を与えると、それに応じた答えを返す」という、数学で習うy = f(x)の様な働きをしてくれるからです。
では、実際のprint「関数」の入力と結果を見てみましょう。 上記のコードのprint関数への入力は"hello world"ということに異論はないでしょうが、それに対するprint関数の結果(答え)とは、なんでしょうか?自分なりに、想像して見て下さい。
では、その想像を検証するために次のコードを考えてみます。
print関数の戻り値をaに代入する形です。y = f(x)のように、 下のコードを使えばprint関数の「結果」がaに入るはずです。もう一度、どんな事が起こりそうか想像してみて下さい。
>>> a = print("hello world")
さて、上記のコードを実行すると、a=という代入がなかった場合のコードと同様に、コンソールには hello world と言う文字が表示されます。 そして、aの中身を確認してみると、特に何も入っていないはずです。
ここで少し混乱した人がいるかもしれません。 まず、代入式のコードを見る前にprint関数の結果を想像した時には、print関数の結果は、「画面にhello worldを表示すること」と考えた人もいたと思います。
しかし、代入式を見た後は、aに入りそうなものを想像することになったと思います。 つまり、皆の常識的にaに入りそうなものとしては、文字列、数字、若しくは、少しプログラムになれている人ならば何もないという結果だったかもしれません。
いずれにせよ、「画面にhello worldを表示すること」がaに入るという選択肢は、常識的に除外されたはずで、 若しくは、画面に表示することと、文字列の区別が曖昧だったり、そのあたりがモヤモヤした人は、無理やり、aには"hello world"という文字列が入ると考えた人もいたかと思います。
さて、pythonでの上記のコード、print関数の結果は、aに何も入っていないことが示している通り、何も返さないという結果を返しています。 では、画面にhello worldと表示された事自体は、関数とどういう関係があるのでしょうか?
実は「関数の結果」とは、何の関係もありません。 この「画面にhello worldと表示された事」は、そのprint関数が実行された時に結果を返す事(関数の主たる作用)と同時に起こった「副作用」と呼ばれるものなのです。
ここで、注意してほしいのは、画面等に何かを表示するコンピューターの働きを「副作用」と呼ぶのではありません。 print関数が返す結果、即ち「主たる作用」、以外の同時に起こっている働きであるから、「副作用」と呼びます。
- 関数が返す結果 主たる作用
- 関数が返す結果以外 副作用
このように、pythonを使っている時には、print関数の結果が何であるか?ということ自体について特に考えたことはなかったと思います。 print関数は、画面に文字列を表示することが重要なのであって、それが関数の結果であろうが、副作用であろうが気にしていないからです。
haskellは素直なものを返す
では、今度は、haskellのhello worldプログラムです。Prelude> putStrLn "hello world"
haskellでは、関数の表記にカッコはありません。「haskellの関数をおしゃれに把握する」を参照してみて下さい。 このコードをghciで実行すると、hello worldという文字列が表示されます。関数名がputStrLnであるという点以外に、pythonのprint関数とコードの形には、違いはありません。
先程はここで、関数の結果は何かを想像してもらいました。 その後、代入式での検証を行って、pythonでの結果は、何もないという結果を返すとともに、 副作用として画面表示をするということを把握してもらいました。
ここで、最初に少しは想像したかもしれない、文字を表示する関数の結果は、「文字を表示すること」という結果であったほうが、 凄く自然だと思いませんか? ただ、「文字を表示すること」が関数の結果であったとした場合、 その結果の戻り値を変数aに入れたり出来なければなりませんが、そもそもそんな事がプログラム上可能なのでしょうか?
haskellは凄いです!そういう事が出来るんです!!
次のコードを見て下さい。
Prelude> a = putStrLn "hello world"
これをghci上で実行すると、画面には何も表示されません。
何が起こっているのかといえば、先に説明したとおりです。putStrLn関数の結果は、「文字を表示すること」であり、 このコードでは、その結果をaに入れたのです。つまり、このコードは「文字を表示すること」をaに入れるコードであって、 「文字を表示する」ためのコードではないため、文字を勝手に表示したりしないのです。
このように「文字を表示すること」というような、 「コンピューター上の動作」そのものを関数の結果としたものの事をhaskellでは「アクション」と呼んでいます。 そして、haskellでは、関数の結果として、数値、文字列、何らかのデータに加えて、「アクション」というものも返す事が出来るようになっているのです。
アクションの効果が生まれるタイミングの違い
さて、このアクションというものは、「文字を表示すること」というコンピューター上の動作を抽象的に表しているものであって、アクションは実行されるまで、そのアクションの効果は実現されることはありません。
つまり、pythonとhaskellでは、プログラム上で「文字を表示する」タイミングが異なるということです。
評価時に副作用として効果が発動するpython
まず、pythonのprint関数を思い出して下さい。a = print("hello world")
上記のコードが、スクリプトやソースの中にあった場合、このprint関数が評価された瞬間に、 結果を戻す(この場合は返り値が無いという結果を戻す)と同時に副作用として画面にhello worldの文字が表示されます。 つまり、「コンピューター上の動作」に関する関数は関数の評価と同時にその瞬間「コンピューター上の動作」が副作用として実現されるのです。
評価時に結果であるアクションが生まれるhaskell
一方、haskellのputStrLn関数は、評価されると関数の結果である「アクション」を返す(生まれる)だけで、「アクション」は実行されません。a = putStrLn "hello world"
ghciで上記のコードを実行した場合、画面には何も表示されなかったはずです。
このコードではまず、putStrLn関数が評価され、その結果として、「画面にhello worldを表示する」というアクションが返されます。そして、そのアクションがaに入れられます。
この様に、pythonのprint関数では、関数が評価されると同時に、画面への表示が行われますが、heskellのputStrLn関数では関数の評価を行っても、 画面への表示が行われない事が確認できます。
いつhaskellのアクションは実行されるのか
では、haskellでは、いつアクションが実行されるのでしょうか?
ghciではhaskellのアクションが実行される
まずは、ghci上で、アクションそのものを評価した場合にghciが実行してくれます。
ghci上で、a = putStrLn "hello world"という風に、aを定義した状態で、aを評価します。
Prelude> a = putStrLn "hello world"
Prelude> a
hello world
若しくは、初めに見たとおり、putStrLn自体を評価します。
Prelude> putStrLn "hello world"
hello world
これだけを見ると、putStrLnの評価によって文字列が表示されているかのように見えますが、実は2段階の事が起こっています。
- putStrLn "hello world"が評価され、画面にhello worldを表示するアクションが返される。
- ghciは関数結果を表示するために、ghciがアクションを実行する
haskellのソースコードではmainとして実行される
python等のスクリプトの場合、スクリプトコードは上から順番に評価されて実行されていきます。 一方、CやJavaではmainに代表されるエントリポイントという関数が実行時に実行されます。
haskellのプログラムは、ソースコードをコンパイルして出来る実行ファイルをシェルで実行して使うことが出来ます。 そして、haskellも実行時にmainとして定義されたものが実行されます。
ここから実際に、サンプルコードのコンパイルから、実行ファイルの実行を体験してみましょう。
次のコードをsample001.hsというファイル名で作成して保存して下さい。
ソースコードを保存できたら、シェルから次のコマンドを実行しましょう。
コンパイルが成功すると、sample001という実行ファイルが出来上がっているはずです。シェルから実行してみましょう。
hello worldと文字列が表示された思います。
さて、haskellのソースコードのコンパイルと実行を把握したところで、あらためて、もう一度、ソースコードの内容を見て下さい。
mainにputStrLn関数の結果であるアクションを入れています。
このように、haskellのアクションは、mainに入れられたものがプログラムとして実行される場合に実行されます。
次のコードをsample002.hsとして作成して下さい。
このコードは、aとbにそれぞれアクションを入れています。
そして、そのうち、aをmainに入れています
コンパイルして実行してみましょう。「aに結びつける」という表示がなされるはずです。
今までの話を整理して、このコードで起こっていることを考えてみましょう。 a=、b=で始まる行はともに、関数の結果としてアクションが作成されるだけでアクションは実行されません。 そして、mainに入っている「aに結びつける」と表示するaのアクションが、プログラム実行時に実行されます。
つまり、haskellのコードの中では、mainの中でアクションは実行されますが、 逆を言えばmain以外の部分では実行されませんので、関数の結果として生み出されたアクションをあちこちへ持ち運ぶような使い方が出来るわけです。 もし、「bに結びつける」と表示するためのプログラムにするとしたら、bに入ったアクションを持ち運ぶようにmainへ入れるとよいわけです。 実際のコードは何処を書き換えればよいかもうわかりますよね。
以上のように、haskellのアクションは、ghci上で評価されるか、mainという名前でプログラム実行時に呼び出されるかのどちらかの場合でのみ実行されます。
sample002.hsを以下のように書き換えてみて下さい。
aとbはアクションですが、mainにdo記法を利用することで、幾つでもアクションを並べて実行させることが出来ます。
ここで、注意が必要なのは、do記法の範囲です。
haskellのソースコードでは、インデント(字下げ)は見た目をわかりやすくするためだけではなくて、 構文的に意味があります。そして、この字下げを使って、do記法の範囲を設定しています。 具体的には、mainの行頭から字下げされている行がその範囲になります。ですから、 sample002.hsのアクションの定義をmainの後に書く場合は、次のようになります。
haskellで標準入力から入力を得る関数は、getLine関数です。 そしてこのgetLine関数もアクションを返す関数であり、「文字列を受け取る」というアクションを返してくれます。
さて、ここでpythonとのコード比較をすることで、関数の結果がアクションであるという事を再認識します。 pythonでは、input関数を利用することで、入力を得ることが出来ます。例えば、インタラクティブ環境では、次のようなやり取りが行われます。
つまり、pythonでは、input関数の結果は、標準入力へ入力された文字列です。
これと同じことをhaskellで行おうとして、次の式をghciで実行しても思ったとおりになりません。
ghciのプロンプトが戻ってきてしまい、入力待ちになりません。
そうです、getLine関数は、pythonのような入力された文字列を結果として返すのではなくて、 「入力を受け取る」というアクションを返すのです。ですから、この式では「入力を受け取る」という アクションがaに入っただけで、そのアクションは実行されません。
実行するためにはアクションをghci上で実行すればOKです。今、aにはそのアクションが入っているので、 ghci上で、このaを評価すると、このアクションを実行することが出来ます。
アクションが実行されて、入力待ちになり、入力を受け取ってくれました(ここではhogeと入力した)。 さて、入力結果は、ghci上に表示されましたが、これでは、自分でその入力内容をプログラムで使えません。 また、pythonの時のように、アクションに=(イコール)記号を使ってもアクションそのものが何かの変数に結び付けられるだけで、 アクションの実行結果である入力文字列が入るわけでありません。このままでは、haskellで入力を自分で好きなように使うことが出来ません。
実は、haskellのアクションはみな、箱のような入れ物を持っています。
そして、アクションが実行された時に、そのアクションで何か結果が生じたら、 その箱の中に入れるようにな仕組みになっています。初めに見た「文字列を表示する」アクションでは、 実行されてもその結果は特に無いため、箱はありますが使われておらっず空っぽです。
一方、入力を受け取るアクションでは、入力された文字列がアクションの結果として箱の中に入れられます。
そして、このアクションの実行結果である箱の中身は"<-"演算子を使うことで受け取ることが出来ます。
ではここで、入力と出力のアクションを使ったコードの例として、半沢直樹してみましょう。
いよいよ >>= 記号です。
入力で得た結果を、別の関数の引数に使うというパターンはよくあるパターンです。
半沢直樹プログラムでいえば、getLineで得た結果をokoに入れ、それを次の行で、okoを呼び出して、表示しています。 この様な場合、okoという定義を介さずに、getLineを実行して、その結果を直接、別の関数の引数に渡すことが出来るのが、 ">>="演算子なのです。
初めに、文字列を一つ受け取って、半沢言葉を画面に表示する、hanzawa_say関数を定義しています。
do記法の2行目で、getLineのアクションで入力取り込みを実行するとともに、その結果を取り出して、 hanzawa_say関数に渡しています。
もとのパターン
xmonadは、defというxmonadの設定データを引数として受け取り、 xmonadを動かすというアクションを返す関数です。 コードは、mainに入れられているので、プログラム実行時にこのアクションは実行されます。
xmobarを実行するパターン
xmobarは、defというxmonadの設定データを引数として受け取り、 xmbarを動かすというアクションを返す関数です。mainの式として入れられているので、 xmobar関数のアクションは、プログラム実行時に実行されます。 そして、実行された結果がxmobarアクションの箱にはいりますが、xmobarの実行結果は、x mobar様に変更されたxmonadの設定データなのです。
ですから、このコードでは、そのxmobar様に変更されたxmonadの設定データが"=<<"記号により取り出され、 xmonad関数の引数として渡されて評価され、xmonadのアクションが返されます。そして、これもmainにあるので、 そのアクションが実行されるのです。
"=<<"記号はいつでも、do記法を使って書き換えることが出来ます。
次のコードをsample001.hsというファイル名で作成して保存して下さい。
main = putStrLn "hello world"
ソースコードを保存できたら、シェルから次のコマンドを実行しましょう。
$ ghc -dynamic sample001.hs
コンパイルが成功すると、sample001という実行ファイルが出来上がっているはずです。シェルから実行してみましょう。
$ ./sample001
hello worldと文字列が表示された思います。
さて、haskellのソースコードのコンパイルと実行を把握したところで、あらためて、もう一度、ソースコードの内容を見て下さい。
mainにputStrLn関数の結果であるアクションを入れています。
このように、haskellのアクションは、mainに入れられたものがプログラムとして実行される場合に実行されます。
次のコードをsample002.hsとして作成して下さい。
a = putStrLn "aに結びつける"
b = putStrLn "bに結びつける"
main = a
このコードは、aとbにそれぞれアクションを入れています。
そして、そのうち、aをmainに入れています
コンパイルして実行してみましょう。「aに結びつける」という表示がなされるはずです。
今までの話を整理して、このコードで起こっていることを考えてみましょう。 a=、b=で始まる行はともに、関数の結果としてアクションが作成されるだけでアクションは実行されません。 そして、mainに入っている「aに結びつける」と表示するaのアクションが、プログラム実行時に実行されます。
つまり、haskellのコードの中では、mainの中でアクションは実行されますが、 逆を言えばmain以外の部分では実行されませんので、関数の結果として生み出されたアクションをあちこちへ持ち運ぶような使い方が出来るわけです。 もし、「bに結びつける」と表示するためのプログラムにするとしたら、bに入ったアクションを持ち運ぶようにmainへ入れるとよいわけです。 実際のコードは何処を書き換えればよいかもうわかりますよね。
以上のように、haskellのアクションは、ghci上で評価されるか、mainという名前でプログラム実行時に呼び出されるかのどちらかの場合でのみ実行されます。
doでアクションをいっぱい実行する。
ここで、mainに=(イコール)記号でアクションを入れるとすれば、そのプログラムでは、一つしかアクションを実行できないことになってしまいます。 ですから、haskellでは、幾つかの方法で、複数のアクションを実行できるようになっていますが、ここではまず、do記法を紹介します。sample002.hsを以下のように書き換えてみて下さい。
a = putStrLn "aに結びつける"
b = putStrLn "bに結びつける"
main = do
a
b
aとbはアクションですが、mainにdo記法を利用することで、幾つでもアクションを並べて実行させることが出来ます。
ここで、注意が必要なのは、do記法の範囲です。
haskellのソースコードでは、インデント(字下げ)は見た目をわかりやすくするためだけではなくて、 構文的に意味があります。そして、この字下げを使って、do記法の範囲を設定しています。 具体的には、mainの行頭から字下げされている行がその範囲になります。ですから、 sample002.hsのアクションの定義をmainの後に書く場合は、次のようになります。
main = do
a
b
a = putStrLn "aに結びつける"
b = putStrLn "bに結びつける"
入力を受け取る関数
haskellで標準入力から入力を得る関数は、getLine関数です。 そしてこのgetLine関数もアクションを返す関数であり、「文字列を受け取る」というアクションを返してくれます。
さて、ここでpythonとのコード比較をすることで、関数の結果がアクションであるという事を再認識します。 pythonでは、input関数を利用することで、入力を得ることが出来ます。例えば、インタラクティブ環境では、次のようなやり取りが行われます。
>>> a = input("please input: ")
please input: hogehoge
>>> a
'hogehoge'
つまり、pythonでは、input関数の結果は、標準入力へ入力された文字列です。
これと同じことをhaskellで行おうとして、次の式をghciで実行しても思ったとおりになりません。
Prelude> a = getLine
ghciのプロンプトが戻ってきてしまい、入力待ちになりません。
そうです、getLine関数は、pythonのような入力された文字列を結果として返すのではなくて、 「入力を受け取る」というアクションを返すのです。ですから、この式では「入力を受け取る」という アクションがaに入っただけで、そのアクションは実行されません。
実行するためにはアクションをghci上で実行すればOKです。今、aにはそのアクションが入っているので、 ghci上で、このaを評価すると、このアクションを実行することが出来ます。
Prelude> a
hoge
"hoge"
アクションが実行されて、入力待ちになり、入力を受け取ってくれました(ここではhogeと入力した)。 さて、入力結果は、ghci上に表示されましたが、これでは、自分でその入力内容をプログラムで使えません。 また、pythonの時のように、アクションに=(イコール)記号を使ってもアクションそのものが何かの変数に結び付けられるだけで、 アクションの実行結果である入力文字列が入るわけでありません。このままでは、haskellで入力を自分で好きなように使うことが出来ません。
アクションの箱から結果を取り出す演算子
実は、haskellのアクションはみな、箱のような入れ物を持っています。
そして、アクションが実行された時に、そのアクションで何か結果が生じたら、 その箱の中に入れるようにな仕組みになっています。初めに見た「文字列を表示する」アクションでは、 実行されてもその結果は特に無いため、箱はありますが使われておらっず空っぽです。
一方、入力を受け取るアクションでは、入力された文字列がアクションの結果として箱の中に入れられます。
そして、このアクションの実行結果である箱の中身は"<-"演算子を使うことで受け取ることが出来ます。
ではここで、入力と出力のアクションを使ったコードの例として、半沢直樹してみましょう。
main = do
putStrLn "どれくらい起こっていますか?"
oko <- getLine
putStrLn ("やられたらやり返す" ++ oko ++ "倍返しだ!!!")
アクションの結果を取り出して別の関数に引数として渡す
いよいよ >>= 記号です。
入力で得た結果を、別の関数の引数に使うというパターンはよくあるパターンです。
半沢直樹プログラムでいえば、getLineで得た結果をokoに入れ、それを次の行で、okoを呼び出して、表示しています。 この様な場合、okoという定義を介さずに、getLineを実行して、その結果を直接、別の関数の引数に渡すことが出来るのが、 ">>="演算子なのです。
hanzawa_say oko = putStrLn ("やられたらやり返す" ++ oko ++ "倍返しだ!!!")
main = do
putStrLn "どれくらい起こっていますか?"
getLine >>= hanzawa_say
初めに、文字列を一つ受け取って、半沢言葉を画面に表示する、hanzawa_say関数を定義しています。
do記法の2行目で、getLineのアクションで入力取り込みを実行するとともに、その結果を取り出して、 hanzawa_say関数に渡しています。
xmonad.hsでよく見るパターン
もとのパターン
main = xmonad def
xmonadは、defというxmonadの設定データを引数として受け取り、 xmonadを動かすというアクションを返す関数です。 コードは、mainに入れられているので、プログラム実行時にこのアクションは実行されます。
xmobarを実行するパターン
main = xmonad =<< xmobar def
xmobarは、defというxmonadの設定データを引数として受け取り、 xmbarを動かすというアクションを返す関数です。mainの式として入れられているので、 xmobar関数のアクションは、プログラム実行時に実行されます。 そして、実行された結果がxmobarアクションの箱にはいりますが、xmobarの実行結果は、x mobar様に変更されたxmonadの設定データなのです。
ですから、このコードでは、そのxmobar様に変更されたxmonadの設定データが"=<<"記号により取り出され、 xmonad関数の引数として渡されて評価され、xmonadのアクションが返されます。そして、これもmainにあるので、 そのアクションが実行されるのです。
"=<<"記号はいつでも、do記法を使って書き換えることが出来ます。
main = do
mb_def <- xmobar def
xmonad mb_def
0 件のコメント:
コメントを投稿