2014年10月12日日曜日

Greedのカスタマイズ

TopCoderのArena用プラグイン「Greed」のカスタマイズに挑戦。




Greedは高機能なので、ほとんど何の設定をしなくても、Arena参戦するのに困る事は無い。
しかし、他のSRM参加者のソースを見ていると、ソースの初めに

#define REP(i, N) for (int i = 0; i < (int)N; i++)

とか書かれていたりする。
これが、なんか出来る人っぽくてカッコいいので自分でも使ってみたくなった。というより、きっと、こういうのは定番なので、ちゃんと技術を盗んでいかねばならないのだ!!


greed.confファイルの作成


Greedの導入時の唯一の設定項目は、workspaceディレクトリを設定することだった。
Greedの設定は、その指定したworkspaceディレクトリ内にgreed.confというファイルを作って行うことになる。なので、とりあえず、空のgreed.confを用意する。


カスタマイズ用のテンプレートファイルの作成

greed.confの次に、テンプレートファイルと呼ばれるファイルを用意する。

Greedは、アリーナから問題を開くことで、ローカルにソースやテストケースのファイルを作成してくれるが、この作成されるファイルのひな形をテンプレートファイルという。

そこで、デフォルトのテンプレートファイルに代えて、自分の好きなテンプレートファイルを作り、そのファイルをテンプレートファイルとして使いますよということをgreed.confで設定してあげれば目的が達成できるようだ。

まず、デフォルトのテンプレートファイル(C++用)の内容は以下の通り。

https://github.com/shivawu/topcoder-greed/blob/master/src/main/resources/templates/source/cpp.tmpl


このファイルをダウンロードして、「my_template.cpp」というファイル名にし、workspaceディレクトリに置く。このmy_template.cppを好きに書き換えることで、カスタマイズをする。


カスタマイズ用のテンプレートファイルとして指定する


先に述べた通り、カスタマイズ用のテンプレートを使うように設定するにはgreed.confに次の一行を書き込む。

greed.language.cpp.templateDef.source.templateFile = my_template.cpp

ここまでで、デフォルトと同じ状態のソースコードが生成される。


C++のマクロ


テンプレートファイルでは、${と}で囲まれた部分が、テンプレートエンジンによって置き換えが行われる部分で、それ以外の部分は、書いたらそのまま出力される。

なので、あまり難しく考えなくても、C++用のマクロは、テンプレートファイル前半部分にべた書きしておけばOK!


以前に紹介した、greedの設定を紹介してくれている人のページを参考にすると、インクルードファイルは#include <bits/stdc++.h>することで、必要そうな色々なものを呼び出してくれるらしいので、それにしてみた。

その他、C++の便利なマクロってどんなのがあるのかな?と思って検索してみると

TopCoderから学ぶ美しいマクロや型宣言 C++

というのが、みつかった。
そこで、初心者でもよく使いそうな奴だけ抜き出して整理してみた。



もっと使いこなせるようになったら、付け足していけば良いのかなと思う。

2014年10月7日火曜日

Greedというプラグイン


土曜日に「今日は思う存分、過去問を楽しもう!」と思ってTopCoderのアリーナ起動したら、過去問の数がいつもと違ってものすごく少ない。なんで??と思ってあれこれみていると、Active Contestsの欄に大会のお知らせのようなものが。

というわけで、TopCoderのSRM大会に初参戦!!

私は、練習問題を解く時には、Arenaで起動されるエディタを使って直接ソースを書いていたのだけれど、他の人のソースを見るとエディタのプラグインのコメントのようなものがあったので、そういうのがあるんだろうなとは思っていた。


Arena用のプラグインGreed


そこで、せっかく大会に出るんだから、環境も適当に整えよう!と思ってネットで検索。
どうやら、Greedというプラグインが評判良いらしい。
https://github.com/shivawu/topcoder-greed

そして、「これいいよー」と紹介している人のページ(日本語)
http://qiita.com/ororog/items/42a9032f20bd9d239f95


上でも書いた通り、SRMの問題に対する回答は、Arenaアプレットのフォームにプログラムを直接書き込むことで提出する。

しかし、このGreedプラグインを使うことで、問題に対する解答のためのソースファイルの雛形がローカル(自分のパソコン)に作成されるので、そのファイルを自分がいつも使っているエディタで編集することが出来るようになる。しかも、このローカルのソースファイルはGreedによってArenaと連動されているので、ファイルがローカルにあるということを意識せず、Arenaアプレット側のコンパイルやテスト、サブミットボタンを押すだけで、通常のアプレット上でのテストやサブミットが行われる。

