2013年11月12日火曜日

xmonadとHaskell(その9:演算子とか結合規則とか)

xmonadのある風景


デスクトップPCの広い画面だと、タイリングの便利さが実感できる。


さて、(その8)で関数の部分適用の話をして、dzenPPの項目の一つ

 ppCurrent = dzenColor "#00ffaa" "" . wrap "[" "]"

の部分のdzenColorやwrap関数の部分が部分適用された新しい関数ということを把握した。

ちなみにdzenColor関数の引数は、一つ目がフォアグラウンドの色、二つ目がバックグラウンドの色、3つ目が中身の文字列となっている。

dzen上での表示は通常テキストで行うが、一定のコマンド文字列を含ませることで、テキストに色を付けたりできる。このdzenColor関数は、そのコマンド文字列を簡単に生成してくれる。

READMEの「(5) In-text formating & control language:」辺りを参照

関数の合成

前回話題に上らなかった関数と関数の間にある「.」(ピリオド)は、関数を合成する演算子。

  g・f(x) = g(f(x))

数学的に書くと上みたいな感じらしい。

あまり難しく考えなくても、単純に、ある関数を適用して、その答えに別の関数を適用して、その答えにまたまた別の関数を適用して、、、という処理をする時、それらの各関数を「.」ピリオド演算子で繋げると合成された関数が返される。

ここで整理しておくべきことは、合成関数が、「関数の合成の話」であるということ。
これは、ピリオド演算子の両側にくるものは「関数」であり、戻り値も「関数」であると常に意識しておくことだ。そうすると、一見ややこしく見える式に出会った時のヒントになるかもしれない。


以下には、この整理の手助けになるかもしれないし、ならないかもしれない例を示してみる。

おなじみのppCurrentの式


 ppCurrent = dzenColor "#00ffaa" "" . wrap "[" "]"


これを簡略化し引数が単純なものを以下に示す。

func1 ::  String -> String
func2 ::  String -> String
resfunc ::  String -> String

resfunc = func1 . func2

これは、func1関数とfunc2関数を使って、resfuncという関数を定義している。


では次に、値を計算する時に、合成関数の書式を使うとどうなるか?

arg = "hogehoge"
res = func1 . func2 arg

と書きたくなる?


でも正解は下のような感じ。

res = (func1 . func2) arg

(func1 . func2) という関数にarg引数を渡すという風に表現する。


ピリオド演算子は、関数を合成する話、すなわち、合成した関数を作る話だったりする。
単に、関数を適用して求めた値に、更に関数を適用して値を求めるならば、

res = func1 (func2 arg)

となるのであり、これと合成関数の話は頭の中で整理して、区別する必要がある。


さて、合成関数の話はここまでにするとして、

では、何故、

 res = func1 . func2 arg

は、うまくいかないか??

「7+7÷7+7×7-7」の答えが分かる人は頭がいいらしい

っていうのが、ツイッターで話題になってたけれど、プログラムやってる人は、これが頭の良し悪し(何が正しくて何が間違っているか)とは無関係なことを知っている。

そう単に、優先順位の約束事がどう決められているかという問題だ。


(その8)で少し触れたけれど、haskellでは演算子よりも関数と引数が強く結びつく。(記号よりも空白の結びつきが強い)

なので、

res = func1 . func2 arg

は、

res = func1 . (func2 arg)

という風に扱われる。

そうすると、ピリオド演算子の後ろ側の型は関数でなく上の例では文字列になるので上手くいかなくなったのだった。

強い結合と弱い結合

巷で見られる結合についての解説では、結合が強いとか弱いという言い回しがよく見られる。
「強い」というのは、上の表現で括弧で括ったように func2  と arg がひとまとまりになっていると考えるので直観的で分かりやすい。

一方、「弱い」結合というのは、そこで「区切られる」という風にイメージする感覚に近い。

 ppCurrent = dzenColor "#00ffaa" "" . wrap "[" "]"

の = と . に色を付けてあるが、まず記号部分に注目して、そこで区切られるというイメージ。

こうイメージすると、その裏返しで、それ以外の部分がひと塊になっている事(強い結合)がイメージしやすい。


右結合と左結合

結合の話の中で「右結合」と「左結合」という単語も出てきたりする。

a * b + c * d

という式があれば、*演算子の各掛け算を先にして、その後で、それを足すという順番を考えたりする。
しかし、全部同じ順位の演算子、例えば、

a + b + c + d

という式があった時、各項目についてどのように着目しているだろうか?
なんとなく自然に、

まず、aとbを足して、
次に、その答えにcを足して、
更に、その答えにdを足すという感じだと思う。

括弧を付けて表現するなら

(((a + b) + c) + d)

こんな感じに着目していくことになっていたりする。
こういう演算子を「左結合」という。

演算子の左側をまず先に計算するイメージだ。
深く考えなくても、普段無意識に注目している通り、左から右へというイメージが「左結合」だ。
但し、この話の意識すべきは「演算子」の話であること。そして、順位が同列の演算子が複数ある時の話であることだ。


では、「右結合」ってどんなのだろう?
実は上で見た、合成関数を作る「.」(ピリオド)演算子が右結合の演算子だ。

a . b . c . d

という合成関数がある時、どういう順序で着目されているかというと

d関数にc関数を合成し、
その合成された関数にbを合成し、
その合成された関数にaを合成する。

となっている。
補助的な括弧を付けると

(a . (b . (c . d)))

こんな感じだ。
ちょうど上で見た足し算の時と括弧の付き方が逆である。
つまり、「右結合」は演算子の右側をまず先に計算する。複数の演算子があれば、より右側の演算子を先に結び付けて計算するイメージだ。


