2013年12月31日火曜日

vimとhaskell

自己紹介の処にも書いてある通り、普段はemacs派。
linux使い始めた頃がJE時代のMuleだったので、それしか知らなかったってのも大きな要素。

初心者の頃、root作業中に意思に反して立ち上がるviは恐怖以外のなにものでもなく、エスケープ連打と:qだけはマスターしてた。

ところが、なんかのきっかけでvimというのを触ってみたら、インサートモードでもバックスペースで文字が消せ、しかも、カーソルキーでカーソルが動く!!そんな普通のエディタっぽくなってたので、ドットインストールとかvimtutorとかして、最近では、とりあえず触るくらいはできるようになった。

で、emacsでは、各プログラム言語に対応した「なんとかモード」的なものがあるのだけれど、vimってどうなってるの?という初心者的疑問から、ちょっとだけ環境整備できたので、そのメモ。


設定ファイルvimrc

個人の設定ファイルの場所は7.4以降(archlinuxでのパッケージは7.4)

~/.vim/vimrc

vimは、「vim script」と云うもので、制御できるらしい。
以下のページのスライドにざっと目を通すと雰囲気が確認できた。
http://www.slideshare.net/cohama/vim-script-vimrc-nagoyavim-1

とりあえずは




プラグインのための準備


vimの機能拡張の管理をするための仕組みとして「neobundle」というのを使う。

https://github.com/Shougo/neobundle.vim

の「Quick start」の項目をみて、準備をする。

まず、~/.vim/bundle/ディレクトリを作成して、その中にgithubからneobundle.vimをコピー

次に、~/.vim/vimrcにneobundleのための必要事項を記入。(例をコピペでOK)

これで準備完了!!

プラグインのインストール


プラグインというものをインストールしてみる。
まずは、ステータスバーをカッコよく!!

https://github.com/itchyny/lightline.vim

スクリーンショット見ただけで、ワクワク。

「Installation」の項目をみると幾つかの方法が示されている。
うちは「neobundle」という方法を使うので、「neobandle」の方法を見てみると

NeoBundle 'itchyny/lightline.vim'


とかかれている。

この一行を、~/.vim/vimrcにコピペする。
コピペの場所は

NeoBundleFetch 'Shougo/neobundle.vim'


の下辺りに書けばよい。



haskellの環境


vimでhaskellする人たちのページによく紹介されているものに「ghcmod-vim」という、haskellコードの中の型の表示やコードのチェックをしてくれる便利なプラグインがある。

https://github.com/eagletmt/ghcmod-vim

で、このプラグインは、「ghc-mod」という外部プログラムを使うので、それを先にインストールしてねと説明される。そして、そのインストールは、「cabal」というのを使ってインストールできますよと説明される。「cabal」っていうのは、haskellプログラムのパッケージマネージャのようなもので、pacmanみたいなものらしい。

そこで、注意が必要なのだ!
archlinuxでは、pacmanというパッケージのシステムがあるが、それを介さずにシステムに何かをインストールする時は、その部分について自分で管理しなければならない。

以前railsとかを使うときに、rubyでも同じようなことがあった。何かのコミュニティが独自のパッケージシステムを持っているときに、ディストリビューションのパッケージシステムとどう折り合うかは面倒くさい問題だ。

というわけで、haskellについてのarchlinuxのwikiをみてみた。
https://wiki.archlinux.org/index.php/Haskell_Package_Guidelines

すると、パッケージング云々の前に、archlinuxでのhaskellの環境について知っておくべき重要なことが書いてあった。

archlinxとhaskell


まずは、リポジトリについて。
Archlinuxとhaskellの関わり方の方法は2つの選択肢があるらしいということを知った。

ひとつは、archlinuxのリポジトリにあるhaskellに関するパッケージを使うという選択肢。

もうひとつは、最新のhaskellパッケージを取り入れてある、ArchHaskellプロジェクトのリポジトリを使うという選択肢だ。

とりあえずは、archlinuxのリポジトリで満足。

次に、「Haskell-Platform」というものについて。
システム上でhaskellを使う場合、通常は「Haskell-Platform」と呼ばれるひとまとまりの環境をインストールするらしい。ところが、archlinuxのパッケージングでは、これらは以下の5パッケージに細分されている。

ghc
cabal-install
haddock
happy
alex
特に、xmonadをインストールする時に依存で勝手にghc等がインストールされた場合には、他のものがインストールされていなかったりするので、超要確認だ

というわけで、archlinuxでcabal installするなら、まずは

pacman -S ghc cabal-install haddock happy alex

しろってこと。

cabalでghc-modをインストール

さて、cabalによるhaskellパッケージのインストールはデフォルトでユーザー領域、~/.cabal以下にインストールされる。なので、当初の心配事であったシステムとの整合性はあまり問題ないとおもう。

まず、cabalを使う下準備は、シェルから「cabal update」すると、はじめてのときは、~/.cabalディレクトリを作って、必要なことをやってくれる。あと、cabal自体の最新版があるからアップデートしてね的なメッセージは無視。cabal-installパッケージ自体はpacmanの方に任せることにする。

その作業が終わったら、パッケージをインストールしてみる。ターゲットはghc-modだ。

cabal install ghc-mod

依存関係にある他のパッケージとかも一緒にインストールしてくれる。
haskellのコンパイルはs101にとっては重労働らしく、結構な時間がかかるが、辛抱強く待てばそのうち終わる。

実は、、


実は、うちでは結構ハマったので、他にも陥っている人がいた時用のメモ。

haskell-platformというものを知らなかったので、適当にcabal-installパッケージをpacmanでインストール。そして、「cabal install ghc-mod」すると、インストール途中で何かのファイル(モジュール)が足りないということで、エラー。
これが、事の発端だと思う。

そこから、あまりエラーメッセージを読まずに、上で書いたarchlinuxのwikiを見て、リポジトリ変えたり、足りないパッケージ入れたりして、試してた。その中で、cabalインストールを初期化した方が良いだろうと思って、.cabalディレクトリを消したりしてた。

で、どうもうまくいかない。そして、エラーメッセージをよく見ると、ありそうなはずのモジュールがないといっている。。。

で、いろいろやってて見つけたのが~/.ghcの中のパッケージデータベース。
~/.cabalを直接消したりしてたから、これとの整合性がとれなくなってたらしい。

なので、はじめてcabalする時にごちゃごちゃやって、上手くいかなくなってるなら、~/.cabalと~/.ghcを両方削除して、一からやり直してみると良いかも?


お勧め?


とりあえず、この辺を順番に試してみようかなぁ

VimでHaskellを書く時に入れておきたいプラグイン5つ

すごい Vim で Haskell を書こう ... の補足

四苦八苦して入れたghc-modを使うghcmod-vimは、s101だと、結構重かったりした。
まだ、全部は試してないけれど、記念撮影♪




2013年12月28日土曜日

xmonadとHaskell(その15:レイアウト設定)

(その14)からの続きで、xmonad関数の型クラス制約に戻ってみる。

xmonad :: (LayoutClass l Window, Read (l Window)) => XConfig l -> IO ()

前回見た単純な形の型クラス制約と比較すると、ちょっと複雑な形なので解きほぐそう。
まずは、カンマで区切ってカッコで包まれいる書式は、複数の型クラス制約をあらわしていて、

LayoutClass l Window



Read (l Windw)

に分解することができる。

そこで、LayoutClassに付いてのみに着目してみると

xmonad :: LayoutClass l Window => XConfig l -> IO ()

のような形に書けるのだが、これも前回の単純なメソッドの型クラス制約と違うところがある。何が違うかといえば、型クラス制約の型の形である「l Window」と、引数「XConfig l」が一致していない。

これは、XConfig l型データそのものが、LayoutClass型クラスにあるメソッドの引数になるものではないからである。

メソッドの引数になるのは、XConfig l型データに内包される「l Window」型の値、すなわち、layoutHookフィールドの値だ。つまり、xmonad関数は、レイアウト操作をする時に、この値を引数にして内部的にLayoutClass型クラスのメソッドを呼び出すのだろう。

というわけで、結論から言えば、layoutHookフィールドに定義するものが、LayoutClass型クラス(及び、Read型クラス)のインスタンスの値であれば良いということだ。

さてさて、LayoutClass型クラスというのは、どんなもので、どんなメソッドがあるのか気になる場合には以下のリファレンスが参考になる。
http://xmonad.org/xmonad-docs/xmonad/XMonad-Core.html#t:LayoutClass

しかし、それらメソッドは上で話した通り、xmonad自身が内部的に呼び出すだけなので、レイアウトに関する型を自作してインスタンスとしての実装をするのでない限りは、そういうメソッドがあるという知識を持つレベルで十分ぽい。

というわけで、「型クラス」というものがあるという知識を得た上で、あらためてレイアウトの設定に臨んでみる。

さっそく、XMonad.Layoutモジュールのリファレンスへ!
http://xmonad.org/xmonad-docs/xmonad/XMonad-Layout.html

layoutHookに定義するのはLayoutClassのインスタンである型の値ということがわかったが、具体的になんの型がLayoutClassのインスタンスなのかどうやって調べるのだろう??

実は非常に簡単!!
リファレンスの「型」の項目には、ちゃんと「インスタンス(instances)」という項目があったりするのだ。

Full a型なら、以下のような記述が見つかる。

LayoutClass Full a
Read (Full a)
Show (Full a)

これは、LayoutClass、Read、Showの型クラスのインスタンスだという記述なのだ。


レイアウト設定の基本


さて、型クラスのはなしはこれくらいにして、次は、xmonad.hs上で実際にレイアウト設定を行う上で知っておくべき更なる実践的な知識を把握しよう。

単独基本形


単独で、LayoutClass型クラスのインスタンスとなっている型は、大雑把に言えば、「基本的なレイアウト」を表している。例えば、「Full a型」や、「Tall a型」であり、これらの型の値は、それ単独で1つのレイアウト設定を行えるのだ。

だから、Full a型を単独でlayoutHookフィールドに設定するのが、レイアウト設定のもっとも基本的な設定になる。

layoutHook = Full


レイアウトの連結