また、ここで作成してくれるソースの雛形は、問題で指定されている提出用のクラスとメソッドを定義してくれるので、あとは中身を書くだけの状態になっている。そして、このソースはそのまま、ローカルでコンパイルして実行すると、問題に付随してくるテストケースをを試して、その結果を表示てくれるプログラムもつけてくれている。(提出時には、これらテストケースを実行するメイン部分は自動的に省いてくれるので、何も気にすることはない。)

更に更に、問題文をHTML形式にしたファイルもローカルのディレクトリに作成してくれる。Arenaのアプレットで読むよりも、いつも使っているブラウザで見る方が字も読みやすいし、翻訳とかもブラウザからのほうがしやすい。

すなわち、このプラグインは「とっても便利!Greed最高!」なのだ。


SRM初参戦日記


さて、そんな感じに、環境は万全に整ったのは良いが、肝心のプログラミング技術の方が素人。

その日までの間に、Div2 Easyの問題にいくつか挑戦していたが、C++のコンパイルってどうやるんだっけ?から始まって、vectorやstringのメソッドをネットで適当に検索しながらプログラムを書くというレベル、、、大体、問題の英語を読んで、問題を理解するのも時間かかるので、今回の大会は、まずは1問を時間内に解けることを目標にしようと思って、エントリー開始時間を待っていた。


エントリー開始は、午後10時。
同意書みたいなのに同意して、エントリー。

参加者の一覧みたいなのが見れるので、覗いてみると、あこがれのレッドコーダーは日本人にもいっぱいいた。というか、日本人の参加者が結構多いんだなぁという印象を受けた。白色のNON-RATEDの人は多分大会初挑戦なんだろうけれど、その時点では日本人の白色は3人程度で、なんとなく、怖気づく。。

大会開始5分前、午前12時55分頃。
チャット画面では、エントリー締め切りがアナウンスされ、なんとなくチャット画面ごしに参加者達がざわざわしている雰囲気が伝わってくる。

そして、各部屋に移動。Div2のとある部屋に振り分けられた。
午前1時開始なのだが、Arenaで、問題の選択が出来るようになっている。
「このメニューもう触っていいの??時間まで、触らない方がいいのなか?」とかと動揺しつつ、大人しくじっと待つ。

午前1時!!!
問題を開いてみる。Div2Easy、250点問題だ!

一見して、問題文がやたら短い!!
短い英文は、英語が不得意だと逆に分かり辛く、正確な意味がわからなかった。
が、しかし、Test casesをみる事でなんなく把握できた。

コーディングしてコンパイルすると、一発でコンパイル完了。
実行してみると、全部のテストケースをパス!!すぐさま、サブミット。

開始からたぶん15分くらいしか経っていなかった。
「何か奇跡がおこっている!!」
そう思って、今まで練習でも一回しか挑戦したことのない500点問題、div2Mediumを開いた。

問題文の英語はなんとなく理解できた。
さっそくプログラムを書き始めたが、使われている変数の型が「long long」とかなっているのが気になる。

私のプログラム技術はforもしくはwhileで単純に調べて回る全探索しか使えないので、あまり扱う数が大きくなると、単純にプログラムの時間が掛かりすぎて、制限時間オーバーになる可能性が高いという認識があった。

だから、long longみたいに「大きな数」を扱うのはヤバそうだったが、どうしょうもない。

とりあえず、プログラムが書けたのでローカルでコンパイル、そして実行。
案の定、実行時間が2秒を超えるテストケースがあった。つまり、時間オーバーだ。

しかし、どこかのページで「TopCoderのサーバーは、自分達が使っているパソコンより性能が高く早い、、」というのを見たのを思い出し、とりあえず、サーバー側でコンパイルしてテスト。

すると1.6秒くらいで実行できており、制限にかからないようだった。
そこで、「改善する方法探すより、次に行こう!!」ってことで、そのままサブミット。

なんと、未知の1000点問題Div2Hardに辿り着いてしまった。
多分、残り時間30分くらい。

勇敢に開いてはみたものの、問題文が何を言ってるかさっぱりわからない。
何度も読み返しているうちに、いわゆる「木構造」とかいうものの話なのかな?とか、edgeって点を繋ぐ線のことかな?とか、それくらいまで想像しつつ、、疲れて飽きてきた。

多分、これは問題が読み解けたところで、コーディングは全く無理と判断して、のんびり、問題文の意味を考え続けていたら制限時間が近づいてきた。
そこで、とりあえず、Greedが生成した空のソースをサブミットしておいた。

で、制限時間到来!!
5分の休憩時間。

