鮭とばとコーヒー

四度目の正直ブログ

tmux設定メモ

シェルコマンドの結果を文字列として入力したくなったため、ちょっとした設定を加えたのでそのメモです。 シェルであればコマンド置換$(command)を使えば問題ありません。zshだとタブを押すと展開もされますよね。 同じようなことをシェル以外のインタプリタ上で実現したくなりました。

僕のケースではSQLiteインタラクティブシェル上で適当なUUIDをデータとして挿入したくなったのですが、わざわざペインを分割してuuidgenを実行してコピペとか面倒だと思った訳です。 pipe-paneというtmuxコマンドで実現できるのですが、もうちょっと簡単にしようと思いcommand-promptと組み合わせることにしました。

~/.tmux.confに以下のように書きます。

bind | command-prompt -p "<!" "pipe-pane -I \"echo -n \\\"$(%%%)\\\"\""

これでPrefix + |を押すと、プロンプト<!が表示されて、そこに入力したコマンドの出力があたかもキーボードから入力したかのように入力されます。 複数行コマンドとかには対応していないのでセミコロン区切りにする必要がありますが、まあそれほど困っていません。これで、

INSERT INTO some_table (name, order_id) VALUES ('John', '

まで入力してprefix + |, uuidgen<CR>とするとUUIDが生成されて入力できるという訳です。

簡単にやっていることを書いておきます。 %%%の部分はcommand-promptの入力文字列で置換されます。%%でも良いのですが、%%%の方が引用符がエスケープされるので扱いやすいのです。 pipe-pane -Iが入力として外部コマンドを受けつけるのですが、末尾の改行が入ると色々と面倒なのでecho -n "$(command)"として除去しています。

今は何層目のクオートの中身を書いてるのか分からなくなりエスケープと引用符の周りでゴタつきましたが、上のものでOKなはず。 バインドするキーは好きに変えて下さい。

余談ですが、僕は今まで|split-window -h -c '#{pane_current_path}に割り当てていました。これは%を使うことにしました。 ほぼデフォルトと同じですが、カレントディレクトリでシェルを開くところだけが違います。"も同じようにカレントディレクトリで開くようにしています。 ホームディレクトリに移動したければcdすればよいだけですからね。カレントディレクトリで作業をしたいことの方が多いですし、こっちの方が僕は好みです。

なお、tmuxのバージョンは3.2aでした。古いやつだと動かないかもしれません。

おわり。

30代男性のATMにおける奇行について

最近、少しまとまったお金を振込む必要があったのですが、ちょっと変なことになったので記事にしてみます。

僕が使っているメインバンクは三井住友銀行 (SMBC)なのですが、この口座から三菱東京UFJ銀行 (MUFG)の口座に200万円弱を振込むことになりました。期日は決まっていますが、急ぎではありませんでした。普通に振り込むとなると、440円の振込手数料が発生します。

振込手数料:三井住友銀行

ケチる程の額か?と言われそうですが、もったいないと言えばもったいない……。そう思った僕は、以下のような奇行に打って出ます。

  1. MUFGに口座を開設
  2. MUFGのATMで、SMBCのメインバンクから現金を引き出す
  3. 引き出した現金をそのままMUFGの口座に預け入れる
  4. 2.と3.を必要なだけ繰り返す
  5. MUFGの口座から目的の口座に振込

正直なところ、もともとMUFGに口座があった方が後々便利だと思っていたからこそ、この方法を採用したという面もあります。そうでなければ諦めて振込手数料を払っていました。

MUFGのATMでは、SMBCのキャッシュカードを手数料無料で利用することができます (SMBCのATMでMUFGのキャッシュカードも使えるみたいです)。ただ、このサービスは平日の日中に限られるため昼休みにATMに通いました。引出し上限額は50万円なので、計4日間。そこまでしてケチる程の額か?

銀行のシステムがどのように作られているのか僕は知りません。ただ、両行のATMは相互に利用可能ですし、オンラインバンキングで他行宛の振込だってできるわけです。恐らく何らかのプロトコルがあり、それによって送金の処理が可能なのでしょう。ただし、手数料が発生します。 代替案として僕が取った行動は、オンラインバンキングであれば一回で済むはずの処理を複数回に分割し、毎日ICチップの付いたカードを機械に挿入、暗証番号を入力して紙の束を取り出してカードを入れ替え、せっかく取り出した紙の束をまた元の機械の中に戻すという珍妙なものと相成ったのでした。それならはじめから振込手数料を無料にしてくれればいいのに!と思ったりもしたのですが……。

後になってできたシステムが既存のものと矛盾してしまうことはよくあります。今回の場合だと矛盾というほど大袈裟な話でもないのですけれど、僕が昼休みを潰してATMと職場を4往復したことは銀行にとって何の利益にも繋がりませんでした。まあそれが面倒なら手数料を払え、ということなのかも知れませんが……。 どちらかといえば、元々存在した振込手数料の仕組みと、新しく導入した両行間でのATM利用料無料という仕組みの狭間に僕のユースケースがすっぽりと落っこちてしまったがゆえに起こったことなのだという感想を持ちました。

おしまい。

いわゆる関数型言語が (扇情的に)バカのためのものと言われるのを実感したことについて

扇情的な宣伝文句として「バカ」のような言葉を使うのは個人的には好きではないのですが、まさにそういう体験をしてしまったので備忘録的に残しておこうと思いました。好きでないというのは、まあ普通に失礼じゃね?というだけのことなのですが。宣伝文句でなくても使わないようにしたいところです。

Elmという言語があります。いわゆるAltJSの一つで、文法などはHaskellとかに近いです。コンパイラHaskellで書かれています。 Haskellがデフォルトで評価を遅延させるのに対し、Elmでは正格評価されます。そのため、再帰関数は末尾再帰にしてやることで最適化が効くようになります。確かバージョン0.16以降で有効だったはずです。他の言語でも、末尾再帰にすることで最適化が効くものがあるようです。例えばErlangでは、末尾再帰になっている関数はスタックフレームが除去されるとのことです。

さて、Elmで遊んでみたときに、以下のような関数を書きました。リストの値に関数を適用した結果でグループ分けするというものです。

groupBy : (a -> b) -> List a -> List (List a)
groupBy f list =
    case list of
    [] -> []
    x :: xs -> List.filter (\y -> f y == f x) (x :: xs) :: groupBy f (List.filter (\y -> f y /= f x) xs)

(シンタックスにElmが使えないっぽかったので、Haskellを指定してみました。微妙に文法が違うようですが、まあ大体一緒らしいのでいいでしょう。)

上のようにgroupByを定義すると、例えば

> groupBy String.length ["foo", "bar", "hello", "baz", "world"]
[["foo","bar","baz"],["hello","world"]]
    : List (List String)

となります。ところがこの関数を末尾再帰にしようとしたとき、僕にはどういうふうにすればよいかすぐに分かりませんでした。バカなので……。

困った僕は簡単な問題の一般化を当て嵌めて考えてみることを思いつきました。階乗を再帰関数で書くのは有名なので、これを使って考えることにしました。末尾再帰でないバージョンはこうです。

factorial : Int -> Int
factorial n =
    case n of
    0 -> 1
    _ -> n * factorial (n - 1)

負数に対応していませんが、今は一般化することが目的ですから細かいことは気にしません。2番目のアームを見ると、次のような形に一般化できそうです。

f x = g (f (h x))

上の場合では、g x y = x * y, h x = x - 1です。そして、階乗の末尾再帰であれば僕にでも書くことができます。

factorial_ n acc =
  case n of
  0 -> acc
  _ -> factorial_ (n - 1) (n * acc)

factorial n = factorial_ n 1

ここから末尾再帰の一般化を考えると、

関数f x = g x (f (h x))に対して、適切なyを選ぶことでf1 x y = f1 (h x) (g x y)の形に変形してもよい

といえそうです。これが一般に成り立つかどうか、僕には分かりません。バカなので……。
反例があったとしても、上のgroupByで成り立てばよいのです。

f1 = groupBy f
h = List.filter (\y ->  f x /= f y)
g xs ys = List.filter (\y -> f x == f y) xs :: ys

を当てはめてみると、

groupBy_ f (x::xs) acc = groupBy_ f (List.filter (\y -> f x /= f y) xs) (List.filter (\y -> f x == f y ) (x :: xs) :: acc

のようにできそうです。replなどで確かめてみると、どうやら正しい結果が得られているようでした。なんだかこのままだと読みにくいですが、なんとなくやっていることは分かるんじゃないでしょうか。

賢い人であればすぐに末尾再帰にできたでしょうが、そうでなくても何とか力技で末尾再帰ができました。そもそも慣れの問題もあり、手続き型の言語であればもっと簡単に書けたのかも知れませんが、タイトルに書いたようなことを思ったので記事にした次第です。

Mac OSの日本語入力とプリウスのシフトレバー

いいかげんなことを書くための免罪符になるか分かりませんが、前もっておことわりしておきます。
デザインの知識などは持ち合わせていないため、以下はあくまでも素人目線で見たときの話です。自動車の運転免許は取得したものの、殆ど乗っていないため車の運転に関してもほぼ素人です。工学に関する知見も持っていません。そういうつもりでお願いします。

Mac OSといえば、洗練されたデザインとか直感的なUIとか、そういう言葉で褒められることが多いと思います。直感的かどうかはさておき、Windowsマシンにも付いていればいいのになあ、と思うキーが「かな入力」と「半角英数入力」のキーです。

Appleのページへのリンク

スペースバーの両側にあるものです。「かな」を押すとかな入力、「英数」を押すと英数入力になります。 Windowsでは「全角/半角」ボタンでトグるようになっているのとは違います。

トグルスイッチが悪いという訳ではないのですが、入力し始めるときに「今はどちらの状態か」を気にしないといけないのが僕にとってはストレスなのです。Macの方式のよいところは、現在の状態にかかわらず、とりあえず目的の入力モードのキーを打鍵して入力をはじめればよい所にあると思うのです。 状態が明確に分かるものにトグルを採用することは悪くないと思います。部屋の電灯やテレビなどの電源であれば、何のストレスもなくトグルスイッチを使うことができます。

ちなみに、僕は入力メソッドにSKKを使っています。<C-j>を日本語入力、<C-l>を半角英数の入力にあてています。これもショートカットを誤爆させることがあり地味にストレスなのですが、良い解決方法を見つけられずにいます。何かいい方法ないですかね……。

閑話休題プリウスのシフトレバーの話です。
プリウスといえば「見たら逃げろ」だの「プリウスミサイル」だのと散々な言われようです。コンビニに突っ込んだり病院の駐車場から転落したりと、事故が多い印象ゆえの不名誉な渾名ですが、原因としてシフトレバーが指摘されることがあるようです。

プリウス シフトレバー」でGoogle検索して上位にヒットした記事のリンクです。予測変換で「わかりにくい」とサジェストされるくらいなので、そう感じている人は多いのでしょう。シフトレバーの操作の分かりにくさの原因として、例えば2つ目の記事では

  1. シフトポジションがレバーの位置で表示しない為、どこに入ってるかわからない
  2. 手応えが従来のシフトレバーと全く違う為、操作した感がない
  3. シフトを動かしても、その場所でレバーが固定されない為わかりにくい

ことが挙げられています。

既存の車ではシフトレバーの位置で現在の状態を確認していたのに対し、シフトレバーが元の位置に戻ってしまうために分かりにくいということなのですが、 殆ど車に乗らない僕にとっては、こちらの方が分かりやすいように思えるのです。実際、同じようなことを書いている人がいました。

プリウスのシフトレバーがわかりにくい?そんなことはないと思う。

上でMacの入力メソッドの切り替えを褒めましたが、プリウスのシフトレバーも同じような設計哲学を持っているのではないかと思うのです。つまり、ある状態への遷移の操作が現在の状態に依存しないという特性を持たせるために、あえて「操作後にシフトレバーが元の状態に戻る」という設計にしているのではないかということです。上記の記事でも触れられていますが、

ストレート式を含めた他のAT車は、Pに入ってる時にRにしたければ下に動かし、DからRの場合は上に動かす。
こっちのほうがややこしくない?まずどっちに入ってるか認識してからどちらかに動かすか考えなければならないもの。

これは「シフトレバーの位置によって現在の状態を示す」ように設計した弊害と言えるでしょう。インジケータとコントローラを分離しなかったがために、運転手が行うべき操作が状態に依存するようになってしまいました。プリウスのシフトレバーはこの点を見事に解決していると思います。 個人的にはPに入れるためにボタンを操作する点は分かりにくい気もしますが、触るのは発進時と停車時だけなのでまごついてもおおよそ問題はなく、安全面の問題にはならない気がします。

既存の車はシフトレバーについて一貫した設計哲学を採用してきました。哲学と呼ぶほどのものはなかったかも知れませんが。プリウスのシフトレバーの「分かりにくさ」は、既存の仕組みに過度に適応してしまった人間側の問題であるような気がしてならないのです。

復帰

このブログのことは完全に頭から離れていたのですが、つい最近思い出したのでまた気まぐれを起こして記事を書いています。

前回の更新は5年前でした。

このとき僕はまだ大学院生で、あまり優秀でもなく、堕落した日々を送っていました。
それは今も変わらないのですが……。

その後、大学院をお情けもあって何とか修了し、企業に就職することになりました。いわゆるバイオインフォ業界です。学生の時分にプログラミングは少し勉強したのですが、バイオインフォ専門というわけでもなく、CSの知識は今でもあまりありません。

そうは言っても少しくらいは覚えたこともあるものです。趣味も兼ねて幾つかの言語を覚えて仕事でちょっと使ったり、他にも仕事で使うための勉強なんかもやったりしました。データ構造とかアルゴリズムも少しはかじっています。5年前よりはプログラミングもできるようになっていることでしょう。そう思って昔の記事を読み返してみると、分からないなりにも奮闘していた痕跡が見られて面白いと思いました。

*.ab1は次世代じゃない、サンガーシーケンサが吐き出すデータです。何でこんなものを読もうとしていたのかもう思い出せませんが、なぜかC言語で頑張っていた記憶はあります。 確か、Pythonみたいな言語ではバイナリデータは読めないというような誤解をしていたような気がします。ファイルポインタと (メモリアクセスのための)ポインタを混同していたのかも知れません。

ループも1回足りなくてデータが読みきれなかったり、逆に多すぎてsegfaultを起こしたりしてたような気がします。案の定途中で挫折してるっぽくてウケますね。 妙に見出しが多いのはMarkdownを覚えたてだったからだと思います。当時の僕がこの文章を読んだら恥ずか死することでしょう。

今後、毎日とは言わないまでも、ちょくちょく更新しようとは思っています。 勤め人なので業務にかかわることは大っぴらには書けませんが、一般的なこととか勉強のメモくらいなら良いでしょう。

次の記事も5年後かも知れません。

ab1ファイルを読み込みたい(3)

めっちゃ更新遅れました。1年前くらいじゃないのかな。 理由は、まあ察して下さい。

読み込んでもデータの読み方が分からなかったんですぅ。
でも一応、tdirっていうディレクトリについて書いておきますね。


tdirとは

他のディレクトリを格納するディレクトリです。従って、はじめにこのディレクトリを見てあげないことにはお話になりません。でも別にそんな難しくないっす。

構成

  • name

"tdir"です。いわずもがな。

  • number

別に要らない情報だと思うんですが、多分1になってます。
使うことはないでしょう。

  • elementtype

これも要りません。多分、1023になっている。

  • elementsize

28です。一つのディレクトリは28 byteのサイズを持っているからです。

  • numelements

多分重要です。子ディレクトリの個数。

  • datasize

28 * numelementsに等しいはずです。僕は使いませんでした。

  • dataoffset

ここから各ディレクトリの情報が始まっています。"tdir"ディレクトリを読んだあとはファイルの先頭から(dataoffset) byte目に飛んで、各ディレクトリがどこにあるかを探していくことになります。

仕様書にもある通り、N番目のディレクトリは (dataoffset) + 28 * (N - 1) byte目から始まります。


で、読み込んだけど

結論から言えば、ApEなんかでhage.ab1を開いた時のあの波形は得られません。

DATAディレクトリに格納されている生データに対して、basecallという作業をする必要があるのでした。

生データは各チャンネルから得られた蛍光強度です。ここから塩基配列を決定する作業をbasecallと呼ぶようです。そのためのソフトウェアはbasecallerと呼ばれるようです。

現在、もっともよく使われているものはphredというbasecallerで、どっかの大学の研究者が開発したようです。ライセンスに関する記述を読んで、
「ライセンスに同意するからphredちょうだい!」
というメールを送ると、Cか何かで書かれたソースがドンッと送られてくるようです。

ほらよ。勝手にコンパイルして使え。

もっと優しくしてよ……って思いますけど、まあ仕方ない。
で、あの波形を得ることは諦めたので、モチベーションが石になって砕け散ったというわけでした。

実は、phred以降も新しいbasecallingの手法は開発されています。

PubMedで"basecall"って検索かけると"baseball"の結果が大量に出てくるんですが、めげずに"basecalling"とか"basecaller"ってクエリを打ち込めば出てきます。

隠れマルコフモデルとかベイジアン何とかみたいなヤツが大量にヒットして、 全体的に難しそうなので僕は「もういいや」ってなりました。

ab1ファイル自体は一応読めたので、もうこの件は忘れてゆるーくblog続けていこうと思ってます……。

ab1ファイルを読み込みたい(2)

早くも飽き始めているけれど、書いていきます。
誰も読んでいないかも知れないけど、自分のモチベーションを保つためにも一応。

確か前回は、ヘッダについて書いたように記憶している。
今回はディレクトリのお話。


ディレクトリの構成

恐らく全てのディレクトリは、以下の形式で保存されている。 C-likeな擬似?コードが仕様書にあるので、転載。

struct DirEntry{  
  SInt32 name; //tag name  
  SInt32 number; //tag number  
  SInt16 elementtype; //element type code  
  SInt16 elementsize; //size in bytes of one element  
  SInt32 numelements; //number of elements in item  
  SInt32 datasize; //size in bytes of item  
  SInt32 dataoffset; //item's data, or offset in file  
  SInt32 datahandle; //reserved  
}

SInt16, SInt32はそれぞれ16byte, 32byteの符号付き整数を表す。
それぞれの変数の意味する所を、簡単に書いていこうと思う。

name

ディレクトリの名前。そのままですね。仕様書にはtag nameとある。
またしても公式のドキュメントの最後の方に、nameとデータの内容についてのまとめが乗っているので、見ておいて頂きたい。

number

tag numberだそうだが、何と説明すればよいものかよく分からない。
というか、僕があまり理解していない。 少なくとも、name属性と対応付けて考えなければならないということは分かる。

例えば、
name = "DATA"
の時、
number = 1
及び
number = 2
はそれぞれ、channel1, channel2の生データとのこと。パーサを書くときに必要になる筈。
因みに、signed intとなっているが、慣習的に値として1000より小さい正の数しか取らない。
故に、1から始まる。

elementtype

含まれるデータの型を表す番号。
例えば、19ならばC言語の(null文字が末尾についた)文字列を表す。
仕様書のp.13から対応表が載っている。

elementsize

データの要素のサイズ(byte)。elementtypeによって規定されるので、これはredundantな情報だと思うのだが。
念の為にelementtypeとのconsistencyが取れているか確認するコードくらい書いてやってもいいかな。
先ほど例に上げたC言語の文字列なら、elementsizeは1であることに注意して欲しい。
データのサイズを表すものではない。

numelements

配列の長さといった所だろうか。
elementtype = 19
なら、文字列の長さ+1(null文字分)となる筈だ。

datasize

データ全体のサイズ(byte)。
原理的にelementsize * numelementsとなるはずで、コレもredundantだと思う。

dataoffset

データの開始位置をbyteで表す数。先頭からx byte目からこれこれのデータが格納されていますよ、という値。5以上になるはず。先頭4byteは"ABIF"だもんね。

datahandle

多分どうでもいい。予備の領域で、nullがギッシリ詰まっているはず。無視しよう。


注意すべき点が1つある。
基本的には上に書いた通りだが、datasizeが4以下の時には、そのディレクトリ専用の領域は確保されず、dataoffsetの中身がそのままデータになっている。
例えば、
elementtype = 19
の時、データ型はC言語での文字列であるので、中身が文字列"AB" であるならば、
dataoffset = 0x41420000
となっている筈だ。

実際にアプリケーションを開発するときは、

fseek(fp, dataoffset, SEEK_SET);
if (datasize > 4){
    fread(buf, elementsize, numelement, fp);
} else {
    buf = dataoffset;
}

的な感じで。やればいいのかな。
その後でnameやnumberを見て、何のデータなのか判別しようと思っている。

tdirについても書きたかったんだけど、長いから次にします。