デフォルトのレイアウト設定でお馴染みのように、レイアウトはm-spaceで切り替えることが可能だ。その仕組みがレイアウトの連結だ。

これは、個々に独立したレイアウト設定であるLayoutClass型クラスのインスタンスの値を(|||)演算子で繋ぐだけである。

layoutHook = Full ||| Tall 1 (1/100) (1/2)


メッセージ


さて、連結されたレイアウトは、m-spaceで切り替える事ができるのだが、それは何故なのか??

実は、レイアウトの設定に働きかける「メッセージ」という仕組みが、そのキーにバインドされているのだ。defaultConfigでのm-spaceのキーバインドを覗いてみると

((modMask, xK_space ), sendMessage NextLayout)

そして、sendMessage関数はXMonad.Operationsに定義がある。

sendMessage :: Message a => a -> X ()


ここにも型クラス制約が出てきた。すなわち、Message型クラスのインスタンスを引数に取るのだ。

で、実際にsendMessage関数の引数に渡されている「NextLayout」は、大文字アルファベットなので、値コンストラクタである。

そして、このNextLayoutもXMonad.Layoutのリファレンスに見つけられる。

「ChangeLayout型」の項目をみてみよう。
そこに、値コンストラクタとしてNextLayoutが見つかり、その他にFirstLayoutがあるのがわかる。また、インスタンス項目には「Message ChangeLayout」と書かれており、Message型クラスのインスタンスであることが確認できる。

というわけで、xmonadでは、このsendMessage関数とMessage型クラスのインスタンスを使ってレイアウト設定を操作することができるのだ。

すなわち、(|||)演算子を使ってレイアウトを結合している時には、このメッセージをつかってレイアウトの切り替えができる。

XMonad.Layoutモジュールにある他のメッセージもついでに見てみよう。

「Resize型」は、マスターペインの大きさを変化させる。
値コンストラクタはShrinkとExpand。

Tallレイアウト等の時、m-lやm-hで変化出来るアレだ。
defaultConfigソースを見れば

, ((modMask, xK_h ), sendMessage Shrink)
, ((modMask, xK_l ), sendMessage Expand)


となっている。

もう一つ「IncMasterN型」は、マスターペイン側に入れるウインドウの数を変化させる。
デフォルトでは、Tallレイアウトのメイン(左側)は、ウインドウが1つだが、この数を増やしたり減らしたりできる。
値コンストラクタは一つの引数Intをとる。増やしたり減らしたりする数の指定だ。
で、またdefaultConfigのソースを見れば

, ((modMask, xK_comma ), sendMessage (IncMasterN 1))
, ((modMask, xK_period), sendMessage (IncMasterN (-1)))


となっている。

このResize型やIncMasterN型のメッセージはTallレイアウトにだけ効果があるわけでなく、同じような構造のレイアウトには同じように効果がある場合もある。

また、それぞれのレイアウトについては、独自の操作用メッセージがセットになっていたりするので、モジュールの中にMessage型クラスのインスタンスを見つけたら、何の操作をするためのものかを確認しよう!!

ちょこっと修正系


レイアウトの中には、自分自身が単独でレイアウトになるのではなくて、既存のレイアウトを変化させて新しいレイアウトを作るものがある。
例えば、「Mirror l a型」だ。

値コンストラクタの形は、

Mirror (l a)

となっており、インスタンスの形が

LayoutClass l a => LayoutClass (Mirror l) a

となっている。

「(Mirror l) a」型がLayoutClass型クラスのインスタンスなのだ。
つまり、LayoutClassのインスタンスをMirror値コンストラクタの引数に与えると、新たなLayoutClassのインスタンス、すなわち、レイアウトが生成される。

このMirror l a型、具体的には、Mirror値コンストラクタに、ある既存のレイアウトを入れると、その既存のレイアウトを90度回転した新しいレイアウトを生成してくれる。

さて、このMirror l a型のような役割を果たしてくれるものの仲間にLayoutModifierと呼ばれるものがある。
以前(その10)でメモした、ステータスバーの部分にウインドウが重ならないようにlayoutHookに設定したavoidStruts関数がこれにあたる。

XMonad.Hooks.manageDockモジュールのリファレンス
http://xmonad.org/xmonad-docs/xmonad-contrib/XMonad-Hooks-ManageDocks.html


avoidStruts :: LayoutClass l a =>
l a -> ModifiedLayout AvoidStruts l a


つまりは、LayoutCLassのインスタンスを引数にして渡すと、ModifiedLayoutなんたら型が帰ってくる。

この「ModifiedLayoutなんたら型」は、XMonad.Layout.LayoutModifierモジュールで規定されているのだが、重要なのは、コレ!

(LayoutModifier m a, LayoutClass l a) =>
LayoutClass (ModifiedLayout m l) a


つまりは、LayoutModifier型クラスのインスタンスを用いて修正された、「(ModifiedLayout m l) a」型はLayoutClassのインスタンス、すなわち、レイアウトになるってことだ。

具体的な使い方のパターンは、avoidStrutsのように、修正用関数にレイアウトを引数として渡すと、新しいレイアウトが返るパターンだ。

LayoutModifierの例をもう一つ。
http://xmonad.org/xmonad-docs/xmonad-contrib/XMonad-Layout-Spacing.html

実は、以前使っていたawesomeからxmonadに乗り換えたきっかけが、このspacing機能だ。

before

layoutHook = spacing 2 $ Tall 1 0.03 0.5

after

わかる??? このウインドウの周辺のクールな隙間!!

smartSpacingなら、ウインドがひとつのときには隙間を作らないという、ちょっとだけかしこな感じを出してくれる。

さて、このクールな隙間!!
http://www.youtube.com/watch?v=4mMb7qXwhuU
の人もawesomeからxmonadへ(隙間がやりたいこととは限らないけれど、そうじゃないともいいきれない)
某掲示版でも、この隙間は話題になってて、みんな、「お前は俺か」なのだ。
そう、xmonadを使う理由は、haskellがどうだとか、xineramaがどうだとか言ってるけど、「ほんとはお前ら単に隙間がかっこいいと思っただけなんじゃね?」と密かに思ってたりする。


さて、話は変わって、実は、(|||)演算子の結合結果も、ある意味このレイアウト修正系の種類の一つであると考える事も出来る。(|||)関数は、もともとのレイアウトに、他のレイアウトも選択できる機能を追加した新しいレイアウトを生み出しているのだ。


(|||) :: (LayoutClass l a, LayoutClass r a) =>
l a -> r a -> Choose l r a


このChoose l r a型はも、LayoutClassのインスタンスだ

(LayoutClass l a, LayoutClass r a) => LayoutClass (Choose l r) a


ちなみに、(Mirror l)や(Choose l r)、(ModifiedLayout m l)は(その13)でみた例の型コンストラクタと部分適用のアレだ。

というわけで、(|||)演算子の戻り値は、ひとつのLayoutClass型クラスのインスタンスなのだ。

これは、何を表しているかというと、「ひとまとまり」であるということも表しているのだ。

なので、次のような書き方は

layoutHook = avoidStruts (Full ||| Tall 1 (1/100) (1/2))

当然、FullにもTallにもavoidStrutsの効果が及ぶ。
これは、カッコで括っているからではなくて、FullとTallの結合した一個のレイアウトに対してavoidStrutsしてるイメージだ。

avoidStrutsの効果を個別に及ぼさせるには、結合させる前にもとのレイアウトを直接変化させればよい。

LayoutHook = Full ||| avoidStruts (Tall 1 (1/100) (1/2))



2013年12月23日月曜日

xmonadとHaskell(その14:型クラス)

(その13)では、XConfig l型のリファレンスで、layoutHookフィールドに定義すべき型が「l Window」型であることを確認し、この型として具体的にどんな型が使えるのかを調べるためにdefaultConfigのソースを覗いてFull a型、Tall a型などの型の値が使われていることを確認した。

しかし、自分でlayout設定をする時に、結局何を頼りにlayoutHookフィールドを設定すれば良いのか?XConfig l型のリファレンスを見ているだけでは、「l Window」型の型変数「l」が何を指しているのか分からずじまいだった。

今回はその答えに近づくために、xmonad関数の型シグニチャをいきなり見てみる。

xmonad :: (LayoutClass l Window, Read (l Window)) => XConfig l -> IO ()

この赤字の部分は、「型クラス制約」とよばれる。
というわけで、今回は「型クラス」の話だ。

型クラスって?


まずは、これ。
人間と猫がいて、挨拶をする関数を作りたいんだけれど、、

「hello」という関数名で人間にも猫にも挨拶しようとしたんだけれどうまくいかない。
普通、同じ関数名で複数の異なる処理を同時に定義することは出来ないのだ。

だけど、、、惜しいよね。
確かに「型」は違うけど、似たような処理じゃん??!

そこで登場するのが「型クラス」の仕組みなのだ。

これで、人にも猫にもhello出来るのだった。



すなわち、型クラスを使えば、同じような処理をするために、同じ関数名を使って、色んな型を扱えるのだ。ちなみに、こういうのを「多重定義」とかいうらしい。

だからもっと増やして、犬にだって、うさぎにだって、インコにだって、挨拶ができるようにすることができるし、更に実は、helloだけじゃなく、goodMornig関数やgoodNight関数等、複数の関数をこのグループに含めることも出来る。

というわけで、実際に上の型クラスを改良しながら、型クラスに親しんでみる。

型クラスを宣言してみる


まずは、4行目が型クラスを作る宣言の部分。

class Hello a where
classwhere」で型クラスを作りますよという書式だ。

そして、その中にある「Hello」が型クラスの名前。

型クラス名は、型や値コンストラクタと同様、大文字のアルファベットから始まる文字列だ。今度は、helloだけじゃなく、色々な挨拶をするので、「Aisatu」という名前の型クラスにしてみる。

更に、型クラスの名前に続いて、「a」と書かれている。
これが、この型クラスで定義する関数の共通の引数になる型を表現している型変数だ。

新しい型クラス「Aisatu」の宣言は以下のようになる。

class Aisatu a where

関数を登録