この時、(その部屋の?)順位みたいなのが発表されていた。
ちらっと見たら、私のハンドル名がリストの上から2番目にあったような気がしていた。

暫くして、TopCoderに特有のチャレンジフェーズという、他人のソースを見てバグを発見し、ポイントをゲットする競争が始まった。そして、チャレンジフェーズが始まってみると、一覧のさっきの場所に、私の名前がなくなっていて「ん???」と思った。

私のHardの問題がチャレンジされて撃墜されていたのだ。
つまりは、Hardまでサブミットしていたので、その時点で点数が入っていて、上位に名前があったらしい。

しかし、あっという間の撃墜だった。まぁ、慣れてる人が見て、「こいつ、なめすぎ!!糞ニュービー」っていって、落とされたに違いないwww

チャレンジフェーズは、私も他人のソースをあれこれと眺めみるが、いまいち把握できない。この辺はきっと訓練が必要だ。

で、チャレンジフェーズが終わったら、システムテストが始まって、システム自体がサブミットしたソースを検証してくれる。これにパスすればポイントが確定してレーティングが計算されるみたい。

参加者の結果一覧をみていると、次々とシステムテストに通ったとか通らないとかの表示が出てくる。私の分もチェックされたらしく、何かのウインドがポップアップしていた。あまりよく読んでいなかったけれど、部屋を入りなおすとレイティングが更新されてるよみたいなことが書いてあったんだと思う。
チャット参加者の一覧から人がどんどん抜けていくので、私も追随。

入りなおしてみると、、、、
私のハンドルネームがブルーに!!!!

なんと、「ザクとは違うのだよ、ザクとは」だったのだ。


2014年10月3日金曜日

xmonadとHaskell(その16:レイアウトのスクリーンショット)

拡張モジュールにあるレイアウトモジュールは凄く沢山あるが、幾つかの視点から整理してみる。


基本的レイアウト

単独でレイアウトになれる型のデータを含むモジュール。
重なりか、非表示かがわかりやすい様にターミナルを透明にしてスクリーンショットを取ってみた。

工夫次第で使えそうな勧めレイアウト


XMonad.Layout.OneBig
一つだけ大きく



XMonad.Layout.TwoPane
XMonad.Layout.DragPane
画面を二分割で固定。マスターとそれ以外の2ウインドウ制。それ以外は非表示。
DragPaneの方が縦、横の選択等のカスタマイズができる。



XMonad.Layout.GridVariants
GridVariantsモジュールでは、Tallのメイン部分を任意の固定行x列にした上で、サブ部分がグリッド配置になるようにできたりする。



XMonad.Layout.ThreeColumns
メイン1行、サブ2行の固定



XMonad.Layout.MultiColumns
メイン1行、サブ行どんどん増えるが各行のウインド個数を指定できる



XMonad.Layout.Simplest
メイン1画面固定。
サブは非表示ではなく、メインの後ろに表示されている点でFullと異なる。



XMonad.Layout.StackTile
stacktileはdishesに似ているがメイン部分の高さが固定、サブ部分が増えるとサブの高さが小さくなる。



Tall改良版

hintを重視(文字がちゃんと表示される高さとか)

XMonad.Layout.FixedColumn
大きさ等指定を割合じゃなく文字数で

XMonad.Layout.ResizableTile
サブ側の高さがリサイズ出来る


その他のレイアウト

XMonad.Layout.Accordion
複数ウインドを開くとフォーカスのあるもの以外帯状に縮んだ状態になる。



XMonad.Layout.Circle
マスタが真ん中に、サブはその後ろで円状に配置



XMonad.Layout.Cross
マスタが真ん中に、サブはその後ろで十字形状に配置



XMonad.Layout.Roledex
斜めに綺麗に並べられる



XMonad.Layout.Spiral
渦巻き状にタイリング



XMonad.Layout.Mosaic
XMonad.Layout.MosaicAlt
各ウインドウの面積がある一定の法則になってる?


XMonad.Layout.Column
隣接ウインドウとの高さの比を規定



XMonad.Layout.Grid
XMonad.Layout.HintedGrid
Grid系は規則正しく格子状になるように配置をする。



XMonad.Layout.Dishes
メインの下にサブがお皿の用に積み重なっていく。



デコレーション系

xmonadでもタイトルバー的なものを付けることも出来るし、マウスによるボタンクリックによるウインドウ操作も可能にできる。

XMonad.Layout.DecorationAddons
XMonad.Layout.ButtonDecoration
XMonad.Layout.ImageButtonDecoration
各ウインドにバーを付ける



XMonad.Layout.TabBarDecoration
レイアウトにタブを追加する



