2013年9月13日金曜日

xmonadとHaskell(その6:ステータスバー)

その5では、dzen関数を使って取りあえずのステータスバーを付けてみたが、「これ、思ってたのと違う」と感じてるかもしれない。確かに、そこいらへんで見かけるスクリーンショットに写っているステータスバーはもう少しカッコいい(他のWMとの比較は置いといて)

そこで、dzenを使ったステータスバーをカスタマイズするための下準備だ。

xmonadとステータスバーの関係


xmonadには、xmonadの状態が変化した時、例えば、レイアウトが変わった、ワークスペースが変化した、フォーカスされているアプリケーションが変わった時などに、呼び出される関数がある。

何かのきっかけが起こると呼び出される関数は、俗に「フック」と呼ばれるが、これもそのひつとであり、xmonadでは、上のような状態変化が起こるとlogHookと呼ばれる関数が呼び出される。


このlogHookは、XConfig l型データの logHookの項目にあり、リファレンスで確認するとXConfigコンストラクタの中で logHook :: !(X ()) と定義され、「windows setが変更された時に実行されるアクション」とコメントされている。(ここで、X ()はIOアクションと同様で、何かを実行する副作用と、それから生成される結果の2つからなるXアクションと把握してOK)

xmonadでは、このフックを利用してステータスバーを作るのが一般的である。
そして、その原始的な仕組みは、

 1.情報を表示するためのアプリケーションを起動しておく。
 2.フックに現在の状況を表す文字列を表示する関数を設定する。
 3.フックが呼び出されるたびに、アプリケーション上に情報が表示される。

この原始的な方法を実践してみるには以下の通り。

XMonad.Hooks.DynamicLogモジュールで定義されたもっとも基本的な表示を行うdynamicLogでlogHookを定義してみる。dynamicLogは情報の書き出し先が標準出力となっているので、これをdzenにパイプで繋ぐため、.xsessionや.xinitrcなどの、xmonadの起動のコマンドラインを

exec xmonad | dzen2

としてみる。

特に、logHookが情報を書き出す様子だけを試すには

exec xmonad >> xmonad.log

とかして、xmonadを起動した後、ターミナルから

tail -f xmonad.log

とかすると、ステータスバーに表示されるような文字列が単にlogに記録されていくのを見ることもできる。

外部アプリとしてのdzen

dzenというアプリケーションは、そもそも、入力された情報を表示するだけのアプリケーションであり、ステータスバーに特化したアプリではない。

YouTubeでのDzen2の紹介
http://www.youtube.com/watch?v=pkKFucEXIlA

X上でのちょっとした情報を表示するためのユティリティであり、ステータスバーのような帯状の表示だけでなく、実際にはウインドウ状の表示エリアを持っているので工夫次第でいろいろな表示の仕方が可能。

一方で、基本的にはテキストしか表示できないので、イメージや動的なグラフ等の表示は難しい。

そこで、dzenを使ったステータスバーというのは、xmonadからの情報を単なる一行の文字列として受け取り、それをこの1行幅のdzenで単純に表示している。なので、そこら辺で見かけるxmonadのスクリーンショットのステータスバーは派手さのない簡素な感じのものになっているのだと思う。

実際には、表示するためのXアプリと、それにデータを送るlogHookの連携でどんなステータスバーだって作ることはできる。(xmobarをはじめ、xmonad-log-appletなんていうのもある)

xmonadの内部情報とそれ以外の情報



このスクリーンショットで見るステータスバーは、実は二つの部分から出来ていたりする。

[home] : tall : s_term1

となっている左側の部分と、それ以外の右側の部分は、別々に起動されたdzen上で表示されている。


なぜ、そうなっているかというと、左側の部分がxmonadの状態をlogHookを使って表示している部分であり、右側の部分はconky(cpuやメモリの状態等を色々と調べるアプリ)を使って収集した情報を表示している部分となっているため。


つまり、理屈的には

xmonad | dzen
conky | dzen

のように起動されて使っている。

別々に起動していても、dzenの表示位置を適切に設定してあげれば一つのステータスバーに見える。

ちなみに、xmobarは、このcpu等のパソコンの状態を収集する機能を独自で持っていたりするので、その分、ステータスバーの設置がやや簡単(単純)になってる。


xmonad.hsの中で外部コマンドを実行する方法


繰り返しになるが、上述の方法によれば情報を表示するためのdzenは外部アプリであり、xmonadとは別に起動しておく必要がある。そこで、先の例では、.xsession等のスクリプトからdzenを起動した。