whereの後ろに、共通で使いたい関数を書いていくことになる。
この、型クラスに属する関数のことは、特に「メソッド」とか「メンバ関数」と呼ばれたりする。

5行目にそのhelloメソッドが書かれている。

hello :: a -> String

関数の型表現のみが宣言されている。
この中の型変数「a」が、型クラス名の後ろにある「a」と連動している。
つまりは、関数の引数として、人や猫等の色々なものにしたい部分を型変数「a」にして、関数の型宣言をすればよい。

ここで、別の挨拶関数も登録しておこう。

hello :: a -> String
goodMorning :: a -> String
goodNight :: a -> String

どの関数も、hello関数と同じで、ある型の値を一つ引数にとって文字列を返す関数だ。

型クラスの仲間であるインスタンスにする


型クラスの仕組みは、まず、あるグループを作って、そこでいろんな型で共通に使いたい関数を宣言する。次に、その関数を使いたい型は、それぞれ型についてのその関数を実装することで、仲間に入るという風になっている。

というわけで、まずは、ある型が仲間になりたいと宣言するための書式が7行目だ。

instance Hello Human where

型クラス宣言と似た感じで「instancewhere」で、ある型をある型クラスの仲間にしますよと宣言する書式だ。そして、仲間になる型を「インスタンス」と呼んだり、ある型を仲間にすることを「インスタンスにする」とか言ったりする。

次に「Hello」が仲間になる型クラスの名前で、その後ろの「Human」が仲間にする型の名前だ。

Humanを新しい型クラス「Aisatu」のインスタンスにするための宣言は、次のようになる。

instance Aisatu Human where

仲間になりたいと宣言したら、後は実際にその具体的な型で、メソッド関数が使えるようにプログラムすればよい。

whereの後ろ、8行目では、hello関数を実際に定義している。
すなわち、whereの後ろで、型クラスで宣言されているメソッドについて、Humanという具体的な型を引数とする時の具体的な処理をプログラムするのだ。

そのための仕組みであって、当たり前といえば当たり前だけれども、11行目を見ると、8行目と同じhello関数が実装している。まさに「多重定義」だ。

さて、新しいAisatu型クラスには、3つのメソッドがあるので、Humanをインスタンスにするには、この3つのメソッドを実装する必要がある。

instance Aisatu Human where
  hello h = "hello, " ++ h_name h
  goodMorning h = "goodMorning, " ++ h_name h
  goodNight h = "goodNight, " ++ h_name h

同様に、Neko型もAisatuのインスタンスにし、更には、新しくInu型を定義して、これもAisatuのインスタンスにしてみる。



さて、実際にghci上でこのコードを読み込んでみる。
そして、hello関数の型シグニチャを確認すると、

hello :: Aisatu a => a -> String

一番初めに見た「型クラス制約」というものが付いていることが分かる。

型クラス制約された関数


今までAisatu型クラスを作ってきた流れから察することができる通り、ここでの「型クラス制約」というのは、「helloの引数aは、Aisatu型クラスのインスタンスを使ってね」ということを表している。

すなわち、aというのは、多重定義を実装した具体的な型のことであり、その実装をしているからこそ、helloの引数に使えるのだ。逆に、実装をしていない型はhello関数の引数になりようがないだろう。

更に、型クラス制約というのは、hello関数そのものつまり、メソッドを使う場合だけにつくものではない。当たり前だが、内部でhello関数を使うような、関数を定義した時にも、その定義された関数に型クラス制約が付く。

例えば、

genkina_hello x = (hello x) ++ "!!!"

というhelloメソッドを内部で使うgenkina_hello関数の型シグニチャは

genkina_hello :: Aisatu a => a -> String

と表現される。

基本クラスと自動導出

おまけとして、巷のxmonad.hsの中でもちょくちょく見られる、便利な技の話。

haskellの標準ライブラリPreludeは聞いたことがあると思う。
ghciを起動するとカーソルに

Prelude>

とか出てくるアレだ。
そして、そのモジュールのリファレンスがこれだ。
http://hackage.haskell.org/package/base-4.6.0.1/docs/Prelude.html

色々と見たり聞いたりしたことのあるようなものが、並んでいると思う。

そんな中、「型クラス」についても標準ライブラリで見たことあるだろうものが基本クラスとして定められていたりする。

Eq型クラスの(==)や(/=)
Ord型クラスの(<)や(>)等

つまりは、これらの関数はメソッドであり、自作の型についても、これらを実装してインスタンスになれば、その関数を使うことができたりするのだ。

しかし、実は、これら基本型クラスのうち、次のものはについては、自作の型についてinstance〜where構文をつかった実装を必要とせずに、自動導出という機能を使って、一瞬でインスタンスになれたりするすごい機能がhaskellにはある。

自動導出できる型クラスはEq,Ord,Enum,Bounded,Show,Readだ。

使い方は、自作の型宣言の後ろに次のようにderivingキーワードとカッコで囲った型クラスを書くだけだ。

data Human = Human {h_name :: String} deriving(Eq,Ord,Show)

複数の型クラスのインスタンスにしたいなら、カンマで羅列すればいい。



2013年12月8日日曜日

xmonadとHaskell(その13:具体型と型コンストラクタ)

タイル型ウインドマネージャの楽しい処のひとつとして、レイアウトがある。

デフォルトのレイアウト「Tall」


デフォルトのキーバインドでは、M-Spaceでレイアウトが切り替わる。
「Mirror Tall」と名づけられたレイアウト


更にM-Spaceで、画面一杯に広がる「Full」レイアウト


xmonadにおいて、これらレイアウトは、「型」として表現されている。

XConfig l型データのリファレンスでいつものようにコンストラクタを確認して、layoutフィールドをみると「l Window型」となっている。
この型のパターン、いつも見ている「XConfig l型」に似た変な型だ。

そもそも、Int型やBool型のような「なんとか型」じゃない、「なんとか なんとか型」ってなんだろう??と思っている人もいると思う。

haskellの初心者解説をネットであちこち漁っていると、「なんとか なんとか型」の代表格である「Maybe a型」にまずは出会うかもしれない。
そして、さらに、「Map k a型」に出会ったり、解説によれば「実はリストも [] a型」だとかいう話も読んだかもしれない。

そこで、にわかhaskellerである我々は、解説を読みながら一生懸命イメージして理解しようとする「このわけのわからん型は具体的に何を表現しているのかな?」と。

Maybe a型は、Just aとNothingがあって、この型はエラーを含む戻り値を表現する場合に使えたりするとか、リストの話を聞いて、aの部分は任意の型であり、それを含んで表現される[a]というのは箱のような存在だとか頑張って想像してみたかもしれない。

やがて、そもそも、我々はxmonadのカスタマイズをするためにhaskellを読み始めていたりするので、遅かれ早かれ、「XConfig l型」とはなんぞや?と理解しようと努力するようになる。

そして、頑張って想像してみるに、「任意のl型データを入れてある箱のような存在がXConfigだ」とか。layoutHookフィールドの中にみつけた同じ「l」の字をもつ、「l Window型」に至れば、その任意のl型データがWindow型データの箱で、、、うにゃうにゃうにゃ、、、

さらに、不幸にもリファレンスからStackSetを覗き見たり、型クラスの領域に踏み入れたりしてしまうと、もっと大量の型引数がでてきてしまって、そっとページを閉じるのだ。「今はよくわからんが、haskell的センスが磨かれたら、想像出来るようになるにちがいない!!だって、haskellは難しいんだから、理解するのに時間がかかるんだもん」と、、、

言わずもがな、少し前の私だ。

しかし、上記の気弱な言い訳は見当違いのものだったりする。わからないのは、難しいからではなくて、知らないからなのだ。

「そもそも型とは何ぞや」という壮大な問いを一生懸命イメージしたりする以前に、型について初心者が知っておくべき超重要なことが存在する。

というよりも、このことを知れば、xmonadの設定レベルでは「そもそも、型とはなんぞや??」なんいうつまらない事で悩まなくなる。

なので、haskellの教科書的な本もまだ持たず、ネットのhaskell情報を断片的に読み漁りながら彷徨っているxmonadカスタマイザーの中でも無謀といえる猛者たちへ、haskellの型にもう少し親しみが持てるようになれるかもしれないメモをしたい。

型の種類


haskellの型に親しんでいくとき、まず第一に覚えなければならないことがある。

それは、型には種類があるということだ。

この型の種類は、一つが「具体型」と呼ばれるもの、もう一つが「型コンストラクタ」と呼ばれるものである。

まず、いわゆる「普通の型」だと思っていたものがだいたい「具体型」だ。
Int型、char型、String型、Bool型

次に、もう一つの種類、型引数をとるものを「型コンストラクタ」という。
いつも見ている「XConfig l型」であるが、「XConfig」の部分が型コンストラクタである。そして、「l」の部分を「型引数」という。

更に、型コンストラクタは必要な型引数が与えられると、具体型になるということを覚える。

すなわち、XConfigという「型コンストラクタ」にlという型引数を与えると、「XConfig l型」という「具体型」になるのだ。

この簡単な約束事を把握した上で、少し練習だ。
ソースコードの中の型を表現する項目のある部分に

 Hoge a

とかかれている部分があったとする。
上の説明を見た後だと、Hogeが何で、aが何で、Hoge aが何かわかるだろうか?





簡単?
正解は、


である。

すなわち、これだけだとわからないことだらけなのだ。
そして、この「わからないこと」がわかるようになると、色々とわかるようになるのだ。

まず、Hogeは、大文字のアルファベットで始まっており、後ろに引数があるので「型コンストラクタ」であることがわかる。

そして、型コンストラクタは「必要な型引数」が与えられると具体型になる。
この「必要な」という部分が超重要なのだ。

型コンストラクタの型引数の数


(その11)でみた連想配列を表現する「Map k a型」を思い出してみる。
上記の流れから言えば、Mapは型コンストラクタであり、k、aはそれぞれ型引数だ。
連想配列のキーとなるものの型がk、値となるものの型をaとして、これを型コンストラクタMapにあたえると、それは具体型「Map k a型」となる。
すなわち、Mapは二つの型引数を必要としている。

では、「Map k」と書いてあると、それは何であろうか??