XMonad.Layout.DwmStyle
各ウインドにDwmスタイルのタグを付ける



XMonad.Layout.DecorationMadness
幾つかのレイアウト(circle,accordion,tall)に飾りを付けたものを提供してくれる


XMonad.Layout.Decoration
テーマやシュリンカをカスタマイズする時のデフォルト値とか


    TopCoder

    本屋さんで「最強最速アルゴリズマー養成講座」という本を買ってみた。

    別に「アルゴリズマー」になろうとしたわけではなく、世の中には「プログラムコンテスト」なるものがあって、その筆者曰く、初心者こそ参加すべきという文言に目が留まったからだ。

    しかし、それよりもなによりも、そのコンテストは「TopCoder」といわれるものなのだが、このコンテストの優秀者(トップ0.2%)達は「RedCoder」と呼ばれ、これまた筆者曰く「『"赤いヤツは3倍早い"』というのはアニメだけの話ではありません。Redcoderは、素早くしかし華麗なコードを書き上げ、Challenge phaseでは容赦なく他人を撃墜していく存在であり、参戦者からすると恐怖の対象でもあります。」とかいう文句に心躍らされてしまった。

    昭和世代のおっさんは、アルゴリズマーであろうがなかろうが、少なくとも皆「ガンダマー」

    なので、参加しないわけには行きません。
    そう、「ザクだからって、なめるな!」なのです。


    http://www.topcoder.com/


    先の本が書かれたころよりも、ページが進化しているらしい。
    とりあえず、「Arena」に行くためには、ページ上段に並ぶ「challenges」からドロップダウンされるメニューの一番下「Topcoder arena」をクリック。

    ContestAppletProd.jnlpとかいうファイルがダウンロードされる。

    さて、windozeの場合、JREがインストールされていれば、このファイルをダブルクリックするなりでArenaが起動するが、archlinuxの場合は、すんなりいかないこともあるので、幾つか確認。


    1. icedtea-webパッケージのインストール
    *.jnlpというのは、JavaのJava Web Startという仕組みを使うらしく、そのために必要なパッケージが「icedtea-web」らしい。

    http://d.hatena.ne.jp/pogin/20120331/1333222488

    そのページを参考に

    #pacman -S icedtea-web

    で、インストール。

    そして、ダウンロードしたContestAppletProd.jnlpを以下のように実行。

    $javaws ContestAppletProd.jnlp

    セキュリティーの確認画面がいくつか出て、了解していけばArenaが立ち上がる。


    2.xmonadとjavaアプレットのFAQ

    xmonad上で、javaのアプレット扱う時にはFAQがある。
    何も設定しないと、アプレットに何も表示されない状態になって、びっくりしてしまうので、以下のページを参考に設定しておくのを忘れずに。

    http://qiita.com/xorphitus/items/efcca6652558bc5cc785


    #include XMonad.Hooks.SetWMName
    defaultConfig {startupHook = setWMName "LG3D"}

    みたいにstartupHookを設定しとけばOK(インクルード忘れずに)





    2014年2月3日月曜日

    xmonadとシステムトレイ

    うちのxmonadのステータスバーはdzenであり、システムトレイ的なものがない。

    スマホで撮った写真を簡単にパソコンで見るために、Dropboxを使ってみたんだけれど、「システムトレイアイコンからうんぬんかんぬん」という説明があったので、なんとなくつけてみたくなった。


    その前に、dropboxだ。

    Dropbox

    データ共有のアレ。
    https://www.dropbox.com/

    Archlinuxでは、AURにパッケージがある。

    また、archlinuxのwikiは以下。
    https://wiki.archlinux.org/index.php/dropbox


    取り立ててメモする程のものはないけれど、一応メモ。

    wikiにある通り、dropboxの使い方には「dropboxd」コマンドをxsession等から実行する使い方と、systemdでサービス(デーモン)として呼び出す使い方の2種類があるらしい。

    下準備


    どちらの使い方をするにしても、初期設定等についてあまり深く考えずに導入するためには以下の方法が良さげ。

    1.アカウントは事前にWeb等から取得しておく。
    2.yaourt等でdropboxのパッケージをインストールしたら、Xのターミナルエミュレータ上で、dropboxdコマンドを実行。ポップアップされる指示に従ってログインして、チュートリアルを最後までみる。

    以上で、ホームディレクトリに必要なファイル等が用意されるはず。
    あとは、適当にターミナル上でC-cとかして終了させる。

    xmonadでタスクトレイがない場合、タスクトレイアイコンからの設定が出来ないので、別途dropbox-cliパッケージ(AUR)をインストールして操作するべきかもしれない。が、うちではほったらかし。
    というか、さっき試しにインストールしようとしたら、チェックサムが合わない(?)とかいうエラーでインストール出来なかった。そのうち、覚えてたら再度試してみようかな。

    systemdから


    上記のタスクトレイアイコンからの設定では「自動起動」の項目なんかがある、DEやopenboxなんかのWMの場合は、その効果が反映されそうだが、xmonadを使ってる限りは関係なさそう。
    なので、systemdのサービスとして使う。

    systemdを使った場合、システムが動いていればアカウントにログインしていようがいまいが、ディレクトリの同期をしてくれるっぽい。一方、Xの起動を前提としていないので、タスクトレイのアイコンが出てこない。

    が、wikiにある通り、次の設定をすればシステムトレイアイコンがでてくる。

    さて、この設定の仕方なんだけどwikiでは
    「/etc/systemd/system/dropbox@.service」のファイルに

    .include /usr/lib/systemd/system/dropbox@.service
    [Service]
    Environment=DISPLAY=:0

    という内容を書くといいとなっている。
    設定の内容は、元の設定に環境変数の設定(X上に表示を行う)を追加しているものだ。

    systemdでは、この「/etc/systemd/system/」ディレクトリ以下に「ファイル」を作成すると「/usr/lib/systemd/system/」ディレクトリ以下の同名の設定ファイルの内容が上書き設定される仕組みになっている。

    しかし、今回の場合は、一部の追加であり、そのため、わざわざ、元ファイルを読み込んでいる。

    この様な時には、追加設定の方法をとる方が格好良さそう。
    その方法は、「/etc/systemd/system/」ディレクトリ以下に「設定ファイル名.d」というディレクトリを作る。上の場合なら「/etc/systemd/system/dropbox@.service.d/」というディレクトリだ。そして、次にそのディレクトリの中に「hoge.conf」というファイルを作りそこに追加したい内容を記述すればよい。

    さて、実際には、dropboxのサービスファイル名はユーザー名が入って構成される。うちの場合「neko」というユーザーなので

    # systemctl enable dropbox@neko.service

    であり、システムトレイアイコンのためには

    #mkdir /etc/system/systemd/dropbox@neko.service.d

    としてディレクトリを作成した上で、

    #vim /etc/system/systemd/dropbox@neko.service.d/env.conf

    とかして

    [Service]
    Environment=DISPLAY=:0

    とだけ、env.confファイルに記述すればよい。

    https://wiki.archlinux.org/index.php/systemd


    stalonetray


    本題の、xmonadでシステムトレイだが特に難しいことはなにもない。
    「stalonetray」という単独のシステムトレイアプリがある。

    うちのdzenは、ご存知の通り「つぎはぎ」されている。
    そして、システムトレイもこれと同様にstalonetrayを「つぎはぐ」のだ!


    本家
    http://stalonetray.sourceforge.net/manpage.html

    Archのwiki
    https://wiki.archlinux.org/index.php/Stalonetray


    とりあえず、実行しておけば、そこにシステムトレイアイコンが表示される。

    dzenとうまく「つぎはぎ」するコツは以下の2点だ。

    背景色を同じにする

    dzenの背景色と同じにすることで、一体感をだす。
    -bg "#000000"


    高さを合わせる


    dzenと高さを合わせる。
    dzenは「-h」オプションでピクセル単位の高さを指定している。

    一方、stalonetray側の高さの指定は「--geometry 」オプションで行い、その引数の書式は

    widthxheight[+x[+y]]
    となっていて、例示すれば「1x1+0-0」という感じだ。
    ここで、注意すべきはwidthもheightも単位はアイコン何個分か?である。つまり、ピクセルではない。

    では、アイコン1個の大きさはどうやって決まるか?
    「--slot-size」オプションにピクセル数を指定して使う。

    dzenの高さが20なら、「--slot-size 20」として、--geometryのheightは1にしておけば良い。

    「--slot-size」の指定は横幅も同じ数値になるので、widthの調整もこの数字に着目して計算すれば良い。

    あとは、適当でも良さげ。
    stalonetrayのオプションの指定は、コマンドラインに並べても、設定ファイル(デフォルトでは~/.stalonetrayrc)を読み込んでもどっちでもいける。

    xmonad.hsの中で呼び出した場合には、コマンドラインに並べたオプションにして、dzenとの関係から、長さを計算で出した方がスマートっぽいかも。

    上手につぎはぎ出来ました

    と思って晒してみたけど、特にスマートっぽくもないか。

    まぁ、システムトレイアイコンがあると見た目が今時っぽくなった。





    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)

    という感じになる。