さて、この「右結合」、実は既に(その8)でも見ていた。

wrap関数の型シグニチャは

wrap :: String -> String -> String -> String

という風に表されるが、実は一つの引数をとって、関数を返すカリー化の性質を表現しているという話をして、補助括弧を以下のようにつけた。

wrap :: (String -> (String -> (String -> String)))

これは、まさに右結合。
関数の型シグニチャの意味がhaskellを初めて見た時に分かりにくいのは、右結合の演算子で表現されているせいもあると思う。

しかし、右結合とか左結合の話をなんとなくでも把握すると、頭の中で瞬時に上の補助括弧が付けられるとともに、部分適用時に戻り値が関数となる様が見て取れるようになるかもしれない。

そもそも、「右結合」っていうルールを作ってあるのは、自然な左結合だけの世界だと、上で見たwrap関数の表現のように括弧をいくつもつけなければならない。

しかし、関数の表現は全部このパターンなのに、いちいち括弧を書くのが煩わしいので、「->」演算子は右結合というルールを作って括弧を外したらしい。合成関数の演算子も同様だ。


セクションとか、中置関数とか

さて、ちょっと演算子の話に戻って、いくつかの豆知識。

「+」等の演算子は、

1+2

とか日常で見慣れた並び方で使われる。

しかし、演算子も実質的には、二つの引数をとる関数である。
haskellでは、演算子を括弧で包むと関数的な表現ができる。

1 + 2

は、

(+) 1 2

と書くことができる。
また、演算子の型シグニチャをghciで見る時には、この括弧付きの形で表現しないといけない。

 :type (+)

括弧を付けづに

 :type +

としたら、エラーになる。

さて、(+)もhaskellの関数であり、カリー化されているので部分適用ができる。
しかし、演算子を括弧で包んだ関数の部分適用は、ちょっと変わっている。

普通の関数のパターンなら

(+) 1

とすることで、部分適用する。
しかし、演算子を関数化している場合には、引数を括弧の中に書いてしまえる。

(1+)



(+1)

こういう表現は特にセクションと呼ばれている。


一方逆に、普通の関数を演算子的に使うこともできたりする。これは中置関数と呼ばれる。
引数を二つとる関数について、「+」等の演算子のように引数と引数の間に関数名が並ぶような感じだ。

