Zipper関連のパッケージ

先日コーディングしていてZipperが必要になる場面に出くわした。それは単純なList zipperだったので自前で実装して済ませたが、ふと、汎用的なZipperライブラリがあるかどうかが気になったので、少し調べた。

syz
traverse-with-class
uniplate
comonad-extras
zippo
zippers
  • zippers: Traversal based zippers
  • lensパッケージに依存。
  • LensやIndexedLensを使ってZipperのフォーカスを動かす。
  • (:>)といった型コンビネータ(?)を使い、非均一なデータ構造を辿る場合でも経路の型情報を全て保持する。


使い勝手がよさそうなのはsyzであるが、generic programmingを使う以上、型安全性が少しだけ弱いように思える。実際に使う場合はMaybeがたくさん出てきそう。

zippersはかなり型安全に見えるが、lensへの依存はそれなりに重い。また、Zipperの型を自分で正しく記述するのは至難の業なようにも思える。全て型推論に任せられるならいいが。

Haskellでテンキーに動的なキーバインドを設定できるやつを作った

テンキーに対して動的なキーバインドを設定するためのHaskellライブラリ"WildBind"をリリースした。

現状はX11デスクトップ環境のみサポートしている。要はxbindkeysみたいなものだが、その実体は普通のHaskellモジュールである。以下のようにして使う。

import WildBind.Task.X11
import System.Process (spawnCommand)

main = wildNumPad myBinding

myBinding = binds $ do
  on NumCenter `run` putStrLn "Hello, world!"
  on NumPageUp `run` spawnCommand "firefox"

「動的なキーバインド」というのは、ここでは2通りの意味で使っている。

第1に、WildBindではアクティブなウィンドウに応じてキーバインドを変化させることができる。例えば、Firefoxが開いているとき、動画プレーヤー(VLCとか)が開いているとき、PDFビューワ(evinceとか)が開いているとき、それぞれに対してキーバインドを設定すれば、それらが自動的に切り替わる。

第2に、WildBindではキーバインド自体が状態を持ち、自身の状態によってキーバインドを変化させることができる。そのため、例えばEmacsのようにキーシーケンスに対してアクションを割り当てることができる。

なお、現状ではWildBindはテンキー上のキーにのみ、キーバインドを設定できる。これは、全てのキーを対象とするように作るのが面倒だったのと、全てのキーを対象とした場合は動的なキーバインドなどほとんど必要ないと思ったからである。テンキーという限られたスペースに可能な限り機能を詰め込むのがWildBindの基本的な考え方である。

詳しい説明やコード例はGitHub上のREADMEを参照されたい。



ところで余談だが、WildBindは数年前に作ったNumpaarというものが元になっている。

NumpaarはPerlとCで書かれていて、やたら複雑なマルチプロセス構成になっており、使いづらい上に不安定だった。そこで今回、全てをHaskellで書き直すことにしたのだった。

慢性上咽頭炎の近況 (5)

前回の近況から3ヶ月が経過した。これまであったことを記す。

幸い、あれから症状が極端に悪化することはなく、耳鼻科に行くこともなく過ごせている。

ただ、依然としてたまに以下の症状が現れている。

  • 頭がぼうっと熱い感覚。午前中からお昼にかけてが多い。
  • 軽いめまい。
  • 疲労感。特に夜に感じ、眠くなる。鼻の奥がツーンとする感覚や、軽い頭痛を伴うことがある。

6月下旬と8月下旬、それぞれ1〜2週間ほどこうした症状が現れていた。

症状が出るきっかけというか、原因についてはよく分からない。睡眠不足、気候の変化、あるいは心理的ストレスだろうか。ただ、症状が出ているときはかなり早く寝てもすぐにはよくならなかったように思う。

とはいえ、逆に言えば上記の期間以外はほとんど症状を感じなかったので、ずいぶん良くなったほうだと思う。

ロリポップ!のサーバ移設でInternal Server Errorが出たので対応

