Post Page Advertisement [Top]

confighaskellxmonad.hs

巷で見かけるxmonad.hsのお洒落な書き方

xmonad.hsの基本形は、モジュールのインポートを省略すると以下のようになっています。


main = xmonad def


xmonad.hsは、haskellのプログラムです。 このxmonad.hsにかかれていることをプログラムとしてあらためて見返してみると、とても単純なものです。 xmonad関数にdefという引数を渡し、 その結果として得られるアクションがmainで実行されています。


もし、あなたがまだ、 haskellでは、関数の引数を表すのにカッコを書かないということを知らない場合、 「haskellの関数について」に目を通しましょう。


さて、ここで、「def」というものがxmonadの引数に渡されていますが、 xmonadのモジュールでは、よく使う型のデフォルトのデータをdefという名前で定義してくれています。 つまり、このxmonad関数に渡している「def」は、xmonadモジュールが事前に準備しているxmonad関数が受け取ることの出来るXConfig l型データのデフォルト値を表しており、 上述の基本コードは、xmonad関数にxmonadモジュールが用意したデフォルト値を渡してxmonadを起動していることになります。 (古いxmonad.hsでは、「defaultConfig」として書かれていますが、今は非推奨になっています。お洒落に今風に「def」を使いましょう!)


そして実は、xmonadのカスタマイズというのは、このxmonad関数に渡すXConfig l型データを自分好みに定義する事に他なりません。


defをレコード構文で書換える

では、具体的にどのように「自分好みの定義」をするのか、 xmonadのもっとも基本的なカスタマイズを例に見てみましょう。


main = xmonad def {terminal = "kitty",
                   modMask   = mod4Mask}


このカスタマイズでは、 xmonadで呼び出されるターミナルをお気に入りのkittyに変更し、 また、Modキーをwindowsキーに変更しています。


これは、haskellの「レコード構文」という書式で記述されています。 レコード構文を使うと、あるデータの一部の内容のみを書き換えた新しいデータを簡単に作ることが出来ます。 上の例では、defというデフォルト定義されたデータを基にして、 「terminal」と「modMask」の内容だけを自分の好きなものに書き換え、あとはデフォルト値と同じ内容の新しいデータを作成し、 これをxmonad関数の引数として渡しています。


このコードの形は、あらゆる意味でカスタマイズの基本形になっています。 本来、haskellで、或るデータを作成する場合、値コンストラクタを使います。 例えば、XConfig l型データは、XConfigという値コンストラクタに18から19の適切な引数(バージョンで異なる)を与えることで作成できます。 しかし、xmonadの設定においては、このような値コンストラクタを使わず、 用意されているdefに基づいてレコード構文で必要な部分を書換える方法を取ります。


このように、haskellのレコード構文を理解することで、 xmonad.hsの理解が大きく進みます。 xmonadの設定に興味が有る場合、haskellのレコード構文に関する知識は最低限必須になります。 全く聞いたことがないという人は、まず、「レコード構文について」を参照してみましょう。


また、XConfig l型はどんなデータなのか、defではどんなふうにデフォルト値が定義されているのか、気になるところだと思います。 まず、型のリファレンスについては「XConfig l型」を参照してみましょう。 難しくてよくわからなかったとしても、どのような項目が有るのか眺めてみてください。 その中には、見覚えのあるterminalやlayoutHookという項目名も発見できるはずです。 defについては、「XMonad.Configのソース」を参考にしてみましょう。 別にこれらについて、熟読して精通する必要はありません。 それらが今後必要なときに、リファレンスがあるということを知っておくだけで十分です。 ここを見ると、なんの項目には何の型の値を定義するのかということを知ることが出来ます。


基本的な仕組みがわかったところで、 xmonad関数に渡すdefをレコード構文を使って書き換え始めると、 だんだん、不便なことに気づき始めるはずです。


まず、レコード構文の中がコードで一杯になって、ごちゃごちゃしてくることです。


次に、これを避けるために、変数名つかって、その変数の中身自体を、 レコード構文の外で定義したりするようになるかもしれません。 しかし、これもコードが散らばってる感が漂ってきます。


そんな感じのxmonad.hsなのですが、 世の中の他人のxmonad.hsをみていると、 xmonad関数と、defとの間に何か色々と入っているコードがあったりしませんか?


実は、それがクールな解決法で、xmonad.hsの定番の書き方なのです。


defを関数で書換える

xmonad.hsは、単なる「設定ファイル」ではなくて、haskellのプログラムコードです。 ですから、xmonad.hsの中に、XConfig l型データを受け取ってXConfig l型データを返す自作の関数を定義する事が普通に出来ます。 つまり、以下のようなコードが簡単にかけるのです。 「関数を自作」しているといっても、レコード構文を利用しただけの単純な関数の定義なので、直感的にも把握できる程度のものです。


main = xmonad 
        $ myCustmA
        $ myCustmB
        $ def

-- ターミナルにkittyを使うXConfig l型データを作成する関数の定義
myCustmA conf = conf {terminal = "kitty"}

-- ModキーをwindowsキーにするXConfig l型データを作成する関数の定義
myCustmA conf = conf {modMask = mod1Mask}