なんか、よく似た事をどっかできいたことあるよね。
関数の部分適用の話だ。

実は、型コンストラクタは部分適用ができ、部分適用された型コンストラクタは、型コンストラクタを表す。

すなわち、「Map k」と言う表現は「型コンストラクタ」を表している。

さて、ここで「Hoge a」である。
そう、Hoge aという表示単独を見た場合、それが具体型を表しているとは限らないのだ。

なぜなら、Hogeは少なくとも型コンストラクタであるが、その必要とする引数の個数がわからないからだ。
もし、Hogeが引数をひとつとるのなら、Hoge aは具体型であるけれども、2個以上の引数を取るのなら、Hoge aは型コンストラクタなのだ。

型コンストラクタの型引数の種類


更に、Hoge aの「a」だ。
型の表現において、小文字アルファベットで始まる文字列で示される者は型変数と呼ばれる任意の「型」である。すなわち、「型」ということは、2種類あり、「具体型」であるとは限らず、「型コンストラクタ」かもしれないわけだ。

そう、今、あなたが「もしや??!!」と思った通り、型コンストラクタは、型コンストラクタの引数をとれる。関数の引数に関数が取れたのと同じだ。

いつも見ている「XConfig l型」の「l」は実は型コンストラクタだったりする。

わかるために注目すべき点

上記から言えることは、単純に、型の世界で大文字で始まっている文字列だけをみても、それがとるべき型引数の数を知るすべはなく、それが具体型か型コンストラクタかが分からないのだ。

しかし、実はこの「Hoge a」については意地悪な問いかけであって、実際には型の表現は次の様になっている。

data Fuga (Hoge a) b = ....

ここで、今一度、「ソースの中で型が表現される場所」に着目してみたい。
haskellのソースでは、型が表現される部分とそれ以外の部分が明確に区別されている。

上の様に型定義のdataキーワードに続くイコールまでの部分、そして、値コンストラクタのフィールド(引数)部分、さらに、型シグニチャに見られる::演算子の右側部分だ。

そして、ここには約束事がある。この部分に表現される型は、必ず具体型なのだ。

すなわち、「Fuga (Hoge a) b」は全体として必ず具体型なのだ。
ここでは、型表現の中でも括弧は用いられてそれがひとまとまりになる。

なので、Fuga型コンストラクタは、型引数を2つとること「だけ」は「わかる」のである。
(Hoge a)自体は、Hogeの必要とする引数の個数、もしくは、Fugaの取る引数の種類の情報がわからなければ、詳しくは「わからない」のだ。

また、先に「l」は型コンストラクタだと書いた。
XConfig l型のlayoutHookフィールドについてみると、

layoutHook :: !(l Window)

すなわち、::演算子の右側は「l Window」が具体型であり、「l」は型引数を一つとる型コンストラクタであることがわかる。
そう、::の右側が全体で具体型だと決まっているので、lは型引数を一つとる型コンストラクタであるということが分かるのだ。ちなみに、Windowは具体型か型コンストラクタかはこれだけだと「わからない」ことが分かると思う。

というわけで、型に付いても関数の時と同じように、こまめにリファレンスをみる必要がある。何を見るかというと、特に、型引数の数である。

また、ghci上の「:k」コマンドで型の種類が調べられる。

import XMonad.Core
:k XConfig

とか試してほしい。「*」が具体型を表現している。

関数の型シグニチャ


実はここまでの知識を活用すると凄く面白いことが見えるようになったりする。

関数の型シグニチャだ。
これも::演算子の右側に書かれていることをいつも見ているので知っているはずだ。

関数については、(その8)でメモした。
関数がカリー化されいることや、部分適用にも馴染んでいるならば、既に、引数の間に挟まれた矢印が、引数を区切る単なる飾りでは無く、「関数」を表現しているというのは何となく感じられるようになっていると思う。

しかし、もっと驚きの秘密があったりする。
関数が::演算子を使って型シグニチャとして表現されているのは、引数の型を表現するためではない。

実は、そもそも「関数」自体が「型」なのだ。

ここで、「関数が型ってどういうことよ?」といって頭を悩ませる必要はない。
単に今まで上で見てきた「型の表現」の話として以下を読み進めて欲しい。

例えば、足し算の関数として、add関数があったとする。
Intを引数に二つとって足して結果を戻り値にする。
それを型で表現すれば、以下のようになるだろう。

add :: Int -> Int -> Int


haskellの関数はカリー化されているので、以下の様に書き換えることが出来る。

add :: (Int -> (Int -> Int))


ここまでは、(その8)でメモした話だ。

ここからが、今回の話だ。

まずは、(Int -> Int)の部分に着目してみる。
矢印記号は、通常の関数における中置表記の様な存在だ。
なので、実は構造としては、二つの引数をとる関数と同じようなものであり、括弧でくるむと前置表記できたりする。

すなわち、こうだ

((->) Int Int)

ここで、「->」というのが型コンストラクタであり、この型コンストラクタが二つの具体型を型引数としてとるとすれば、、((->) Int Int)は、「(->) Int Int型」のデータとよめる。

同じように全体も書き換えられる。

((->)  Int ((->) Int Int))

(->)型コンストラクタが、Int型のデータと、((->) Int Int)型のデータを型引数としてとって具体型になっているのだ。

つまり、「関数」というのは、(->)型コンストラクタが型引数を二つとって作る具体型なのだ。

凄くない??
だけど、まぁ、xmonadの設定には何の関係もなかったりする。

レイアウトの仕組み


さて、そんなことよりもxmonadでのレイアウトだ。
その設定は、layoutHookフィールドに定義することになる。

さっきもみたが、

 layoutHook :: !(l Window)

である。
型の話だけで言えば、Windowを型引数にとって具体型になるものである。

で、これって、具体的になんなのか?

まずは、XMonad.Layoutモジュールのリファレンスを見てみる。
http://xmonad.org/xmonad-docs/xmonad/XMonad-Layout.html

そこに見慣れた文字がある、FullやTallやMirrorだ。

data Full a

と書かれている。あらためて型の定義を見ると分かることがある。
これは、Full型コンストラクタが型引数をひとつとって「Full a」型という具体型をつくるということだ。

もう一つ、

data Tall a

も同様だろう。ちなみに、「Tall」は、背が高いではなく、多分、TileのTとall windowのallじゃなかろうかとおもう。

更に、

newtype Mirror l a

というのがある。
newtypeキーワードは気にしないでみると、Mirror型コンストラクタは型引数を二つとって「Mirror l a」型という具体形をつくることはわかる。そして、これは、(Mirror l)型コンストラクタが一つの型引数をとって、「(Mirror l) a」型という具体形をつくるといいかえることができる。

すなわち、このどれもが「l Window」型に合致するのだ。

ここまでは、型の話だ。
では実際に、layoutHookフィールドではどうかくのか??
型定義のイコールの右側、値の話である。

Full a型の値コンストラクタはFullだ

layoutHook = Full

Tall a型の値コンストラクタは引数をとるので

layoutHook = Tall 1 3/100 1/2

最後のMirrorの値コンストラクタには

Mirror (l a)

と書かれている。これは、Mirror値コンストラクタは、「l a」型の値を引数にとるということである。その具体的な値は例えば「Tall 1 3/100 1/2」であるので、

layoutHook = Mirror $ Tall 1 3/100 1/2

と書く事が出来る。

さて、これらの書き方だと、単独のレイアウトしか使うことが出来ない。実際には、M-Spaceでこれら三つのレイアウトが切り替わったりするのだ。

どうするのか??
というより、defaultConfigではどう定義されているかいつものリファレンスからソースを覗き見てみよう。
http://xmonad.org/xmonad-docs/xmonad/XMonad-Config.html


layout = tiled ||| Mirror tiled ||| Full
 where
  tiled = Tall nmaster delta ratio
  nmaster = 1
  ratio = 1/2
  delta = 3/100


このlayoutがlayoutHookに定義される。

注目点は(|||)演算子だ。この演算子の説明は、XMonad.Layoutのリファレンスに見つけることができる。


(|||) :: (LayoutClass l a, LayoutClass r a) =>
       l a -> r a -> Choose l r a


(|||)は右結合演算子なので分解して、まず「Mirror tiled ||| Full」を型で表現してみると次のようになる。

 (Mirror Tall) a -> Full a -> Choose (Mirror Tall) Full a

(Mirror Tall)が型の部分適用で型コンストラクタとなり型引数「l」に対応だ。

そして、同リファレンスで紹介されている通り、「Choose」は型引数を3つとる型コンストラクタであることが、「Choose l r a」型の定義からわかる。
なので、「Choose (Mirror Tall) Full a」は「Choose (Mirror Tall) Full」を型コンストラクタ 、「a」を型引数として、分解でき、型変数を使った表現「l a」型に合致する。

更に、

(tiled ||| (Mirror tiled ||| Full))という式の型は

Tall a
-> (Choose (Mirror Tall) Full) a
-> Choose Tall (Choose (Mirror Tall) Full) a

と表される。
そしてこれもまた、型変数で表せば、「l a型」となっている。

この「l」というのは、任意だということは何となく知っていたと思うが、aを型引数としてとるために部分適用で表現された変幻自在の型コンストラクタだという驚愕の真実までは知らなかったと思う。

まぁ、実際には、(|||)の引数になるには、一定の制限(LayoutClass l a等)があるものの、レイアウト設定の仕組みは、型引数を一つとって具体型になる型を(|||)演算子でいくつでもつなげることが出来、しかも繋げた結果も同じ型だったりするので、その型の値をそのままlayoutHookに定義してあげれば良いようだ。


さて、ここであらためて、リファレンスで、defaultConfigの型シグニチャをみると

defaultConfig :: XConfig (Choose Tall (Choose (Mirror Tall) Full))

となっている。

このメモを読む前ならきっと見て見ぬふりをしていただろう。

しかし、今ならそこに書いてある意味がなんとなくわかったりするんじゃなかろうか?
というより、その文字の並びは上記で既に見覚えがある。

defaultConfigはXConfig l型の実際の値である。なので、layoutHookフィールドにも実際の値が定義される。ということは「l」の部分にもその値に基づく具体的な型が表れてくるのだ。

