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))



0 件のコメント:

コメントを投稿