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名とか)