その実際の型は長ったらしいけれども、単なる一つの型引数をとる型コンストラクタのことなのだ。

2013年12月3日火曜日

xmonadとHaskell(その12: キーカスタマイズ)

前回は、xmonadのキーバインドの仕組みについて、defaultConfig のkeysフィールドを参考に見ながらメモしてみた。

今回は更に続けて、haskellでのキーボタンを表現する具体的な値に付いてメモ。

前回も見たが、一つ目のハッシュ
((modMask .|. shiftMask, xK_Return), spawn $ XMonad.terminal conf)

(modMask .|. shiftMask, xK_Return)が、キーボタンの組み合わせを表現している。
タプルの第一要素が修飾キー(winキーとかctrlキーとか)で、第二要素がキーシンボルの組み合わせだ。

修飾キー、キーシンボルで使える値は、Graphics.X11.Typesモジュールの中で定義されている。リファレンスは以下のページ。
http://hackage.haskell.org/package/X11-1.4.5/docs/Graphics-X11-Types.html

XConfig l型で定義されたkeysフィールドの型シグニチャによれば、修飾キーの型は、「ButtonMask型」であるが、これは「Modifier型」の型シノニムになっている。また、defaultConfigでのkeys定義では「KeysMask型」(これもModifier型の型シノニム)になっている。というわけで、よくわからんけれど、とりあえず、以下の値を使えば不都合無いと思う。

shiftMask   シフトキー
controlMask  Ctrlキー 
mod1Mask   altキー 
mod4Mask     windowsキー
0      修飾キーを押さない

最後は、もし、修飾キーを押さないことを表現したいときは、数字の0でよい。

上の例では、(.|.)演算子が見られる。これは、複数の修飾キーの同時押しを表現したい時に使う。なので、上の例はmodキーとシフトキーの同時押しを表している。

(.|.)演算子は、Data.Bitsモジュールにあるビット演算子で、ORを表している。
http://hackage.haskell.org/package/base-4.6.0.1/docs/Data-Bits.html
一般に、論理演算は、「AND」が「&&」で、「OR」が「||」だったりする。そこで、このビット演算はそのビットを「.(ドット)」に見立てて、「.&.」や「.|.」にしたのかな?と思うと面白い。

Modifier型の実態はCUInt型の符号なし整数であり、ソースを覗いて定義されている定数を見ると、ビット表現で同時押しを認識しているのがわかる。

さて、「mod1Maskとmod4Maskはネットの説明でも見かけるんだけど、それ以外の2とか3とかってなに??」と興味が沸いているかもしれない。そういうときは、まずは、以下のページをざっとみて、それからxmodmapを色々と調べてみると良いかも。
http://x68000.q-e-d.net/~68user/unix/pickup?xmodmap

次に、キーシンボルの方も上のリファレンスに値が羅列されている。
KeySym型の値は「xK_」で始まる名前になっている。特殊そうなキーもこのリファレンスで探せると思う。また、コンフィグアーカイブにある以下の人のxmonad.hsは、凄く沢山のキーバインドを定義しているので、キーの組み合わせ表現の参考になると思う。
http://www.haskell.org/haskellwiki/Xmonad/Config_archive/Nnoell%27s_xmonad.hs

キーカスタマイズ

今回は、具体的なキーカスタマイズの方法をメモする。
そして、カスタマイズする内容の例として、以下の二つを取り上げる。


xmonad.hsの再読み込みと再起動

前回紹介したように、デフォルトの設定のままのm-qのキー操作では、xmonadの再起動によってdzenが多重起動される場合がある。

defaultConfigで設定されるm-qの内容を見る(長いので折り返している)と

spawn "if type xmonad; \
     then xmonad --recompile && xmonad --restart; \
     else xmessage xmonad not in \\$PATH: \"$PATH\"; fi"

の様になっている。

spawn関数なので、その戻り値は、引数文字列をshellに渡して実行されるXアクションである。これにはエラー用の処理が施されているが、主となる部分は、再起動用のコマンドであり、それだけを取り出すと以下の通り。

spawn "xmonad --recompile && xmonad --restart"

これに、dzenの重複を避けるため、一旦、dzenプロセスを切るようにすることとして以下の様な、Xアクションにする。

spawn "killall dzen2; xmonad --recompile && xmonad --restart"

そして、これを、m-qに割り当てるカスタマイズをする。

xmonadのヘルプ

さて、もうひとつ。defaultConfigのkeysフィールドの中で

((modMask .|. shiftMask, xK_slash ),
  spawn ("echo \"" ++ help ++ "\" | xmessage -file -"))

及び

((modMask, xK_question ),
  spawn ("echo \"" ++ help ++ "\" | xmessage -file -"))

と定義されている部分がある。

これは、「M-shift-/」また「M-?」で、ヘルプ(キーバインド一覧)が表示されるものである(知ってた?)

ヘルプの内容は「help」という名前で定義された文字列であり、これはdefaultConfigのソース内の最後のほうで定義されている。そしてこれは、単なる文字列で設定されているだけなので、キーカスタマイズしても自動的にヘルプの内容が変わるわけではない。

というわけで、この機能を削除しても特段、支障はないので、このキーをキーバインドから削除するための練習用に使ってみる。

以上の2点である。

そして、巷のxmonad.hsをみると、キーカスタマイズの方法には幾つかの異なる方法があることがわかる。

そこで、今回のメモでは、巷のカスタマイズが読み解けるように、上記2点のカスタマイズを実現するための複数の方法を紹介する。

defaultConfigベースのコピペ型

xmonadでキーバインドのカスタマイズする時、一番根本的な方法はXConfig l型のkeysフィールドを直接書いて、全てのキー定義をしてしまうことである。そして、その具体的な方法の一つは、defaultConfigのソースからkeysフィールドの定義すべてをコピペし、必要な部分を書き換えたり追加したりする方法だ。

「一部分のカスタマイズのために、キーバインド全部の定義をしなおすのは、大げさだ!」とも思う。しかし、実際は「コピペ」なので、事実上の手間はかからない。
また、必然的に知らないキーバインドが無くなり、また、初心者の時には書式に親しむことにもなるため選択肢としてはアリだと思ったりする。

ソースの中では、keysの定義をコピペして、その名前をmykeysとした。
設定の変更については、直接該当項目を書き換え、削除は、単にその項目を削除する。

XMonad.Util.CustomKeysモジュールの利用

xmonadのモジュールの中には、キーカスタマイズを簡単にするための関数を配布しているものがある。その一つがXMonad.Util.CustomKeysモジュールだ。

リファレンスは以下のページ
http://xmonad.org/xmonad-docs/xmonad-contrib/XMonad-Util-CustomKeys.html

このモジュールを使ったカスタマイズは、主としてcustomKeys関数を使って行う。

この関数は、defaultConfigに設定されているキーバインドをデフォルト設定とした上で、そこから削除したいキー操作のリストを返す関数と、追加したいキー操作、Xアクションの組み合わせのリストを返す関数を引数として与えると、XConfig l型のkeysフィールドに定義すべき関数を返してくれるものである。(また、customKeysFrom関数をつかえば、カスタマイズされたXConfig l型データがある場合それをデフォルト設定とすることも出来る)

ここでまず、customKey関数の型シグニチャを読んでみよう。

関数の型シグニチャの中で括弧があると、その部分はひとまとまりの関数、すなわち、関数を引数としてとることを表している。なので、この関数は、二つの関数を引数としてとる。

そして、その続きの部分をよくみると、keysに設定するための関数、すなわち、前回から何度も見ている

 XConfig Layout -> Map (keyMask, KeySym) (X ())

となっている。すなわち、二つの関数を引数にとって、keysに設定する関数を返してくれることがわかる。

矢印を見る目が変わってくると、カリー化された関数の部分適用が実際に活用されている部分に気付くことができるようになり、「おおおおお~!部分適用ってこうやって使われるのか!!」って興奮できるかも。

あらためて各引数をみてみる。

削除する関数部分

一つ目の引数は、いらないキーバインドのリストを返す関数である。

(XConfig Layout -> [(KeyMask, KeySym)])

削除したいキーボタンを、修飾キーとキーシンボルの組み合わせのタプルで表現し、そのリストを返す関数だ。

ここでは、「M-S-/」と「M-?」を削除する関数delkeysを作ってみる。
Modキーについては、引数として渡されるデータから読み出すためレコード構文型のパターンマッチを使う。下の例では「modkey」という名前が仮引数になる。

delkeys XConfig {modMask = modkey} = [(modkey .|. shiftMask, xK_slash)
                                     ,(modkey, xK_question)]

追加する関数部分

二つ目の引数は、追加したいキーバインドのリストを返す関数である。

(XConfig Layout -> [((KeyMask, KeySym), X())])

一つ目の関数と違って、キーボタンの組み合わせと、動作させるX()型のデータとのタプルをリストにしたものだ

今度は、addkeys関数と名前をつけた関数を作って、m-qの内容を変更する。ここで登録する関数は、追加だけでなく、既にキー登録されているものはその内容が上書きされるので、内容の変更もこの関数登録で行えばOK。
引数のパターンマッチはdelkeys関数の定義と同様。

addkeys XConfig {modMask = modkey} =
    [( (modkey, xK_q)
    , spawn "killall dzen2; xmonad --recompile && xmonad --restart")]


以上をまとめた形でxmonad.hsに組み入れると以下のようになる。

keysフィールドの定義をcustomKeys関数の部分適用で行い、また、上記で説明した、引数となる関数「delkeys」,「addkeys」をmain関数にローカルな名前としてwhere構文を使ってみた。

defaultConfig{}の各フィールド値の定義については、短いものだと直接{}の中で定義してしまうこともあるし、設定ファイルによれば、わざわざ全部に「myなになに」と名前をつけて、別の場所で定義しているものもある。混乱しない限りは、あまり統一しすぎた形式にこだわらず、長ければ、名前をつけて外で定義する、短ければ中で直接書くって感じで、臨機応変に見やすさを選んでも良い感じ。where構文を使えばローカル定義になるので、名前の重複を気にしなくても良い。