しかし今度は、xmonad.hsの中から外部コマンドを呼び出す関数を使って起動してみる。

そして同時に、先の例では、シェル上でパイプを使ってストリームを繋げ、logHookからの文字列データをdzenに渡していたが、ここでは、XMonad.Util.RunモジュールにあるspawnPipe関数を使うことによって、haskell上でファイルハンドルを使ってdzenに文字列を渡す事が出来るようにする。

spawnPipe :: MonadIO m => String -> m Handle

型シグニチャは少し見たことがないものになっているが、main = doの中で使われる限り、IOアクションが戻り値となり、副作用はコマンドの実行、生成される結果は、そのアプリの入力用ハンドルであると見なしていい。

実際のxmonad.hsでは
...
import XMonad.Util.Run

main = do
     h <- spawnPipe "dzen -hoge_option"
     spawn "conky conf_file | dzen2 -hoge_option"
     xmonad defaultConfig{...}

という感じになる。

h <- spawnPipe ... の構文は前に見た通り、IOアクションから、生成された結果のみ(ここではファイルハンドル)を取り出して、hという名前に拘束するもの。

そして、この入力用ハンドル「h」をlogHookで定義する関数の中に適切に設定することにより、xmonadから外部アプリの入力に直接文字列を渡すことが出来るようになる。

また、単なる外部コマンドの起動はXMonad.Coreモジュールにあるspawnコマンドが使えるので、上記の用に書けば、conkyによる情報収集のためのプロセスを別途起動することも出来る。

logHookに定義すべき関数

内部情報がアップデートされる度に呼び出されるlogHookに定義すべき関数として、XMonad.Hooks.DynamicLogモジュールには、dynamicLogWithPPが用意されている。

dynamicLogWithPP :: PP -> X ()
dynamicLogWithPP は PPデータ型というものを引数にとるのだが、このデータ型が、書き出すデータの書式等を設定するための型である。(これは、xmonad関数に対するXConfig l型のようなもの。)

なので、logHookには、適当なPPデータ型の値を引数とするdynamicLogWithPPを定義してあげれば良い。

そして、PPデータ型の値にはXconfig l型の時のdefaultConfigと同様にデフォルト値が定義されており、その一つが「dzenPP」でdzen用の初期設定がされている。これを元にdefaultConfigの時と同様にレコード構文を使って好きな部分だけをカスタマイズしたPPデータ値を作ることが出来る。

そして、各デフォルト値の内容は、リファレンスからソースをみて確認できる。(リファレンスのページでは、型シグニチャの右側端にあるリンクからその項目のソースを見ることが出来て便利。)

dzenPPのソース

2013年9月8日日曜日

xmonadとHaskell(その5:IOアクション)

IOアクション

(その4)でみた通り、dzenやxmonadの戻り値の型は「IO(なんたら)型」だった。
これはIOアクションと呼ばれる。

IOアクションは、「アクション」というだけあって、「何かを実行するもの」を表している。と同時に(なんたら)の部分で結果を生成してくれたりする。

すなわち、IOアクションは2つの事を行う。
「副作用」を生じさせ、その副作用が「結果」を生成する。

これを、先の2つの関数で見てみると以下の通り。

xmonad関数の副作用は、xmonadウインドマネージャを実行する。
生成される結果は無い。

dzen関数の副作用は、パネルのdzenを起動する。
生成される結果は、dzen用の設定を追加したXConfig l型データ。

Haskellで 半沢直樹!

以下の基本的な入出力の関数はIOアクションを返すもので、haskellのhello worldプログラムなんかで使われるもの。

コンソールへの表示
putStrLn :: String -> IO ()

コンソールからの入力
getLine :: IO String

この2つの関数で半沢直樹してみる。


Haskellでスクリプトを実行したい場合はコンソールで
$ runghc io_hanzawa.hs

また、コンパイルして実行ファイルを作ることも出来る。
$ ghc --make io_hanzawa.hs
$ ./io_hanzawa


main、そしてdo

Haskellにおいて、IOアクションの実行は、mainという名前でのみ行われる。
(ただし、ghciを使って対話的環境は、そのまま実行される)

なので、単純に言えば、

main = putStrLn "hello world"

と、一つのIOアクションを定義づけることしかできないが、io_hanzawa.hsの様に、do構文を使うことで複数のIOアクションをひと纏めにして、mainに定義づけることが出来る。
(若しくは、その4みたいに「>>=」演算子で繋ぐ)

