ユユユユユ

webエンジニアです

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 にしろ!」と怒られてしまう。

苦心惨憺して、動くコードを書けはしたが、正直言って数撃ちゃ当たる戦法の試行錯誤がたまたまヒットしたというだけであって、ちゃんとドキュメントから演繹的にたどりつくことができたわけではない。

かなり苦しい時間を費やした。悪態をつかなかったとは言わないが、「でも誰に向けて?」というやり場のなさを、こうして吐き出すことで弔うことができればと思う。

新しいルールを学ぶというのは本質的にこういう経験の繰り返しであったな、ということを思いつつ、さじを投げなかった自分を褒めたい気持ちもある。

少なくとも、ここでひとつのパターンを手痛い思いをしながら見つけたので、これは繰り返さないように戒めとしておく。