その方法は関数名を「`」バッククオートで囲ってやること。

add :: Int -> Int -> Int

 というような二つの引数をとって和を返す関数があった時

add 1 2

は、

1 `add` 2

と書くことができる。


巷のxmonad.hsでdefaultConfig{}の後ろに

`additionalKeysP`

なんて言うのがよく見られるが、これがまさにそうだ。

普通の関数で書けば

additionalKeysP defaultConfg {} hogehoge

という形になる。


中置関数は、視覚的な理解しやすさとともに、演算子化することで、結合の強さが変化し、括弧が不要になるという効果もある。


例えば、上のaddの場合、引数に別の関数hoge、fugaがある場合

add (hoge 1 2 3 )  (fuga 1 3 4)

という表現になるが、中置にすれば

hoe 1 2 3 `add` fuga 1 3 4

となって括弧が外れる。

まあ、日曜プログラマ的に言えば、やっぱり括弧は、わかりにくくならない程度に明示したほうが良さげなんだけれど。


2013年10月29日火曜日

xmonadとHaskell(その8:関数の面白性質)

前回書いた、謎のカリーおじさんのイラストの話。
謎でもなんでもなくて、ハスケル・ブルック・カリーさんらしい。。。。

さて、本題のHaskellの関数の話。

括弧がなさ過ぎてよくわからない?

C言語なんかだと、関数の引数は括弧で囲まれている。
 
 hogefunction (arg1,arg2)

みたいな感じ。

しかし、Haskellにはそういう括弧はないので、初めて接する時にどれが引数なのかわからなかったり、そもそも関数がどれかもわからなかったりするかもしれない。上のコードはHaskell的には

 hogefunction arg1 arg2

みたいな感じになるが、この名前を単純にして

 a b c

と書かれると、なんだかわからなくなるかもしれない。


名前が単純でもC言語的に書けば

 a(b,c)

で、なんとなくわかる。

このわからなさを解決するアバウトな目線としては、単語が並んでいたら先頭が関数、それ以外は引数って感じで把握するのがよさげ。
だいたいでいえば、関数と引数の結びつきは、演算子よりも強い。
なので、何かの記号の間にある単語は関数とその引数と考えても大体間違いない。

あとは、関数を小まめに調べるという作業をしていると、なんとなく慣れてしまう。
そして、この「関数を小まめに調べる」という作業は実に重要。

xmonad.hsに限って言えば、xmonadのモジュールのリファレンスのページをみたりすること!

Haskellの関数と引数

なぜ、関数を小まめに調べることが必要なのか?
それは、Haskellの関数は、決まった引数の数をとる必要がないからだ。

例えば、C言語で引数を3個とるhogeという関数があれば

 hoge(arg1, arg2, arg3)

という風に使われる。
そして、この関数を勝手に

 hoge(arg1arg2)

と使うことはできない。

ところが、Haskellだと、

 hoge arg1 arg2 arg3

という関数は

 hoge arg1 arg2

と使ったり、

 hoge arg1

と使ったりできる。

なので、xmonad.hsとかで他人のコードを見た時、その書かれている引数がその関数で定義されている引数のすべてとは限らない。
だから、関数を調べることが重要になってくる。

部分適用

では、もともと3つの引数がとれる関数を3つより少ない引数で評価するとどうなるか?
結論から言えば、省いた引数を引数として、もとの目的の値を返すための関数を返す

例えば、前回見たwrap関数(XMonad.Hooks.DynamicLogモジュールのリファレンス)

  wrap :: String -> String -> String -> String

この関数は、3つの引数、左側区切り文字列、右側切り文字列、中身になる文字列をとり、加工された文字列を返す。具体的には、

  wrap "[" "]" "home"

と使えば"[home]"という文字列を返す

そして次に、この引数を一つ省いて

  wrap "[" "]"

として評価すると、中身になる文字列を(省いた引数)引数としてとって、加工された文字列(もとの目的の値)を返すという関数を返す

  kakko_wrap = wrap "[" "]"

と定義し、このkakko_wrap関数の引数に中身になる文字列を渡してあげれば

  kakko_wrap "home"

"[home]"という文字列が返る。


もちろん二つ省けば、引数が二つ必要な関数が返る。

 maedakekakko_wrap = wrap "["

 maedakekakko_wrap "]" "home"

とすれば"[home]"になる。




こんな感じで、ある関数の引数の一部分だけを決定して使うことを部分適用という。

前回defaultPPの中で定義されていた一行をもう一度見てみる。


 ppCurrent = dzenColor "#00ffaa" "" . wrap "[" "]"

赤字の部分を見ると、まさに上で見たwrap関数の部分適用!
当然予想できると思うが、dzenColorもリファレンスで調べてみよう。型シグニチャは、、

  dzenColor :: String -> String -> String -> String

やっぱり、dzenColorは引数を三つとって、文字列を返す関数だけど、ここでは、二つの引数のみ渡されているので、

 ppCurrent = dzenColor "#00ffaa" "" . wrap "[" "]"

この赤字の部分は、dzenColor関数が部分適用され、ひとつの文字列を受け取って、なんかの文字列を返す関数となっている。

カリー化

あの肖像画の挿絵の人がカリーさんで、その人がハスケルさんだったとはびっくり。
そもそも、ハスケルさんが言い出しっぺということ自体しらんかった。

と、それくらいにカリー化について知らなかった。
なので、巷では、「カリー化と部分適用が混同されてうんたらかんたら」という難しそうな話があるが、ここではそういう話はない。

「カリー化」って名前からして、自分で何かをカリー化するのかな?と思っていたが、そういうことではなかったってこと。

そして、型シグニチャの書き方は、そういう意味だったんだ!!ってのにちょっと感動できること。

初めに話した括弧だけど、慣れると括弧いらん、、、というより、無いほうが自然だったってこと。

についての、まさにチラ裏話。


そもそも「カリー化」という言葉は、「関数をカリー化する」というのではなくて、「Haskellでは、関数がカリー化されている」とかいう風に使うほうがわかりやすいっぽい。(Haskellでは、自分で何かをしなくても関数がもともとそうなってるから)

そして、「カリー化」とは、どういうことなのかというと、wikipediaのカリー化のページによれば

 複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引 数を取り結果を返す関数」であるような関数にすること

となっている。言葉で言えば、ややこしいけれど、さっきまで見ていた、wrap関数の部分適用を思い出してほしい。

複数の引数をとる関数として、wrap関数で具体的に見てみる。

wrap関数についての「もとの関数の最初の引数」は、左側の区切り文字列である。
そして、その最初の引数だけを与えて評価すると、「もとの関数の残りの引数を取り結果を返す関数」、すなわち、右側区切り文字と、中身の文字列を引数にして、出力文字列を返す関数、上の例のmaedakekakko_wrap関数を返す。

つまり、上のカリー化の条件に合致していて、wrap関数は既にカリー化された状態にある。というより、Haskellでの関数は勝手にカリー化されているのだ。

なので、「関数を頑張ってカリー化するぞ!!」ということは考えなくてもよいし、カリー化が何かを知らなくても大丈夫なのだ。

でも、知っていると面白いこともある。
それが、型シグニチャだ。

関数書式を表すのに一般的に必要なものは、関数名、引数、そしてその引数の型がわかればよいので、なんとなくそれらが並んでいればよいような気がするのだけれど、Haskellではなんかやたらと矢印が並んでいる。

  wrap :: String -> String -> String -> String

変な書き方だなぁ、、例えば、

 関数名:wrap
 引数:String String String
 戻り値:String

とかのほうがずっとわかりやすいのにとか思ってた。

しかし、それは大間違いだった!!

まずは、引数が一つの関数を考えてみる。

 hogefunc :: String -> String


文字列の引数をとって、文字列の値を返す関数。
そして、矢印は関数を表している。そう、矢印は関数なんです!!
これを肝に銘じて、引数が二つの関数を考えてみる。

 hoge2func :: String -> String -> String

いまのところ、まだ、初めの二つのStringは引数で、最後のStringが戻り値で、間の矢印は単なる飾りにみえてますよね。

そこで次に、目が覚めるようにシグニチャに括弧をつけてみます。

 hoge2func :: String -> (String -> String)

なんかちょっと見えてきませんか??

hoge2funcは、一つ目の引数を与えると、 (String -> String)というものを返す関数であるって風に見えますよね??
で、(String -> String)っていうのは、もとの関数の二つ目の引数をとって戻り値を戻す関数です。つまり、文字列の引数を一つ受け取って、ある関数を戻り値にする関数を表している。

そして、これってさっきまで話してたカリー化の話!!
Haskellの関数はカリー化されているので、複数の引数をとる関数に、元の関数の最初の引数だけを与えると、元の関数の残りの引数をとり結果を返す関数を返してくれるってことが表現されている。

つまり、矢印は関数を表していると同時に、その矢印の連続は、カリー化された関数的な表現だったりする!


さて、調子に乗って、引数を三つにしてみる。
そう、wrap関数だ。

  wrap :: String -> String -> String -> String


さっきまでと、少し違って見えませんか?

  wrap :: String -> (String -> (String -> String))


こんな感じにみえてきませんか??

なんだか狐につままれた感じかもしれないけど、おもしろいですよね!!
もう、型シグニチャの矢印が、引数の間に挟まってる単なる飾りには見えなくなったでしょ??


さて、最後に括弧の話。
一番初めに、かっこが無いのは慣れないとコードが読みにくいという話をした。

括弧自体の話は、括弧がありすぎるLispも訳が分からないし、慣れればどうってことないはず。

というより、慣れると、カッコがなくても結合規則があれば、見通せるっていうのが、これまた気持ちいい気がする。


そして、またwrap関数にもどるけれども、wrap関数はもともとある文字を、区切り文字で挟むための関数である。で、wrap関数自体は、区切り文字をカスタマイズできるんだけれども、区切り文字が決まってしまっていて、単に、その区切り文字で挟んだ文字列を返す関数を表す関数が、部分適用で表現できるっていうのが、、、なんか、凄く表現が自然に見える。


 wrap "[" "]"  ← この表現で関数を表してる。とってもCool!!




2013年10月16日水曜日

xmonadとHaskell(その7:ワークスペース名あれこれ)

ステータスバーをカスタマイズするためのPP型の話の続き
リファレンスはココ
XMonad.Hooks.DynamicLog

dzenPPの基礎となる、PP型のコンストラクタをざっとみる。
PP型は13個の項目から成り立つ。

初めの4つはワークスペースの表示の仕方について。

ppCurrent
 現在のフォーカスがあるワークスペース

ppVisible
 見えてるけれどフォーカスが無いワークスペース
 (複数モニタを使っている場合のみ)

ppHidden
 見えてないワークスペース(中にウインドウがある)

ppHiddenNoWindow
 見えてないワークスペース(中にウインドウがない)

ワークスペース

タイリングウインドマネージャでは定番のワークスペース。

タイリングウインドマネージャは、そもそも、画面の中に複数のウインドウを重ならないように敷き詰めて、配置作業の効率化を目指してたりすると思う。

一方、ワークスペース機能は、複数のデスクトップを切り替えられる機能で、タイリングウインドマネージャとは関係なく、昔から色んなデスクトップ環境で用意されていたと思う。

S101のような小さなノートPCでは、一つのウインドウを画面内で最大化して使う事が多く、タイル配置はあまりしない。そのかわり、ワークスペース毎に一つのウインドを配置し、複数のワークスペースを移動して、画面を切り替えるっていうのが、とても使いやすい。

ブラウザで色んなページをタブで開いて、タブを行き来するのと同じ感覚。
これが大いに気に入っている。

そして、今は、S101だけでなく、デスクトップでもタイリングウインドマネージャを使うようになり、本来のタイリングの恩恵を受け、更に、マルチモニタでも「おまえらxmonadでマルチモニタって捗りすぎ」と、「捗る」って言いたいだけなのがバレバレなくらいお気に入りになっていたりする。


さて、ワークスペースの定義は、xmonadにおいては、xconfig l型の中で

workspaces :: ![String]

として定義する項目がある。
defaultConfigでは、1から9までの数字を名前として定義してある。

 workspaces = ["1","2","3","4","5","6","7","8","9"]

みたいな感じ。

もちろん、好きな文字列を使うことも出来るし、ワークスペースの数も好きなだけ作ることが出来る。

ワークスペース名

以前使っていたawesomeは、始めて出会ったタイリングウインドマネージャだった。
スクリーンショットでみるステータスバーには何を意味するのか分からない数字の1〜9が並んでいたが、それがなんだかとてもかっこよく思えた。

そして、それがワークスペースを表すものだと知り、更には、色々なカスタマイズを見ていると、数字以外の「1:web」とか「2:chat」などの文字列が表示されているのをみて、またまた、「こんなことも出来たのか、すげーカッコいい!!」と思って、自分でもやってみたりした。

しかし、間もなく1文字で表されていた数字の偉大さを知ることとなる。

まず、ワークスペース名に文字列を使うと、その表示だけでステータスバーの大部分を占めることになり、見た目が凄くうるさいくなること。

次に、せっかく、「web」とか「chat」とかつけても、わざわざウインドウを開く(特に自動の割り振りとかをしないと)のに、面倒くさくてそのワークスペース名称通りに使わないこと。

なので、awesomeの時に結局落ちついたワークスペース名は、1から5の数字だった。



上のawesomeのスクリーンショットを見ると、多分、今のワークスペースは「2」であることがわかる。そして、さらによく見てみると、ワークスペース名である数字のすぐ左上に小さな三角がついている(5のみ付いていない)。この三角はそのワークスペース内にウインドがあることを示していたりする。

awesomeの場合、この表示のパターンが概ね決まっていた様に思う。
要するに定義済みのワークスペース名が全て表示され、カレントワークスペースは表示色が変わり、ウインドのあるワークスペースには何か好きな印を付けるという感じ。

と、ここまでは、awesomeを使ってた頃の話。

ワークスペース名の表示の仕方

先に書いたワークスペース名に文字列を付けるとステータスバーがゴチャゴチャする原因は、定義されているものが全て表示されたからだ。

なので、文字列を使ったワークスペース名であっても、使っているワークスペース、すなわちウインドが存在するワークスペース名だけを表示させれば、割とすっきりとする。

初めに書いたppCurrent等の設定で、これを簡単に実現できる。

PP型コンストラクタでみる、ppCurrentの定義は次の通り。

ppCurrent :: WorkspaceId -> String

すなわち、WorkspaceId型の引数をとって、文字列を返す関数をppCurrentに定義すればいい。

WorkspaceId型というのは、文字列型のエイリアス。
haskellでは、エイリアスのことを「型シノニム」と言う。
何にせよ、単なる文字列というよりも、ワークスペース名を表す文字列ですよといった方がわかりやすいかもっていう、お馴染みのアレだ。


さて、では、具体的にどんな関数を定義すればそれっぽいことをしてくれるか、例をみてみる。

XMonad.Hooks.DynamicLogモジュールでPP型のデフォルトとして定義されているdefaultPPを覗き見る。

何を覗くかというと、前回紹介したソースである。
リファレンスのdefaultPPの右にあるソースリンクをクリックしてみる。
引用すると以下の通り。

ppCurrentに注目してみると

ppCurrent = wrap "[" "]"

となっている。

wrapはサランラップかクレラップのラップだろうから、ワークスペース名を"["とか"]"でラップするっぽい。

実際、wrapはこのモジュール内で定義されている関数で、リファレンスのFormatting Utilitiesの項目で見つけることが出来る。

型シグニチャを見ると、3つの文字列引数を受け取って、文字列を返す。
一つ目の引数の文字列を左側に、二つ目の引数の文字列を右側につけることで、三つ目の
引数の文字列を真ん中にサンドイッチした文字列を返す。defaultPPのppCurrentは、ワークスペース名文字列について左を"["で、右を"]"ではさんで表示するための定義だとわかる。

そして、ppVisibleは同じような感じで文字列で挟み、ppHidden、すなわち、画面表示されていないワークスペースはそのままの文字列を表示、但し、ppHiddenNowWindowsの定義でウインドウの無いワークスペースはワークスペース名を表示しないということが、id関数やconst関数の意味を調べてみると分かるかもしれない。

なので、上で書いていたワークスペース名のスマートな表示については、これを参考にすれば良いことになる。

表示のカスタマイズ??

さて、ワークスペース名の表示の仕方のカスタマイズだけの話なら、両側で挟む"["や"]"の部分の文字列を別の文字に書き換えることで、なんとなくカスタマイズが出来る。

しかも、具体的には、defaultConfigと同じパターンで、レコード構文を用いて

myPP = defaultPP{ ppCurrent = wrap "[[[" "]]]"}

何て感じでいけそうかな?と予想できる。
もっと、欲張ってdefaultPPのソースの並びを見るとdzenPPなんかも当然見れてそのppCurrentは、

ppCurrent = dzenColor "white" "#2b4f98" . pad

となっており、ちょこっと複雑化しているけれど、なんとなく、whiteの部分や#2b4f98を書き換えると色が変えれるのかな?と試してみたくなる。



何度も表示しているうちのステータスバーの定義は以下の通り。

ppCurrent = dzenColor "#00ffaa" "" . wrap "[" "]"

実際にカスタマイズってのは、こんな感じで適当に出来るんだけれども、、、、

さっき話した、wrap関数の三つ目の引数は何処に行ったの?とか、そもそも、ppCurrentで関数を定義するってどういうこと??とかが気になるようになると、見よう見真似で色を変えたり、左右のデミリタ文字を変えたりするのではなくて、

ppCurrent = dzenColor "#00ffaa" "" . wrap "[" "]"

が何を意味しているのか分かって、自分でその式を書くことが出来るようになるきっかけの1行だったりする気がする。

カリー化とか部分適用とかいう例のアレだ。

ちなみに、私の持ってる「すごいHaskellたのしく学ぼう!」っていう本には変な挿絵のイラストがいっぱいあるが、私にとって二つ印象的なイラストがある。

その中ひとつがカリー化関数という項目のページに書いてあるおじさんの肖像画で下になんとかカリー(筆記体で読めない)と名前があるので、多分カリーおじさんなんだろうなぁ、、カリー化のカリーは人の名前でその人がこの人なのかなぁ?と気になる。

もう一つ印象的なのは、I/Oアクションの解説で出てくる「足がたくさん生えている箱」の絵。本文の内容でも、そういう話なんだけれど、、、そこは「ネコバスのほうがいいなぁ、、」と思う。

2013年9月13日金曜日

xmonadとHaskell(その6:ステータスバー)

その5では、dzen関数を使って取りあえずのステータスバーを付けてみたが、「これ、思ってたのと違う」と感じてるかもしれない。確かに、そこいらへんで見かけるスクリーンショットに写っているステータスバーはもう少しカッコいい(他のWMとの比較は置いといて)

そこで、dzenを使ったステータスバーをカスタマイズするための下準備だ。

xmonadとステータスバーの関係


xmonadには、xmonadの状態が変化した時、例えば、レイアウトが変わった、ワークスペースが変化した、フォーカスされているアプリケーションが変わった時などに、呼び出される関数がある。

何かのきっかけが起こると呼び出される関数は、俗に「フック」と呼ばれるが、これもそのひつとであり、xmonadでは、上のような状態変化が起こるとlogHookと呼ばれる関数が呼び出される。


このlogHookは、XConfig l型データの logHookの項目にあり、リファレンスで確認するとXConfigコンストラクタの中で logHook :: !(X ()) と定義され、「windows setが変更された時に実行されるアクション」とコメントされている。(ここで、X ()はIOアクションと同様で、何かを実行する副作用と、それから生成される結果の2つからなるXアクションと把握してOK)

xmonadでは、このフックを利用してステータスバーを作るのが一般的である。
そして、その原始的な仕組みは、

 1.情報を表示するためのアプリケーションを起動しておく。
 2.フックに現在の状況を表す文字列を表示する関数を設定する。
 3.フックが呼び出されるたびに、アプリケーション上に情報が表示される。

この原始的な方法を実践してみるには以下の通り。

XMonad.Hooks.DynamicLogモジュールで定義されたもっとも基本的な表示を行うdynamicLogでlogHookを定義してみる。dynamicLogは情報の書き出し先が標準出力となっているので、これをdzenにパイプで繋ぐため、.xsessionや.xinitrcなどの、xmonadの起動のコマンドラインを

exec xmonad | dzen2

としてみる。

特に、logHookが情報を書き出す様子だけを試すには

exec xmonad >> xmonad.log

とかして、xmonadを起動した後、ターミナルから

tail -f xmonad.log

とかすると、ステータスバーに表示されるような文字列が単にlogに記録されていくのを見ることもできる。

外部アプリとしてのdzen

dzenというアプリケーションは、そもそも、入力された情報を表示するだけのアプリケーションであり、ステータスバーに特化したアプリではない。

YouTubeでのDzen2の紹介
http://www.youtube.com/watch?v=pkKFucEXIlA

X上でのちょっとした情報を表示するためのユティリティであり、ステータスバーのような帯状の表示だけでなく、実際にはウインドウ状の表示エリアを持っているので工夫次第でいろいろな表示の仕方が可能。

一方で、基本的にはテキストしか表示できないので、イメージや動的なグラフ等の表示は難しい。

そこで、dzenを使ったステータスバーというのは、xmonadからの情報を単なる一行の文字列として受け取り、それをこの1行幅のdzenで単純に表示している。なので、そこら辺で見かけるxmonadのスクリーンショットのステータスバーは派手さのない簡素な感じのものになっているのだと思う。

実際には、表示するためのXアプリと、それにデータを送るlogHookの連携でどんなステータスバーだって作ることはできる。(xmobarをはじめ、xmonad-log-appletなんていうのもある)

xmonadの内部情報とそれ以外の情報



このスクリーンショットで見るステータスバーは、実は二つの部分から出来ていたりする。

[home] : tall : s_term1

となっている左側の部分と、それ以外の右側の部分は、別々に起動されたdzen上で表示されている。


なぜ、そうなっているかというと、左側の部分がxmonadの状態をlogHookを使って表示している部分であり、右側の部分はconky(cpuやメモリの状態等を色々と調べるアプリ)を使って収集した情報を表示している部分となっているため。


つまり、理屈的には

xmonad | dzen
conky | dzen

のように起動されて使っている。

別々に起動していても、dzenの表示位置を適切に設定してあげれば一つのステータスバーに見える。

ちなみに、xmobarは、このcpu等のパソコンの状態を収集する機能を独自で持っていたりするので、その分、ステータスバーの設置がやや簡単(単純)になってる。


xmonad.hsの中で外部コマンドを実行する方法


繰り返しになるが、上述の方法によれば情報を表示するためのdzenは外部アプリであり、xmonadとは別に起動しておく必要がある。そこで、先の例では、.xsession等のスクリプトからdzenを起動した。

しかし今度は、xmonad.hsの中から外部コマンドを呼び出す関数を使って起動してみる。

そして同時に、先の例では、シェル上でパイプを使ってストリームを繋げ、logHookからの文字列データをdzenに渡していたが、ここでは、XMonad.Util.RunモジュールにあるspawnPipe関数を使うことによって、haskell上でファイルハンドルを使ってdzenに文字列を渡す事が出来るようにする。

spawnPipe :: MonadIO m => String -> m Handle

型シグニチャは少し見たことがないものになっているが、main = doの中で使われる限り、IOアクションが戻り値となり、副作用はコマンドの実行、生成される結果は、そのアプリの入力用ハンドルであると見なしていい。

実際のxmonad.hsでは
...
import XMonad.Util.Run

main = do
     h <- spawnPipe "dzen -hoge_option"
     spawn "conky conf_file | dzen2 -hoge_option"
     xmonad defaultConfig{...}

という感じになる。

h <- spawnPipe ... の構文は前に見た通り、IOアクションから、生成された結果のみ(ここではファイルハンドル)を取り出して、hという名前に拘束するもの。

そして、この入力用ハンドル「h」をlogHookで定義する関数の中に適切に設定することにより、xmonadから外部アプリの入力に直接文字列を渡すことが出来るようになる。

また、単なる外部コマンドの起動はXMonad.Coreモジュールにあるspawnコマンドが使えるので、上記の用に書けば、conkyによる情報収集のためのプロセスを別途起動することも出来る。

logHookに定義すべき関数

内部情報がアップデートされる度に呼び出されるlogHookに定義すべき関数として、XMonad.Hooks.DynamicLogモジュールには、dynamicLogWithPPが用意されている。

dynamicLogWithPP :: PP -> X ()
dynamicLogWithPP は PPデータ型というものを引数にとるのだが、このデータ型が、書き出すデータの書式等を設定するための型である。(これは、xmonad関数に対するXConfig l型のようなもの。)

なので、logHookには、適当なPPデータ型の値を引数とするdynamicLogWithPPを定義してあげれば良い。

そして、PPデータ型の値にはXconfig l型の時のdefaultConfigと同様にデフォルト値が定義されており、その一つが「dzenPP」でdzen用の初期設定がされている。これを元にdefaultConfigの時と同様にレコード構文を使って好きな部分だけをカスタマイズしたPPデータ値を作ることが出来る。

そして、各デフォルト値の内容は、リファレンスからソースをみて確認できる。(リファレンスのページでは、型シグニチャの右側端にあるリンクからその項目のソースを見ることが出来て便利。)

dzenPPのソース

2013年9月8日日曜日

xmonadとHaskell(その5:IOアクション)

IOアクション

(その4)でみた通り、dzenやxmonadの戻り値の型は「IO(なんたら)型」だった。
これはIOアクションと呼ばれる。

IOアクションは、「アクション」というだけあって、「何かを実行するもの」を表している。と同時に(なんたら)の部分で結果を生成してくれたりする。

すなわち、IOアクションは2つの事を行う。
「副作用」を生じさせ、その副作用が「結果」を生成する。

これを、先の2つの関数で見てみると以下の通り。

xmonad関数の副作用は、xmonadウインドマネージャを実行する。
生成される結果は無い。

dzen関数の副作用は、パネルのdzenを起動する。
生成される結果は、dzen用の設定を追加したXConfig l型データ。

Haskellで 半沢直樹!

以下の基本的な入出力の関数はIOアクションを返すもので、haskellのhello worldプログラムなんかで使われるもの。

コンソールへの表示
putStrLn :: String -> IO ()

コンソールからの入力
getLine :: IO String

この2つの関数で半沢直樹してみる。


Haskellでスクリプトを実行したい場合はコンソールで
$ runghc io_hanzawa.hs

また、コンパイルして実行ファイルを作ることも出来る。
$ ghc --make io_hanzawa.hs
$ ./io_hanzawa


main、そしてdo

Haskellにおいて、IOアクションの実行は、mainという名前でのみ行われる。
(ただし、ghciを使って対話的環境は、そのまま実行される)

なので、単純に言えば、

main = putStrLn "hello world"

と、一つのIOアクションを定義づけることしかできないが、io_hanzawa.hsの様に、do構文を使うことで複数のIOアクションをひと纏めにして、mainに定義づけることが出来る。
(若しくは、その4みたいに「>>=」演算子で繋ぐ)

またHaskellの構文では、パイソンとかと同様にインデント(段落下げ)に意味があることに注意。do構文の及ぶ範囲もインデントで表されている。

Haskellのコードは、コピペした時にエラーが出たら、インデントもチェックしてみるべし。

IO(なんたら)のなんたらって何?

先に書いたようにIOアクションは実行すると2つの事を行う。
一つが「副作用」、もうひとつが「結果」の生成。

putStrLnの型シグニチャは

putStrLn :: String -> IO ()

であり、実際のコードでは

putStrLn "どれくらい怒ってますか?"

として、使ってみた。
ここでの「副作用」は、画面に

どれくらい怒ってますか?

と表示する事であり。
生成される「結果」は、型シグニチャに示されたIOの次にある()である。
()は見た目の通り空のカッコであり、何もないという結果を返してくれる。

何もない結果というのは分かりにくいので、次のgetLineを見てみる。
型シグニチャは
getLine :: IO String

であり、実際のコードでは

okorido <- getLine 

として、「<-」演算子を使っている。
「副作用」は、画面にカーソルが出てリターンキーを押すまでの文字列入力を受け付けること。
一方、生成される「結果」は、画面から入力された文字列である。型シグニチャにあるIOの後ろのStringは、この入力から生成された文字列を示している。

そして、「<-」は 、「結果」のみを名前に結びつける演算子である。

何を言っているかというと、
他の言語では、標準入力からのデータを受け取る関数の戻り値は「文字列」そのものであることが多いので、

okorido = getLine

って感じに書きたくなるが、これはダメだということ。
getLineは、IOアクションを返す関数であり、「結果」である「文字列」そのものを返す関数ではない。
上のイコールを使った式は、単に、getLineと同じことがokoridでも出来るように定義されるだけなのだ。

xmonad.hsをdo構文で

(その4)で見ていたxmonad.hsでは「=<<」演算子を使っていて、その意味はIOなんたら型から、なんたらのみを取り出して、他の関数の引数にするための演算子と説明した。
実は、この役割と同じで直接別の関数の引数にするのではなくて、名前に結びつけるのが「<-」演算子の役割。

というわけで、(その4)の「=<<」演算子を使ったxmonad.hsを

do構文を使って書き換えれば以下のようになる。


dzenのIOアクションの「副作用」は、dzen実行ファイルを実行し、画面にステータスバーの表示等を行う。そして、その「結果」は、引数として受け取ったdefaultConfigをベースとして、ステータスバーの表示に必要な加工を行った新しいXConfig l型のデータを生成する。

そして、この結果である、XConfig l型データをconfという名前にバインドし、そのconfを引数としてxmonadを呼び出す。

xmonadのIOアクションの「副作用」は、xmonadウインドマネージャの表示とか管理とかである。そして、結果は空っぽの()である。

2013年9月7日土曜日

Gist

コード部分をきれいに表現するためにGitHubのGistを使ってみてる。
シンタックスハイライトをしてくれるので、かっこよい。
Bloggerでは、埋め込み用のコードをそのままhtmlモードに張り付ければよいだけなので使い方も簡単。

このGist、バージョン管理もしてくれるんだけど、ブログに引用する場合、以前のバージョンを指定することができるのかどうかがわからなかった。

今、例で書いてるxmonad.hsは、一つのGistでバージョン管理していけばよいのかなと思っていたけど、Gistのページで以前のバージョンを表示した状態で、埋め込み用タグをコピペしてもダメっぽい。

おかげで、いったん書き換えたのを元に戻す羽目に、、、、


Gistでのコード表示を張り付けるのがかっこよいからといって、コード部分を全部Gistを使っていたらなんだか収拾つかなくなりそうかなぁ。
ブログに引用するためのアカウントを別に作って管理するほうがよさげかも。


https://github.com/


2013年9月6日金曜日

XmonadとHaskell(その4)

初心者に優しそうなリンクとか

xmonadに興味を持って初めて触って見るときには、Archlinuxユーザーならarchlinuxのxmonadのwikiをかじりつつ、"xmonad haskell"とかでググるかもしれない。

割と最近の記事で、ひと通りの設定の仕方が短く纏まってて見やすいのがここ。
Haskell に興味がある人向け xmonad 設定ガイド

haskellには、見慣れない演算子なんかも目立つが、以下のサイトは基本的な事が書いてあってざっと見ておくとよさげ。"$"や"."は何?から、リファレンスを見る際に必須となる型や関数の定義の見方の参考として
Haskell基礎文法最速マスター

そんなこんなで、あちこちを見て回って、結局戻ってくるのが、本家のドキュメントページにあるモジュールのリファレンス。awesomeの時にもリファレンスはあったけど、xmonadの方が充実している気がするので、ガンガン読んでガンガン試す!!
xmonad api docs
xmonad-contrib api docs

もうひとつは、本家のページで紹介されているいろんな人の実際のxmonad.hsファイル
config archive


ステータスバーをつけてみる

ステータスバーを利用する場合に必要となるモジュールのリファレンスページ。

ステータスバーは、xmobar、若しくは、dzenを使う。
xmobarの紹介の方が多いような気がするので、ここではdzenを使ってみる。
(その1)で紹介したように、dzenを別途インストールして呼び出せるようにしておく。(archlinuxのパッケージとしてはdzen2とかdzen2-gitとか)

とりあえず、xmonad.hsの先頭部分でモジュールを読み込む。
そして、xmonadに渡す引数をちょっと変えてみる。



リファレンスで紹介されている通り、上記の使い方がもっとも簡単なステータスバーの付け方になっている。

さて、「=<<」である。
xmonadのページをよく見ると「>>=」という記号や、これをモチーフにしたロゴっぽいものもあちこちで見られる。これが、いわゆるモナドの演算子。なんだか、暗号チックだけれど、あんまり気にしなくても大丈夫。

xmonadの引数はXConfig l型

(その3)までに見てきた形は以下のもの。
main = xmonad defaultConfig {...}

defaultConfigは、既にデフォルト値を設定されたデータであり、{...}の部分は好きな部分だけをデフォルト値に上書きすることの出来る書き方だった。
そして、defaultConfigというのはXConfigなんたら型という型のデータだった。

ここらあたりで、なんとなく一度基本部分のリファレンスを見ると良いかもしれない。
xmonad api docs

XMonad.Mainモジュールのxmonad関数をみると

xmonad :: なんたらかんたら => XConfig l -> IO()

すなわち、xmonad関数は、XConfig l 型の引数を受け取るのだから、xmonadには、XConfig l 型を渡してあげれば良い。そして、defaultConfigそのものはXConfig l 型であり、defaultConfig{...}によって新たに生成されるデータもXConfig l 型なのだ。

XMonad.Coreモジュールをには「data XConfig l」の項目があり、コンストラクタが紹介されている。(その2)辺りで見てきた「値コンストラクタ」だ。

Humanの例で見ていた時、値の種類が複数あり、Otoko、Onna、Okamaというふうに型の名前と値コンストラクタを別にしていたが、値の種類が一個の場合は型の名前と値コンストラクタの名前を一致させるのが慣例らしい。なのでXConfig l型の値コンストラクタはXConfigになっている。リファレンスには、15項目のキーワードとその説明もされており、もちろん、このキーワードでレコード構文を用いてデータを作成できるし、直接順番に引数を15個渡してデータを作成することも出来る。

xmonadが受け取る値とdzenが返す値の型が違う

ステータスバーを付けるために dzen 関数を使い、更に謎の「=<<」演算子も付け加えて、以下のような形になった。

main = xmonad =<< dzen defaultConfig

ここで、dzen関数をリファレンスでみると

dzen :: なんたらかんたら => XConfig l -> IO ( XConfig(なんたらかんたら))

となっており、確かに、「dzen defaultConfig」の部分をみると、dzen 関数が XConfig l型の引数をとっている。
そして、戻り値としては「IO ( XConfig(なんたらかんたら))」というものが返されている。

一方で、xmonadは引数としてXConfig l型をとるので、見た目では、ちょっとだけ型が食い違っている。そう、なんか「IO()」とかいうので包まれてる。

なので、「=<<」は、この包んでる「IO()」を取って、中にある「XConfig なんたら」だけを取り出すみたいな演算子なのだ。

そして、一般に見られるのは「>>=」の方向であり、

hoge >>= hage >>= fuga >>= nya

なんて感じで、左から右に連続して適用されて行ったりするらしいが、「=<<」を使って逆に書くことも出来、思考を表現しやすい方を使えば良いらしい。

すなわち、
main = dzen defaultConfig >>= xmonad

と書いても同じ意味であり、動くが、なんか「main = xmonad」とひっついてる方が良さげに見えるので、そう書いてあるのかもしれない。