またHaskellの構文では、パイソンとかと同様にインデント(段落下げ)に意味があることに注意。do構文の及ぶ範囲もインデントで表されている。

Haskellのコードは、コピペした時にエラーが出たら、インデントもチェックしてみるべし。

IO(なんたら)のなんたらって何?

先に書いたようにIOアクションは実行すると2つの事を行う。
一つが「副作用」、もうひとつが「結果」の生成。

putStrLnの型シグニチャは

putStrLn :: String -> IO ()

であり、実際のコードでは

putStrLn "どれくらい怒ってますか?"

として、使ってみた。
ここでの「副作用」は、画面に

どれくらい怒ってますか?

と表示する事であり。
生成される「結果」は、型シグニチャに示されたIOの次にある()である。
()は見た目の通り空のカッコであり、何もないという結果を返してくれる。

何もない結果というのは分かりにくいので、次のgetLineを見てみる。
型シグニチャは
getLine :: IO String

であり、実際のコードでは

okorido <- getLine 

として、「<-」演算子を使っている。
「副作用」は、画面にカーソルが出てリターンキーを押すまでの文字列入力を受け付けること。
一方、生成される「結果」は、画面から入力された文字列である。型シグニチャにあるIOの後ろのStringは、この入力から生成された文字列を示している。

そして、「<-」は 、「結果」のみを名前に結びつける演算子である。

何を言っているかというと、
他の言語では、標準入力からのデータを受け取る関数の戻り値は「文字列」そのものであることが多いので、

okorido = getLine

って感じに書きたくなるが、これはダメだということ。
getLineは、IOアクションを返す関数であり、「結果」である「文字列」そのものを返す関数ではない。
上のイコールを使った式は、単に、getLineと同じことがokoridでも出来るように定義されるだけなのだ。

xmonad.hsをdo構文で

(その4)で見ていたxmonad.hsでは「=<<」演算子を使っていて、その意味はIOなんたら型から、なんたらのみを取り出して、他の関数の引数にするための演算子と説明した。
実は、この役割と同じで直接別の関数の引数にするのではなくて、名前に結びつけるのが「<-」演算子の役割。

というわけで、(その4)の「=<<」演算子を使ったxmonad.hsを

do構文を使って書き換えれば以下のようになる。


dzenのIOアクションの「副作用」は、dzen実行ファイルを実行し、画面にステータスバーの表示等を行う。そして、その「結果」は、引数として受け取ったdefaultConfigをベースとして、ステータスバーの表示に必要な加工を行った新しいXConfig l型のデータを生成する。

そして、この結果である、XConfig l型データをconfという名前にバインドし、そのconfを引数としてxmonadを呼び出す。

xmonadのIOアクションの「副作用」は、xmonadウインドマネージャの表示とか管理とかである。そして、結果は空っぽの()である。

2013年9月7日土曜日

Gist

コード部分をきれいに表現するためにGitHubのGistを使ってみてる。
シンタックスハイライトをしてくれるので、かっこよい。
Bloggerでは、埋め込み用のコードをそのままhtmlモードに張り付ければよいだけなので使い方も簡単。

このGist、バージョン管理もしてくれるんだけど、ブログに引用する場合、以前のバージョンを指定することができるのかどうかがわからなかった。

今、例で書いてるxmonad.hsは、一つのGistでバージョン管理していけばよいのかなと思っていたけど、Gistのページで以前のバージョンを表示した状態で、埋め込み用タグをコピペしてもダメっぽい。

おかげで、いったん書き換えたのを元に戻す羽目に、、、、


Gistでのコード表示を張り付けるのがかっこよいからといって、コード部分を全部Gistを使っていたらなんだか収拾つかなくなりそうかなぁ。
ブログに引用するためのアカウントを別に作って管理するほうがよさげかも。


https://github.com/


2013年9月6日金曜日

XmonadとHaskell(その4)

初心者に優しそうなリンクとか

xmonadに興味を持って初めて触って見るときには、Archlinuxユーザーならarchlinuxのxmonadのwikiをかじりつつ、"xmonad haskell"とかでググるかもしれない。

割と最近の記事で、ひと通りの設定の仕方が短く纏まってて見やすいのがここ。
Haskell に興味がある人向け xmonad 設定ガイド