さて、customKeys関数を使う方法は、先のdefaultConfigコピペ型と比較すると、設定変更を行う部分のみが設定ファイルに書かれることになり簡潔に表現できる。また、Map化自体は関数がしてくれるので、データの表現自体は組み合わせのリストでよい。

XMonad.Util.EZConfigモジュールの利用

まずはリファレンス
http://xmonad.org/xmonad-docs/xmonad-contrib/XMonad-Util-EZConfig.html

このモジュールを使ったカスタマイズの特徴として、キーボタン操作を"M-q"という風に一般的によくみられる文字列(リファレンスではemacsスタイルと書かれている)で表現することができるので、意味が分かりやすくなる。

もう一つの特徴は、このモジュールのカスタマイズ用関数の戻り値は、XConfig l型のkeysフィールドに定義する関数ではなく、keysフィールドが定義されたXConfig l型そのものを返す。

すなわち、ベースとなるXConfig l型データを受け取り、カスタマイズされたXConfig l型データを戻す関数だ。これは、xmonadでのカスタマイズで見受けられる設定のパターンのひとつだ。

例えば、(その4)でステータスバーを付けるためにdzen関数を使ったがこれも似たパターンだ。dzen関数は、Xアクションを返すが、その結果部分についてだけ見れば、引数で受け取ったXConfig l型データについて、dzen用のカスタマイズとしてフィールドを書き換えたXConfig l型データを返すものだ。

また、このモジュールでは、追加用(既にある場合は上書き)の関数と削除用の関数が別々に定義されているので、必要なものだけを使うことも出来る。

まず、このモジュールで主となる関数は以下の追加用の関数だ。

additionalKeysP :: XConfig l -> [(String, X ())] -> XConfig l

二つ目の引数に着目すると、文字列とXアクションがペアになったタプルのリストである。
この文字列部分が、キーボタン操作を表現する部分で、emacsスタイル、すなわち、"M-q"と表現することが出来る。

また、削除用の関数は以下のように、引数のリスト部分はキーボタン操作を表現した文字列のみだ。

removeKeysP :: XConfig l -> [String] -> XConfig l

尚、このモジュールにはキー操作部分の表現に従来通り(ButtonMask, KeySym)を使えるadditionalKeys関数とremoveKeys関数も定義されている。


さて、このモジュールの関数を使った典型的なキーカスタマイズ例は以下の通り。

巷のxmomad.hsで見受けられる中置関数化してXConfig l型データの後ろに引っ付けるやつだ。

ここで、このモジュールを使う場合の留意点を幾つかメモ。

emacsスタイルの表現

emacsスタイル表現の文字列について、まず、修飾キーの表現は以下の通り。

 M-   modキー
 C-   ctrlキー
 S-   シフトキー
 M1-  altキー
 M4-  windowsキー等(スーパーキー)
ここで、modキーというのは、引数となるXConfig l型でのmodMaskで定義されているキーだ。

次にキーシンボルはそのままの文字を書けば良い。但し、リターンキーやカーソルキーなど特殊なキーの表現については、<Return>や<KP_Up>という表現がなされるので、リファレンスからみつける。

更に、xmonadのキー操作においては、サブマップという仕組みを使うことで、連続したキー入力を受け付けることが可能になる。例えば、「Ctrl+x」と押した後に、「a」を押すというような操作方法だ。

そして、サブマップの機能は、additionalKeysP関数を使うと簡単に実現できる。
連続キー入力の表現の仕方は、emacsスタイルを使って、単に、続けて押したいキー操作とキー操作の間にスペースを置き、"C-x a"と表現する。

なお、このサブマップの仕組みは、キー操作だけの単純な問題ではなく、実際にはXアクションの中に更にキー操作を受け付けるXアクションを入れた、入れ子になった構造を利用していることを頭の片隅に置いておくとトラブルを避けやすいと思う。(XMonad.Actions.Submapを参照)

mkKeymap関数の利用

additionalKeysP関数等、上記でみた関数は、カスタマイズされたXConfig l型を返す関数だった。
しかし、実はこのモジュールにもXConfig l型データのkeysフィールドに設定する関数を作るのに便利な連想配列を返す関数も定義されている。それがmkKeymap関数だ。

mkKeymap :: XConfig l -> [(String, X ())] -> Map (KeyMask, KeySym) (X ())

型シグニチャをみると見慣れたMap型が戻り値となる関数だということがわかる。
そして、この関数はemacsスタイル表現で[(String, X ())]部分のキーバインドリストを作成し、これをkeysで読めるMapに変換してくれる関数なのだ。

ここで、keysフィールドの型は

 keys :: !(XConfig Layout -> Map (ButtonMask, KeySym) (X ()))


なので、この関数を使って次のようにすればkeysフィールドに設定する関数を定義できる。

  mykeys conf = mkKeymap conf [("M-a",hogehoge)
                              ,("M-b",hagehage)
                              ,...]

但し、この場合、キーバインドの追加削除でなく、keysそのもの、すなわち、全てのキーバインドの設定をしなければならない。

checkKeymap関数によるチェックの仕方

更に、このモジュールには、emacsスタイルの表現が間違っていないかどうか等をチェックしてくれる便利なユティリティ関数checkKeymapがある。

checkKeymap :: XConfig l -> [(String, a)] -> X ()

引数は、一つ目の引数にXConfig l型データ、二つ目の引数にキー操作、Xアクションのペアのリストである。なので、この関数によるチェックをする時には、additionalKeysPの引数で直接にキーバインドのリストを書くのでなく、別途名前を付けたリストとして定義しておくと便利だ。

戻り値のXアクションは、それが実行されると、emacsスタイルの表現がおかしくないか、キーバインドが重複していないかをチェックして、もし、おかしい場合にはxmessageアプリをポップアップしておかしな箇所を指摘してくれるものだ。

リファレンスによればこの関数を置く場所に相応しいのはXConfig l型データのstartupHookフィールドだと書かれ、以下のような例が書かれている。(mykeysがキーバインドのリスト)

myConfig =
  defaultConfig { .... 
                , startupHook = return () >> checkKeymap myConfig mykeys
                , ..... }

そして、注意書きとして「return () >>」を書いておきなさいと書かれているようなので(再帰定義の無限ループに落ちる?)、指示にしたがっておくほうが良さそうだ。

さて、巷のxmonad.hsを眺めるとキーコンフィグに関してはadditionalKeysP関数を利用し、checkKeymap関数でチェックを掛けているものが多くみられるように感じる。そこで、今回のメモのまとめとして、dzen呼び出しをした(その10)のxmonad.hsをベースに以下のようなxmonad.hsを例示してみる。


ghciで名前定義するときに使われる「let」。
実は、「let式」といわれるもので、「in」と対になっており、let式としての値は、in以下の評価値となる。

 ans = let a = 1
           b = 2
       in a + b

は、ans = 1 + 2 と同じだ。

例のmyConfigの定義は、c(configのc)でキーカスタマイズ以外のコンフィグを行い、inの部分で、cをベースにadditionarlKeysP等を使って、キーカスタマイズを織り込んだXConfig l型データを作っている。

これは、コンフィグアーカイブの一番初めに紹介されている/adamvo's xmonad.hsでそんな定義が使われている。初め、意味が分からなかったのだが、読めてみると凄くかっこよい定義に見えたので真似てみた。

myAddkeysの定義でXConfig l型を引数にしているのは、modキーのカスタム値は別として、他のXConfigのフィールド値を受け取って、Xアクションで活用することが出来るようにしておくため。(terminal名とか)

2013年11月25日月曜日

xmonadとHaskell(その11:キーバインドのしくみ)

xmonadでは、xmonad.hsを書き換えた場合、xmonadを再起動しなくても、デフォルトでは"mod-q"のキー操作で新しい設定に更新できる。

カスタマイズをする時に頻繁に使うキー操作だが、前回(その10)のカスタマイズをした後、このキー操作を行うと違和感を感じたりする人がいると思う。

更新のキー操作をするたびに、見た目では気が付きにくいが、実はステータスバーが重なって表示されていたりする。リロードする度にconkyのプロセスがどんどん増えてしまうのだ。

なので、"mod-q"で、単にxmonadのリロードを行うだけでなく、conkyのプロセスを終了する処理を加えるカスタマイズをメモしたいなと思う。

そのためには、まず、xmonadでのキー設定の仕組みに着目してみる。

いつものように、XConfig l型のリファレンスXConfig l型のリファレンスでコンストラクタをみる。
この中の、keysフィールドで、キーバインドの設定をすることとなる。

 keys :: !(XConfig Layout -> Map (ButtonMask, KeySym) (X ()))

ちょっとだけ見た目がややこしいので整理すると、keysに設定すべきものは矢印があるので、関数であることがわかる。
そして、落ち着いて分解すれば、この関数は、「XConfig Layout」型の値を引数としてとり、「Map (ButtonMask, KeySym) (X ())」型の値を返すような関数だと読める。

「XConfig Layout」型は、具体的に言えばdefaultConfigのような値であり、いつも見ているXConfig l型の具体的な値の事である。

で、問題は「Map (ButtonMask, KeySym) (X ())」型である。

タプルというデータ表現

haskellでは、データの表現の方法にタプルというものがある。

(1, 2)

("hoge", 2, True)

要素をカンマで区切り、丸括弧で囲んだものである。要素になるものの型は何でもよい。

ここで、データの表現で見慣れているリストと新しく見るタプルを比較して、その特徴をみることにする。

[1,2,3]

["hoge","hage","fuga"]

リストは型で表現すると「[a]」となり、いくつもの要素があっても全て同じ型である。そして、型からみれば、その要素の数はいくつでも構わなかったりする。

一方、タプルは、要素ごとに好きな型を取ることができ、上の例を型で表現すると

(Int, Int)

(String, Int, Bool)

である。そして、リストと違い要素の数は型の表現どおり固定されている。

ここで、問題の式を見ると、そこにタプルがある。

(ButtonMask, KeySym)

この部分がタプルだ。

タプルは見ての通り、何かの組み合わせを表現する時に便利。
このタプルは、ButtonMask型のデータとKeySym型のデータの組み合わせを表現している。