そもそも「$ってなんだっけ?」的な感じならば、 「haskellの関数について」をまた参照しましょう。 xmonad関数に渡される引数は、別の関数を適用した結果の入れ子になっています。 単純なカッコを使って書けば次の通り。


main = xmonad (myCustmA (myCustmB def))


つまりは、myCustmBの結果をmyCustmAの引数として、その結果をxmonadの引数として渡していく流れになっています。 そして、各々の関数では、XConfig l型データの中で書換えるべき部分のみを書換え、その結果を次の関数へ渡していくようにしてあるのです。


実際に、xmonadの設定をdefに対する直接のレコード構文のみで行っていると、 そこにコードが集中しすぎて、見た目がすごく重くなり、見通しが悪くなります。 また、上記例のように単純なものならばよいのですが、XConfig l型の複数項目を書換えるような場合で、 他にもそのような処理があったとしたら、どれとどれが関連するのか直ぐに全くわからなくなってしまいます。


そこで、処理ごとに関数を作って、そのなかでコードを完結させると色々とわかりやすくなりますし、 また、そのカスタマイズの適用、非適用も、その関数を適用している部分の1行をコメントアウトしたりするだけで切り替えが簡単にできるようになります。


ここでは、単純な関数を自作していますが、 xmonad-contribモジュールの中には、このようなXConfig l型データを受け取って、XConfig l型データを返す関数が幾つも用意されています。


このように、直接defをレコード構文で書換えるのではなくて、 書き換え処理を関数化して処理を見やすくまとめるというのが、xmonad.hsのわかりやすいクールな書き方になっているというわけです。


クールな実例

巷でよく見る実物を見てみましょう。


docks関数

main = xmonad 
        $ docks
        $ def


あなたのxmonad.hsにdocks関数が入っていませんか? docks関数のコードを見てみましょう。


XMonad.Hooks.ManageDocks.html#docks


docks :: XConfig a -> XConfig a
docks c = c { startupHook     = docksStartupHook <+> startupHook c
            , handleEventHook = docksEventHook <+> handleEventHook c
            , manageHook      = manageDocks <+> manageHook c }


docks関数は、ステータスバーを表示するときに、ステータスバーがステータスバーとして振る舞うようにする設定を行ってくれる関数です。 そして、その関数の定義を見てみると、さっき定義した関数と似た感じになっていると思います。受け取ったXConfig a型のデータをレコード構文で書換えて作った 新たなXConfig a型のデータを返しているだけです。


レコード構文の項目名はデータからその項目のデータを取り出す関数です。つまり、startupHook cは、受け取ったデータのstartupHookの値になり、 それをdocksStartupHookに追加したもの(<+>演算子で追加できるタイプなので)を設定しています。 受け取ったデータに何かを追加するパターンでよく使われる構文です。 「なにそれ?」って感じの場合、「レコード構文について」をまた見返してみましょう。


この場合、docks関数を使わなくても、xmonadに渡すdefにレコード構文で直接、3つの項目を書換えても効果は当然同じです。 しかし、直接書いてしまうとその3つがステータスバーを使うためのカスタマイズであるという関連性は全くわかりません。


layout設定での活用例

また、別の例を見てみましょう。 レイアウトの設定はどうやっていますか? 例えば、家ではこんなふうです。


main = xmonad 
        $ docks
        $ def {layoutHook = mylayouthook}
        
---------------------------------------------------------------
-- レイアウト
---------------------------------------------------------------
mylayouthook 
  = smartBorders $ avoidStruts (mytall ||| mymirror) ||| myfull
  where 
    mytall 
      = renamed [CutWordsLeft 1] 
      $ spacingRaw True (Border 10 10 10 10) True (Border 5 5 5 5) True 
      $ Tall 1 0.03 0.5
    
    mymirror 
      = Mirror mytall
    
    myfull
      = noBorders 
      $ Full


せっかくなので関数化してみましょう。


main = xmonad 
        $ docks
        $ myCustmLayout
        $ def
        
---------------------------------------------------------------
-- レイアウト
---------------------------------------------------------------
myCustmLayout conf = conf {layoutHook = mylayouthook}

mylayouthook 
  = smartBorders $ avoidStruts (mytall ||| mymirror) ||| myfull
以下略


haskellの関数定義では、引数による場合分けは単純なパターンマッチで記述できます。 関数化すると、異なる引数でレイアウトを別のものにする事が簡単に出来るようになります。 実際のベタ書きだと、defに定義する変数名を変えて切り替えるのと変わりがありませんが、 実際には、ホスト名を取得するアクションを経由して、複数ホスト用のレイアウトを定義して 切り替えるようにして使ったり出来るはずです。


以下では、第一引数に"laptop"を与えたときは、カスタムのレイアウトにし、 それ以外の場合には、defで規定されあデフォルトのレイアウトにするというもの


main = xmonad 
        $ docks
        $ myCustmLayout "laptop"
        $ def
        
---------------------------------------------------------------
-- レイアウト
---------------------------------------------------------------
myCustmLayout "laptop" conf = conf {layoutHook = mylayouthook}
myCustmLayout _ conf = conf {layoutHook = layoutHook def}

mylayouthook 
  = smartBorders $ avoidStruts (mytall ||| mymirror) ||| myfull
以下略


0 件のコメント:

コメントを投稿

Bottom Ad [Post Page]