haskellには、見慣れない演算子なんかも目立つが、以下のサイトは基本的な事が書いてあってざっと見ておくとよさげ。"$"や"."は何?から、リファレンスを見る際に必須となる型や関数の定義の見方の参考として
Haskell基礎文法最速マスター

そんなこんなで、あちこちを見て回って、結局戻ってくるのが、本家のドキュメントページにあるモジュールのリファレンス。awesomeの時にもリファレンスはあったけど、xmonadの方が充実している気がするので、ガンガン読んでガンガン試す!!
xmonad api docs
xmonad-contrib api docs

もうひとつは、本家のページで紹介されているいろんな人の実際のxmonad.hsファイル
config archive


ステータスバーをつけてみる

ステータスバーを利用する場合に必要となるモジュールのリファレンスページ。

ステータスバーは、xmobar、若しくは、dzenを使う。
xmobarの紹介の方が多いような気がするので、ここではdzenを使ってみる。
(その1)で紹介したように、dzenを別途インストールして呼び出せるようにしておく。(archlinuxのパッケージとしてはdzen2とかdzen2-gitとか)

とりあえず、xmonad.hsの先頭部分でモジュールを読み込む。
そして、xmonadに渡す引数をちょっと変えてみる。



リファレンスで紹介されている通り、上記の使い方がもっとも簡単なステータスバーの付け方になっている。

さて、「=<<」である。
xmonadのページをよく見ると「>>=」という記号や、これをモチーフにしたロゴっぽいものもあちこちで見られる。これが、いわゆるモナドの演算子。なんだか、暗号チックだけれど、あんまり気にしなくても大丈夫。

xmonadの引数はXConfig l型

(その3)までに見てきた形は以下のもの。
main = xmonad defaultConfig {...}

defaultConfigは、既にデフォルト値を設定されたデータであり、{...}の部分は好きな部分だけをデフォルト値に上書きすることの出来る書き方だった。
そして、defaultConfigというのはXConfigなんたら型という型のデータだった。

ここらあたりで、なんとなく一度基本部分のリファレンスを見ると良いかもしれない。
xmonad api docs

XMonad.Mainモジュールのxmonad関数をみると

xmonad :: なんたらかんたら => XConfig l -> IO()

すなわち、xmonad関数は、XConfig l 型の引数を受け取るのだから、xmonadには、XConfig l 型を渡してあげれば良い。そして、defaultConfigそのものはXConfig l 型であり、defaultConfig{...}によって新たに生成されるデータもXConfig l 型なのだ。

XMonad.Coreモジュールをには「data XConfig l」の項目があり、コンストラクタが紹介されている。(その2)辺りで見てきた「値コンストラクタ」だ。

Humanの例で見ていた時、値の種類が複数あり、Otoko、Onna、Okamaというふうに型の名前と値コンストラクタを別にしていたが、値の種類が一個の場合は型の名前と値コンストラクタの名前を一致させるのが慣例らしい。なのでXConfig l型の値コンストラクタはXConfigになっている。リファレンスには、15項目のキーワードとその説明もされており、もちろん、このキーワードでレコード構文を用いてデータを作成できるし、直接順番に引数を15個渡してデータを作成することも出来る。

xmonadが受け取る値とdzenが返す値の型が違う

ステータスバーを付けるために dzen 関数を使い、更に謎の「=<<」演算子も付け加えて、以下のような形になった。

main = xmonad =<< dzen defaultConfig

ここで、dzen関数をリファレンスでみると

dzen :: なんたらかんたら => XConfig l -> IO ( XConfig(なんたらかんたら))

となっており、確かに、「dzen defaultConfig」の部分をみると、dzen 関数が XConfig l型の引数をとっている。
そして、戻り値としては「IO ( XConfig(なんたらかんたら))」というものが返されている。

一方で、xmonadは引数としてXConfig l型をとるので、見た目では、ちょっとだけ型が食い違っている。そう、なんか「IO()」とかいうので包まれてる。

なので、「=<<」は、この包んでる「IO()」を取って、中にある「XConfig なんたら」だけを取り出すみたいな演算子なのだ。

そして、一般に見られるのは「>>=」の方向であり、

hoge >>= hage >>= fuga >>= nya

なんて感じで、左から右に連続して適用されて行ったりするらしいが、「=<<」を使って逆に書くことも出来、思考を表現しやすい方を使えば良いらしい。

すなわち、
main = dzen defaultConfig >>= xmonad

と書いても同じ意味であり、動くが、なんか「main = xmonad」とひっついてる方が良さげに見えるので、そう書いてあるのかもしれない。