Elixir: bitstring を再帰で作るパターン
bitstring を accumulate する手続きには癖がある、という話。
ACGT を2ビットで表すとして、対応表をこうおく。
nucleotide | bits |
---|---|
A | 00 |
C | 01 |
G | 10 |
T | 11 |
で、引数として渡される charlist を bitstring に変換する関数 DNA.encode
を定義する。先に動くコードを書くとこうなる。
defmodule DNA do def encode(dna) do_encode(dna, <<>>) end defp do_encode([], encoded), do: encoded defp do_encode([head | tail], encoded) do do_encode(tail, <<encoded::bitstring, encode_nucleotide(head)::2>>) end defp encode_nucleotide(code) do case code do ?A -> 0b00 ?C -> 0b01 ?G -> 0b10 ?T -> 0b11 end end end
<<encoded::bitstring, encode_nucleotide(head)::2>>
で bitstring をコンストラクトする、という表現がとにかく難産だった。
まず ::2
を書き忘れると、これは単に Integer の表現として評価されてしまうので、エラーこそ出ないが padding zero が落ちてしまう。
encoded
が bitstring なのは自明だから、と思って
<<encoded, encode_nucleotide(head)::2>>
とすると非自明な ArgumentError
が出る。これはなにも教えてくれないので、デバッグのしようもない。
迷走して、 <>/2
を使おうとしても、ここで扱おうとしている binary は 2-bit であるから、「8-bit にしろ!」と怒られてしまう。
苦心惨憺して、動くコードを書けはしたが、正直言って数撃ちゃ当たる戦法の試行錯誤がたまたまヒットしたというだけであって、ちゃんとドキュメントから演繹的にたどりつくことができたわけではない。
かなり苦しい時間を費やした。悪態をつかなかったとは言わないが、「でも誰に向けて?」というやり場のなさを、こうして吐き出すことで弔うことができればと思う。
新しいルールを学ぶというのは本質的にこういう経験の繰り返しであったな、ということを思いつつ、さじを投げなかった自分を褒めたい気持ちもある。
少なくとも、ここでひとつのパターンを手痛い思いをしながら見つけたので、これは繰り返さないように戒めとしておく。