連想配列の型

さて、改めて「Map (ButtonMask, KeySym) (X ())」をみてみる。
複雑にみえるがまず、これは「」を表現しているということをしっかり認識すること。

これは、「Map k a」型と呼ばれる型であり、haskellで連想配列(ハッシュ)を表す型だ。具体的な値となるときには、kの部分にキーとなるものの型、aの部分に値となるものの型を書いてあらわす。

連想配列、連想記憶配列、ハッシュ、いろいろな風に呼ばれるが、何かのプログラム言語を触ったことがあればなんとなくイメージできるだろう。(連想配列のウィキペディア

簡単に言えば、名前をキーに、電話番号を値にみたいな感じでデータを管理する方法だ。
イメージ的には

("isono"    => "090-xxxx-0000",
 "fuguta"    => "090-xxxx-1111",
 "namino" => "090-xxxx-2222")

みたいな感じ。
電話番号を探すのに人の名前で探せる。まさに、連想だ。

haskellの話や本のなかで、map,zip,fmap等々にたようなのが出てきて(これらはまったくMapとは関係ない関数の話)初めの頃は混乱しがちになるので、整理しておくべき。

とりあえず、haskellで「Map」といえば、「連想配列」を表すデータのこと。
同時に、大文字のMで始まっていることに意識しすれば、型の話だと認識しやすい。

で、「Map (ButtonMask, KeySym) (X ())」のキーは、上でみたタプルで表現された型であり、(ButtonMask,KeySym)型である。そして、値は、X()型、すなわち、前にみたIOアクションのように、xmonad上で「実行される何か」の型である。
まさに、キー操作と動作の組み合わせで、キーバインドそのものだ。

以上をまとめると、

 keys :: !(XConfig Layout -> Map (ButtonMask, KeySym) (X ()))

というのは、XConfig Layout型のデータを引数にあたえると、キーバーインドの連想配列を返す関数を登録することになる。

ここで、キーコンフィグの話を進める前に、もう少し連想配列の話。

Data.Mapという連想配列のモジュール

上で出てきたのは、連想配列の型の表現の話だった。
今度は、実際に連想配列のデータを作ってみる。

まずは、そのために下準備が必要だ。
連想配列であるMapは、haskellのData.Mapというモジュールで定義されているのでそのモジュールをインポートする必要がある。

ここで、巷のxmonad.hsをみても、ファイルの初めに沢山のモジュールがインポートされているが、Data.Mapのモジュールのインポートは単純な

import Data.Map

ではなく、大概以下の様になっている。

import qualified Data.Map as M

これは、複数のモジュールの間で同じ関数名が使われている場合に混乱するので、「この関数を使うときには必ず、その旨を明記します」という約束書きである。

具体的には、Data.MapモジュールにあるfromList関数を使いたいとしたら、必ず、「M.fromList」と書く。先の「as M」の部分は短縮形を使うという書き方であり

import qualified Data.Map

とだけ書いた場合には、Data.Map.fromListという書き方になる。

xmonad.hsでみられる他の重複を避ける書式もみておく。

import Data.List ((\\), find)
これは、括弧内で示した関数のみをインポートする場合。

import XMonad.Hooks.DynamicLog hiding(ppWindowSet)
これは、上と逆でその関数のみインポートしない場合。

さて、xmonadのモジュールはXMonadではじまり、そのリファレンスのページは、XMonadのドキュメントのページからみれるが、Data.MapはXMonadのモジュールではない。
そんな時は、とりあえず、HoogleのページにData.Mapとか入れて検索してみるとよさそう。

連想配列を作る

連想配列データは次の関数で作ることが出来る。

 fromList :: Ord k => [(k, a)] -> Map k a

キーと値の組み合わせをタプルで表し、それをリストにしたものを引数として渡す。すなわち、fromList [(キー, 値),(キー, 値),(キー, 値),...]見たいな感じ。

厳密に言えば、タプルのリスト、すなわち、[(キー, 値),(キー, 値),(キー, 値),...]、そのものが、連想配列なのではない。fromListからの戻り値が連想配列、つまり、それがMapと呼ばれるものだ。

上に書いた名前と電話番号のイメージを具体的なプログラムにしてみると次の様になる。


defaultConfigのkeysフィールド

XConfig l型の具体的なデフォルトデータ、おなじみのdefaultConfigのkeysフィールドを見てみよう。

http://xmonad.org/xmonad-docs/xmonad/XMonad-Config.html

このページの右隅にソースへのリンクがある。

該当部分の出始めの数行をコメント抜きで引用して、後を省略すると次のような感じになる。


戻り値になる連想配列が、上で話したM.fromListを使って作られているのが見て取れる。

さて、keysフィールドに設定すべきものは関数である。
キーバインドの設定をするなら、連想配列でデータを表現できるので、関数ではなく単なるMap k a型の連想配列データそのものでも良い様な気がするが、関数なのだ。

これは、xmonadの設定のパターンの一つであり、その理由は、連想配列を作るための一つ目のタプルを見てみるとすぐに分かる。

((modMask .|. shiftMask, xK_Return), spawn $ XMonad.terminal conf)

この連想配列は、modキー、シフトキー、リターンキーの同時押しで、ターミナルウインドウを起動することを示している。

例えば、modキーはwinキーに決まっていて、ターミナルはxtermに決まっているのなら、

((mod4Mask .|. shiftMask, xK_Return), spawn "xterm")

として、固定的な連想配列だけを作れば良い。

しかし、ここで、思い出して欲しいのは、最小限のxmonad.hs


つまり、modキーは、XConfig l型データのフィールドの一つ、modMaskフィールドでカスタマイズ可能だ。また、ターミナルウインドウもterminalでカスタマイズ可能だ。

なので、xmonadの意図に沿うなら、カスタマイズされたXConfig l型データを通じて、ターミナルウインド起動の時には、modキーが何に設定されているか、ターミナルウインドに何を使うのかを知る必要がある。

ここであらためて、keys関数の引数は何なのかというとXConfig Layout型である。

これは実際にxmonad関数に渡される値であり、xmonadはキーバインドを行う時に、xmonad関数に引数として渡された今私たちがカスタマイズしているXConfig l型データを、keys関数に引数として渡し、その結果得られる連想配列でキーマッピングを行っている。

わざわざ関数になっているのは、連想配列作成時に、カスタマイズデータを織り込むことができるようにする仕組みなのだ。

たぶん、他のプログラミング言語だと、グローバルな変数名で受け渡しする話なのかもしれないけれど、こういうところが、haskellの関数型言語っぽい作法なのに違いない。

実は、設定データを引数で渡すという方法は、あちこちで見られるパターンだ。
(その10)で紹介したxmonad.hsの中でも、「my_dzen_PP h = ...」と定義している部分は、まさにこれと似ている。PP型のデータ自体は、固定的だが、書き出しハンドルは動的なので、その部分のデータを知る必要がある。そこで、ハンドル(h)を引数として受け取る関数を作っている。

パターンマッチ

 keys conf@(XConfig {XMonad.modMask = modMask})

上は、kesy関数の定義の一部だ。
「keys」が関数名で、「conf@(XConfig {XMonad.modMask = modMask})」が引数。

haskellの関数定義においては、引数にパターンマッチとよばれる仕組みがある。

例えば(その2)で定義したデータ型

ここで、このHumanデータ型を引数にとって挨拶するhello関数を作ってみる。型シグニチャは以下の通り。
(値コンストラクタがOkamaの場合のみ。nameフィールドから名前を取り出してその名前に挨拶する文字列を作る。)

hello :: Human -> String

この関数を色々な仮引数で定義して、代数データ型のパターンマッチに親しんでみる。
hello dareka としたときの結果はすべて"hello, tubasa"だ。



・その1

 hello h = "hello, " ++ (name h)

Human型そのままを仮引数とするパターン。名前の取り出しは、レコード構文で自動的に作られるフィールド名の関数で取り出す。

・その2

 hello (Okama n i) = "hello, " ++ n

値コンストラクタに引数を与える形のパターンマッチのパターン。仮引数nが、名前を表すものとなる。フィールドは引数の位置によって識別されるので、値コンストラクタの引数は全てを仮引数として表現しないとダメ。

・その3

 hello (Okama{ name = n}) = "hello, " ++ n

値コンストラクタをレコード構文の形でパターンマッチするパターン。仮引数nが名前を表すものになる。フィールド名で特定できるので、必要なものだけ仮引数として表現。

そして、最後に「asパターン」と呼ばれるパターンがある。1と3の合成だ。

hello h@(Okama{name = n}) = "hello, " ++ n ++ show (saikyou h)  

@の前の部分がその型全体を表現する仮引数(その1)、後ろの部分が値コンストラクタのパターンマッチ(その3、または2でもOK)
このhelloは、名前の後ろにさいきょう度がつく。

というわけで、keys関数はこのasパターンであり、仮引数は赤字の部分

 keys conf@(XConfig {XMonad.modMask = modMask}) 

confがXConfig Layout型の値全体を、modMaskがXConfig Layout型のmodMaskフィールドで定義された値を指している。

このmodMaskっていう仮引数名の付け方は紛らわしいことこの上ない。
フィールド名称と同じであるし、具体的には、連想配列定義の以下の赤字、青字の部分

 ((modMask .|. shiftMask, xK_Return), spawn $ XMonad.terminal conf)

がこの引数なんだけど、一見すると、「modMask」自体が「ButtonMask型」に定義されている、値なのかと見間違えそう。

ちなみに、うしろのconfが値全体のconfで、「XMonad.terminal conf」がお気に入りのターミナルを表す例のアレだ。




2013年11月20日水曜日

xmonadとHaskell(その10:ManageDocks)

(その4)辺りで「ステータスバーを付けてみる」と言いつつダラダラとメモしてきたので、いい加減ここらで具体例。

いつもの見慣れたスクリーンショットの様なステータスバーを付ける。


xmonadでステータスバーを表示するには外部アプリを利用する。
ここでは、表示用に「dzen」、情報取得用に「conky」。

ステータスバーは画面の上部に表示するが、このステータスバーは左右二つのdzenから構成されている。左側がxmonadからの出力を表示しているdzen、右側がconkyからの出力を表示しているdzen。


まずは、xmonad.hs
外部アプリの起動は、do構文を使ってmainで行う。

8行目でxmonadからの出力を表示するdzenを起動。spawnPipeを使ってハンドルを得る。

一方、9行目はconkyからの出力を表示するdzen。conky出力をパイプでdzenに繋いだシェルコマンドをそのまま実行するので、ハンドル(結果)は必要ない。そこで、これの実行については、spawnPipeではなく、spawnを使っている。

spawnPipe等に渡す引数はシェルで実行する文字列をそのまま文字列として渡せばよい。

dzenコマンドの引数の概略(詳細はhttps://github.com/robm/dzen

 -x  左端表示位置
 -w  長さ
 -ta 内容の表示位置、右寄せならr、左寄せならl

以下のものは左右のdzenともに共通なので18行目でdzen_styleとしてまとめて定義

 -h ステータスバーの高さ
 -fg フォアグランド色
 -bg バックグラウンド色
 -fn フォント名

xft用(?)のフォント名は「fc-list」コマンドを使ってシステムで使えるフォント名を調べる。サイズの指定は、そのフォント名にコロン「:」で繋げて「size=10」とかする。
また、普通のフォントの名前なら「xfontsel」コマンドとか使って調べられる。

そして、conky(http://conky.sourceforge.net/)
ネットで検索すると、デスクトップ上にグラフィカルに表示されたかっこ良いシステムモニタの画像がたくさん出てくる。

しかし、ここでは、この表示能力は使わず、その情報収集の機能のみを利用する。
上記xmonad.hsの9行目 "conky -c ~/.xmonad/conky_dzen_laptop" として呼び出している。
このconky_dzen_laptopの内容は以下の通り。
尚、/neko/home/.xmonad/icon/ディレクトリ内のアイコンファイルは、もともとdzenのサイトで配布されていたみたいなんだけれど、現在の配布は不明。しかし、githubで管理されているxmonad.hsの中によく入っているので適当に検索すると拾える。

さて、今までメモしてきたことは、dzenというアプリを画面上に表示してそこに情報を表示するということでしかなかった。

しかし、ステータスバーとして機能するためには、その表示用アプリであるdzen上にウインドウが重ならず常に見えていることが必要だ。

それを実現するための機能がxmonadには用意されている。

XMonad.Hooks.ManageDocksモジュール

このモジュールには3つの便利な機能が用意されている。

manageDocks

xmonadには、manageHookという機能がある。
簡単に言えばこれは、xmonad上でアプリケーションが実行されて新しいウインドウが開いた時、そのウインドウをどんなふうに扱うかを操作するための仕組みだ。
例えば、「firefoxが起動されたら、そのウインドウをwebというワークスペースに移動させる。」とか、「gimpが起動された時には、フローティング配置(タイリングしない)する」とかである。

この機能の設定は、XConfig l型データのmanageHookフィールドで行う。

で、ステータスバー的に振る舞わせたいウインドウ、すなわち、他のウインドウのようにタイリング表示されたり、特定のワークスペースだけに表示されたりするのではなく、常に一定の同じ場所で表示され続けるウインドウとして扱いたい旨の指定を簡単にしてくれるのが、このモジュールのなかの「manageDocks」という値だ。

上記xmonad.hsの15行目

manageHook = manageHook defaultConfig <+> manageDocks

XConfig l型データのコンストラクタをみるとmanageHookフィールドに設定するものはManageHook型のデータである。

ManageHook型データそのものの具体例については、別の機会にメモするとして、上の式の意味をみてみる。

式の読み方はだいたい、「manageHook defaultConfig」と「manageDocks」を演算子「<+>」で繋いだものであるという感じ。

まず、「manageHook defaultConfig」は、(その3)でもメモした「レコード構文で定義したフィールド名(項目名)は、その型のデータからそのフィールドの値を取り出す関数になる」というアレである。
つまり、defaultConfigとして定義されている値のmanageHook項目に入っている値を取り出している。そして、当然その値の型はManageHook型だ。

次に、「manageDocks」は、上で書いた通りステータスバー等の制御のためのデータが入ったManageHook型の値だ。

そして、演算子「<+>」はManageHook型の値を結合してひとつのManageHook型の値にするものである。

なので、15行目では、defaultConfigで定義されているmanageHookの内容に、manageDocksの内容を追加したものになる。

avoidStruts

xmonadには、layoutHookという機能がある。さまざまなタイリング、フルスクリーン、タブ等など。
おなじみのレイアウトであるが、どんなレイアウトを使うかは、XConfig l型データのlayoutHookフィールドで行う。

ステータスバーを使用する場合、ステータスバーの部分に他のウインドウが重ならないように配置を制御しなければならないが、このレイアウト制御をしてくれるのが「avoidStruts」だ。

上記xmonad.hsの14行目

layoutHook = avoidStruts $ layoutHook defaultConfig

layoutHookに定義すべきデータは(l window)型で、この型自体については、また別機会にメモ。
とりあえず、式の構造をみてみると、「avoidStruts関数」に「layoutHook defaultConfig」を引数として渡した戻り値となっている。

まず、「layoutHook defaultConfig」は先と同じで、layoutHookが関数であり、defaultConfigで定義されたlayoutHookフィールドの値が戻り値となる。

そして、avoidStrutsは関数であり、レイアウト制御のデータをとって、それに、ステータスバー用の隙間を開ける加工をしたレイアウト制御データを返すというものである。

docksEventHook

リファレンスの説明から具体的なイメージがよくわからないので、今回のxmonad.hsには含めてないが、使い方はmanageDocksと同じパターンで、XConfig l型データのhandleEventHookフィールドに定義すればよさげ。

handleEventHook = handleEventHook defaultConfig <+> docksEventHook

みたいにすれば、デフォルトのイベントフックにこの機能が追加される。


レコード構文を使った設定
初心者のころは、レコード構文のフィールド名が、データ型からそのフィールドの値を取り出す関数である、すなわち、同じ単語を使ってるけど意味が違うってのを知らないので、xmonad.hsが読めなかったりする。

しかし、これを理解して、改めて実際のxmonad.hsでの設定をみるとレコード構文の便利さが実感できる。

なんにせよ、xmonadの設定のあちこちでこれに似たパターンは使われるので

my_data = hoge_default { field_a = add_func $ field_a hoge_default }

みたいな形の理解は必須だ。

上下にステータスバー

スクリーンショットでもよく見られる上下のステータスバーは簡単にできる。
dzenコマンドに渡す引数の配置パラメータを少し変えて、左側dzenを上に、右側dzenを下にする。

8行目、-w 400をはずす

left_bar <- spawnPipe $ "dzen2 -x 0 -ta l " ++ dzen_style

9行目 -x 400 を変更して、-y 582を追加

...省略 | dzen2 -x 0 -y 582 -ta r " ++ dzen_style



しかし、S101はもともと画面の高さが低いので、両方のステータスバーを常に表示するとメインの表示領域が少なくなるから嫌だ!と考えたとしよう。

そんな時に役に立ちそうなのが、同じもジュール内にある「avoidStrutsOn」関数だ。上下左右のステータスバー領域を個別に設定できる。

avoidStrutsOn関数の一つ目の引数は、ステータスバーのための余白を開けたい方向を示す値(上はU、下はD、左はL、右はR)を要素としたリストとなり、二つ目の引数でレイアウトデータを渡す。

なので、上下にステータスバーがあるが上だけ常に表示したい場合は、以下の通り。

layoutHook = avoidStrutsOn [U] $ layoutHook defaultConfig



この方向を示すデータは、Direction2D型の値で、UとかDとかはいわゆる値コンストラクタだ(TrueとかFalseみたいなもの)。

Direction1D型のNext、Prevとともにxmonad.hsのカスタマイズのなかでよく出てくるのでリファレンスをチェックして頭の隅においておこう。
XMonad.Util.Typesモジュール

実際のところ

実際のところ、dzemanageDocksやdocksEventHookが効果出てるのかとかわからない。
まぁ、よそのxmonad.hsにはたいがい書いてあるので、おまじない的につけておけばOKなのかな。

それより、dzenの位置が画面の端から離れてると、avoidStrutsが効かないようなので注意。例えば、上で紹介した上下バージョンのステータスバーで下側のdzenの位置を -y 580とかするとavoidStrutsしても余白を作ってくれなくなる。

ついでに$演算子

$演算子は、割とどこでも説明されている「括弧を少なくするための記号」的なものだ。
解釈は、$の位置から最後までを括弧で囲んだのと同じ。なんて言う風に紹介されている。

つまり、上で紹介した

layoutHook = avoidStrutsOn [U] $ layoutHook defaultConfig

は、

layoutHook = avoidStrutsOn [U]  ( layoutHook defaultConfig )

となる。まぁ、そのおとりで簡単!

しかし、ここで、脱初心者的に$演算子の型シグニチャをみる。

($) ::  (a -> b) -> a -> b

まずは、$の左側(一つ目の引数)は関数で、右側(二つ目の引数)は「その関数に与える引数」が引数になるってこと。
そして、この演算子は右結合であるとともに、優先順位が最低なのだ。

すなわち、優先順位が最低ということは、必ず$記号が区切りとなるし、右結合なので、$のより右側が先に扱われるようになる。

だから、

a b c $ d e  $ f g $ h i

は、

a b c $(d e $(f g $(h i)))

になるとか聞くと、「へ~~~」ってなる。

ちなみに「$の位置から最後までを括弧で囲んだのと同じ。」という解釈は上手くいかなくて困ることもあるかもしれない。

例えば合成関数のときだ。

a . b . c $ d e

は、

a . b . c (d e)

という意味ではなかったりする。

何が違うかというと、(.)は右結合で、まずcを見ることになるが、その時、.演算子より空白結びつきが強いので

a . b . (c (d e))

となり、意図したのと違うものとなる。

実際には、$演算子を使うとその記号部分で区切られる(結合が弱い)ことになるので、あえて括弧を付けるなら

(a . b . c) (d e)

という感じになる。

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のソース