ブログやQiitaに書かないようなこと(最近は主にUbuntuのインストールメモ)は個人的に立てているPukiWiki(http://debugitos.main.jp/)の方に書くようにしているが、昨日ここにアクセスしたら"Internal Server Error"が出てしまっていた。

このPukiWikiロリポップ!のサービスでホストしていただいているのだが、そういえば8/18に「サーバ移設」が行われていた。

ということで、多分このせいでエラーが出るようになったのだろう。

いろいろ調べたところ、どうもアクセス制限のための設置していた.htaccessがいけなかったらしい。もともとは<Files>ディレクティブを使って一部のファイルにのみアクセス制限をかけていたが、これを取り除くと動くようになった。

サーバ移設によってApache httpdバージョンが2.2から2.4に上がったらしいが、そのせいでエラーになっていたのか、それとも利用可能なディレクティブがより少なく設定されているのか、よく分からない。

ただ、この作業の過程でよかれと思ってPHPバージョンを5.2から5.6に上げたのだが、そのせいで今度はPukiWikiのバグによるエラーが出るようになった。

どうやら古いバージョンのPukiWikiではPHP 5.4以降でエラーが出るらしい。

ということで、せっかくなのでPukiWikiを最新版に差し替えて、ついでにEUC-JPからUTF8に移行することにした。

まず、phpをローカルにインストールする。コマンドラインで起動できれば十分。

$ sudo aptitude install php-cli php-mbstring

次に、以下のサイトからdata2utf8.php.txtをダウンロードし、data2utf8.phpに名前変更。

data2utf8.phpPukiWikiのルートディレクトリに設置して、実行。

$ php -f data2utf8.php

これで文字コードの変換は完了。

UTF-8の最新版PukiWikiに旧PukiWikiのデータを流し込む。

$ cp -an ../old/wiki/* wiki/
$ cp -an ../old/attach/* attach/

backupやcacheは面倒なのでいじらなかったが、これでなんとなくうまく動くようになった。

あとは適宜skinを移植。基本的にimageリソースとskin/pukiwiki.{css,skin}.phpをコピーすればそのまま動いた。

ただ、これでサイト上のPukiWikiページにアクセスするとどうもページロードが遅い。というか、スタイルがワンテンポ遅れて当たっている感じだ。調べてみると、pukiwiki.css.phpのロードに700msもかかっていた。遅すぎる。

ということで、pukiwiki.css.phpが吐いたCSSを手動でファイルに保存し、skinではこのCSSファイルを直接読みこむように変更したところ、だいぶマトモなスピードで動作するようになった。

慢性上咽頭炎の近況(4)

前回の近況からまた2ヶ月がたったので、これまであったことを記す。

Bスポットの終了

2016年4月5日にいつも通り耳鼻科でBスポット療法を受けた。この日でだいたいBスポットを始めてから半年が経過したことになる。症状はだいぶ改善してきているし、ここでいったん定期的なBスポット療法をやめ、様子を見ることになった。

耳鼻科の先生が言うには、「何ヶ月かしてまた戻ってくる人もいれば、こない人もいる。ともかく鼻呼吸を心がけよ」とのことだった。

それからここ2ヶ月の間は以下のような症状があった。

  • 後鼻漏。
  • 頭がぼうっとする感覚。目まいほどではないが、午前中に間食すると起こりやすかった。
  • 光がまぶしい感覚。特に午後3時から4時あたりで西日が強い時によく感じた。
  • 軽い吐き気。頻度は高くなかったが、液晶モニタでずっと文書を読んでいる時などに感じた。

こういった症状はコーヒーを飲んだり涼しい場所に移動することでやわらげることができた。

いずれにせよ、これらの症状は次第に弱くなってきており、今ではほとんど感じない程度になっている。

その他の措置

鼻うがいは朝夕に1日2回、引き続き行っている。特に寝ている間に鼻の奥に鼻水がたまることがあるので、朝に鼻うがいをすると気分がスッキリする。

さすがに鼻うがいには慣れてきたようで、水を押し込むタイプでも吸い込むタイプでもほぼ百発百中で鼻からノドに流せるようになってきた。不慣れなうちは、特に押し込むタイプではノドに抜けなかったことがあったが、ノドに抜ける感覚をイメージしながらゆっくり水を押し流すとうまくいくように思う。

腕と頭へのお灸は、最近は頻度を少し減らしてみている。結局効果があったのかどうか、よく分からない。鍼灸院で買ったもぐさがまだだいぶ余っているので、それを使い切るまでは続けようかと思っている。

.cabalファイルのghc-optionsに何を書くべきか

ふと、Haskellパッケージの.cabalファイルのghc-optionsフィールドに何を書いておくとよいか不安になったので、少し調べた。

なお、cabalが実際にどのようなオプションをghcに与えているかは--verboseオプションで確認できる。

$ cabal build --verbose

stackを使う場合、--verboseオプションはstack自身の、--cabal-verboseオプションはcabalのverbosityを制御する。

$ stack build --verbose --cabal-verbose

参考:

-Wall -fno-warn-unused-imports

これらは書くようにしている。

"-Wall"はご存知コンパイラの警告を全て表示するオプションである。

"-fno-warn-unused-imports"は不必要なモジュールやシンボルのimportに対する警告を無効化するものである。古いバージョンのghcでは必要だったimportが新しいバージョンのghcでは必要無くなることがあるのだが、このオプションがないとそんな場合にも警告が出てうっとうしいからだ。

-O[n]

最適化オプション。個人的には、これは書かなくてよいと思っている。

cabalはデフォルトで-Oオプションをghcに渡してコンパイルするらしい(cabal-install-1.24.0.0時点)。この挙動はconfigure時に設定できる。

$ cabal configure -O2

デフォルトでレベル1の最適化をするので、まあ特に設定しなくていいかなと思っている。他人のパッケージの.cabalをいくつか見てみたが、-O2と書いているものもあれば、何も書いてないものもあり、まちまちなようだ。

他のオプションでもそうだが、最終的にはユーザーがビルドする際に上書き設定すればよいだろう。

-X[LANGUAGE_EXTENSION]

ghc拡張機能スイッチ。これは書くべきではない。.cabalではdefault-extensionsフィールドとother-extensionsフィールドが使える(Cabal-1.10から。それより前ならextensionsフィールドが使える)ので、こっちを使うべきである。

なお、default-extensionsフィールドに書いた拡張機能はパッケージ内の全モジュールで有効になる。other-extensionsフィールドはパッケージが使う拡張機能を宣言するだけのものであり、各モジュールはLANGUAGEプラグマを使って必要な拡張機能のみを有効にする。

-threaded

OSスレッドを使用する。リンカオプションなのでlibraryセクションでは(多分)不要。他は個別の事情に合わせて、といったところだろう。

プログラムの作りによっては-threadedオプションがないと動かなかったり、逆にあると動かなかったりするので注意を要する。

以前、同じテストプログラムを-threadedのあるバージョンとないバージョンで2通りテストしたいことがあったが、結局test-suiteセクションをコピーして2つ並べてしまったことがある。何かよい方法はないだろうか。

-rtsopts

コマンドラインやGHCRTS環境変数からのRTSオプションを全て受け付ける。リンカオプションである。入れたほうがexecutableを使う立場としては便利だが、セキュリティ上のリスクがあるのかもしれない。

なお、デフォルト(-rtsopts=some)ではごくごく一部のRTSオプションしか受け付けないらしい。

"-with-rtsopts=-N[n]"

"-with-rtsopts"はRTSオプションをリンク時に指定する。-Nオプションはn個のOSスレッドの同時使用を許可する。n省略時はCPUコア数に応じたよしなな値が使われる。-threadedをつけるならセットでつけておいた方がなにかと便利だろう。

なお、-with-rtsoptsによる設定は-rtsoptsがなくても有効になる。-rtsoptsはあくまでプログラム実行時にRTSオプションを設定できるかどうかを制御するものである。

また、-with-rtsoptsではスペース区切りで複数のRTSオプションを並べることができるが、これを.cabalに書くには上に示したように-with-rtsopts=...全体をダブルクォートするべしとのこと( https://github.com/haskell/cabal/pull/1346 )。

"-with-rtsopts=-M512m"

"-M"オプションは最大ヒープサイズを制限する。自分の書いたプログラムのバグによってメモリが無限に食いつぶされるのが怖いので、自分はtest-suiteにはこれをいれるようにした。(参考: http://qiita.com/debug-ito/items/87fa50d5324e20936d7e )

Stackageに自分のパッケージを上げる

最近はもうstackとstackageには頼りっきりで、使いたいパッケージがstackageに入っていないとちょっとイラッとくるくらいになってきたが、そういえば自分もいくつかのパッケージをhackageに上げている。大したものではないとはいえ、それらをstackageに載せないのはスジが通らないなと思い、stackageに上げることにした。

さて、自分のパッケージをstackageに上げるにはどうすればいいか。必要なことは以下のページに書いてある。

手順としては、stackageレポジトリをforkして、build-constraints.yamlに自分の名前とパッケージのリストを追加して、pull requestを送るだけだ。

ただし、stackageに入れるパッケージは以下の3点を満たす必要がある(あるいは、満たす「べき」である?)

  1. All packages are buildable and testable from Hackage.
  2. All packages are compatible with the newest versions of all dependencies.
  3. All packages in a snapshot are compatible with the versions of libraries that ship with the GHC used in the snapshot

travisスクリプトとビルドエラー対応

第1の条件はこれまで通りきちんとパッケージングしてhackageに上げれば満たされる。

テストに関しては.travis.ymlが提供されているのでこれをサクっと利用するのがいいだろう。

GHC + cabalおよびstackを用いたビルドパターンをいくつかのバージョンでやっている。なかなか良さそうだ。

なお、stackageに上げたパッケージがビルドに失敗する場合、「1週間以内を目安に解決しろ」とのこと。


依存パッケージのバージョン上限問題

第2の条件は、stackageに上げるパッケージが依存するパッケージのバージョン上限に関係する。

例えば、.cabalファイルに以下のようなbuild-dependsがある場合、

  build-depends: foo-bar >=0.3.0.1 && <0.5

foo-bar-0.5がリリースされると第2の条件が満たされなくなる。この場合、foo-barに依存しているパッケージの作者が(1週間以内を目安に)解決しなければいけない。foo-barの変化が小さければ新バージョンでのビルドとテストを確認してbuild-dependsを変更するだけでよい。そうでない場合はちょっと厄介なことになるだろう。

ここで問題となるのは、依存パッケージが他人のものである場合、それがいつメジャーバージョンアップされるか分からないということだ。この問題に対処するために、以下のサービスが公開されている。

このサービスは検索したパッケージの依存パッケージのバージョン上限を調べ、それが現在の最新版をカバーしているかチェックしてくれる。パッケージ検索ではauthorフィールドも検索対象になるので、例えば筆者の場合は

をウォッチすればいい。RSSフィードも吐いてくれるので、こいつをフィードリーダーに突っ込んでおけば速やかに依存パッケージのメジャーバージョンアップに対処できる、はずだ。

なお、PVPには反してしまうが、依存パッケージにバージョン上限を記載しないという考え方もある。この件についてMichael Snoyman氏がブログに書いている。

stackage的にはバージョン上限はあってもなくても構わない、ということだろうか。また、stack sdistとstack uploadには--pvp-boundsというオプションがあり、build-dependsのバージョン下限・上限をいい感じに書き加えてくれるらしい。

個人的には、今のところbuild-dependsのバージョンは自分で管理すればいいと思っている。いろいろ規模が大きくなると破綻しそうな気もするが。


依存パッケージのバージョン下限問題

第3の条件は依存パッケージのバージョン下限に関係する。要は、「GHCにバンドルされるパッケージバージョンがカバーされるくらいには下限を広く取れ」ということである。

このことの理由は以下で詳細に説明されている。

"lenient"とは"not as strict as expected"という意味らしい。

例えばあるGHCバージョンにパッケージfoo-bar-0.2がバンドルされており、自分のパッケージのbuild-dependsがfoo-bar >=0.3だとする。すると、バンドルされたfoo-bar-0.2とユーザスペースにインストールされたfoo-bar-0.3が混在する状況になりうる。これが結構ややこしいコンパイルエラーを引き起こし、運が悪い場合は詰む。

さらに言うと、Haskell Platformで提供されるパッケージバージョンをカバーしておくといいようだ。これらは以下のページで確認できる。

ぶっちゃけそれほど多くないので、現状では人力でチェックすればいいと思う。