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アクションの解説で出てくる「足がたくさん生えている箱」の絵。本文の内容でも、そういう話なんだけれど、、、そこは「ネコバスのほうがいいなぁ、、」と思う。