Magnolia Tech

いつもコードのことばかり考えている人のために。

属人性をどう捉えるか?

「職場の属人性を排除して、誰でも同じ作業ができるようにしよう」みたいな話、よく話題になる。

でも、そのタスクのフェーズによって「属人性」が表す意味は変わってくる。

その作業が、その組織にとって完全にコモディティ化していて、誰でもできないと困る作業(会社のルールとして明確に定義されている事務手続きとか)は、特定の人しか知らない、できない、というのは確かに好ましくない。また、その作業自体が、一定以上の量が有り、多くの人数を揃えないといけないような特性が有る場合も、ムラが起きないようにしないといけない。

だけど、その人しか生み出せない”価値”に対して「属人性」というラベルを貼るのは違うんだけど、もう一方でその価値が一度発生すれば、ずっとそのままか?というそれも違ったりする。

また、別の価値を生み出すためには、どこかでそのことを他の人に委譲する時が必ず出てくる。

「属人性」という言葉は、割とネガティブに使われることが多いし、強い言葉なんだけど、うっかり尊重しないといけない「価値」に対して使わないように気をつけないとなって思った。


追記:onkさんのツイートをみて、ハッとした、それだ!

もう「自動化」って言わなくてよくないですか

以前noteに書いてた記事の転載

—-

なんとなく、「自動化」って言葉のニュアンス、“今の状態にわざわざ苦労して付け加えてやること”みたいな、特別感出していませんか。

それより、わざわざ”手作業”でやる方を「手動化」って言った方が、よくないですか?

「え?それ手動化でやるんですか、まだまだ人間じゃないと判断できない価値が有る作業なんですねー」みたいに。

どっちを特別視するか?って時に、より「当たり前の方」にはわざわざ名前つけないですよね。特別だと思っているから名前付けるので…だから、手作業の方に名前を付けた方が、その価値に気づきやすいんじゃないかって。

手作業の方が、直接的な作業時間以外にも、さまざまな意味でのコストがかかっていて…例えばどんな些細なことでも「判断する行為」は人の関心量を削って行くんですよ。

そもそも「自動化」の価値って、やってみると案外”自動でやってくれる”ことより、「作業フローが可視化される」「判断基準が明確になる」みたいなところだったりしますしね。そうすることで属人性を排除すべきところと、そうでないところが明確になるんですけど、これ自動化じゃなくてもやらないといけないことですよね。

基準が明確になってしまえば、作業が分離できるんですよ。

任せられる。

人の関心量には限界が有るので、少しでも関わった以上は、その関心量の消費が必ず一定量有るんですよ。その直接的に手を動かした以上に、関心量の消費が色々な所に効いてくるんです。なのでとにかく関心を分離して、任せられる状態になっていることに価値が有るんじゃないですか。

現代的なプログラミングの基本は、「関心の分離」です。より集中すべき所に集中すべき時に、まずは作業じゃなくて、関心を分離する方法を考えるといいんじゃないかって。

自動化でも手動でも大事なことは変わらない訳です。そこに気づくために、あえて手作業こそを特別なこととして、「手動化」って呼んでみるといいんじゃないかと思った話です。

Scalaでバイトコードに出力されるparamterにドットが含まれることへの対応

docs.oracle.com

4.2.2. Unqualified Names

Names of methods, fields, local variables, and formal parameters are stored as unqualified names. An unqualified name must contain at least one Unicode code point and must not contain any of the ASCII characters . ; [ / (that is, period or semicolon or left square bracket or forward slash).

なるほど、仕様書にそう書かれているのか。

先日のエントリでなぜそんなチェックをしているのか分からないと書いたけど、Javaの仕様書には書かれていなかった、JVM側の仕様に起因していた。バイトコードだからそりゃそうか。

blog.magnolia.tech

Scala側にissueをあげた

It is possible to set a parameter name to a method parameter that is illegal in Java · Issue #12306 · scala/bug · GitHub

そうしたら、早速PRが作られた。JVMの仕様と合っていないから、直すのが適切だろうと。

Parameter names are encoded by som-snytt · Pull Request #9435 · scala/scala · GitHub

なにげなく立てたけど、改善に役になってよかった。

ちなみにDotty(Scala 3.0.0)はバイトコードにパラメータ名を設定しないことが分かった。issueをあげようかな。

Re: 一冊の本をじっくり読み込み、知識を吸収するためにはどうすればいいのか

blog.magnolia.tech

先日、公開したエントリがわりと反響が有って、かなりのブクマを集めた。そして、ブクマのコメントで皆さんの色々な読書法が集まり、まさに”集合知"という感じで知見が得られたのが有り難かった。

b.hatena.ne.jp

ただ、前回のエントリの冒頭にちゃんと書いておけばよかったと思ったのが、自分もすべての本をあのような読み方をしているわけでは当然なくて(時間がいくら有っても足りない)、特に技術書などで「これはじっくり読みたい」と思えるものを、どうやって中断せず、関心を継続し、深く読み込むか?という観点で書いたエントリなのです。

また、何人かの方が言われているように「何度も読む」という方法は定番ですが、自分には向いていなかったですね(最近の技術書、分厚いので何度も読めないですしね…)。

人それぞれいろんな読み方が有りますね。


いろいろな読書法に関する書籍を教えていただいたので、こちらも読んでみたい、と思いました。どれも全然知りませんでした。

知的戦闘力を高める 独学の技法

知的戦闘力を高める 独学の技法

学術書を読む

学術書を読む

また、「解釈学的循環」という言葉が非常に興味深かったですね。

解釈学的循環 - Wikipedia


引き続き、こうやって読み込んでいる!というやり方があれば教えてください!

コメントやブクマ、Twitterへのリプライ、DMでも大丈夫です!

twitter.com

Scalaのメソッドのパラメータ名をリフレクションで取得する

長い人生、生きていれば、Scalaで実行時にメソッドのパラメータ名を取得したくなることが一度や二度有りますね。

そんな時のためのやり方のメモです。

package example

import scala.reflect._
import com.thoughtworks.paranamer.{BytecodeReadingParanamer, CachingParanamer}

case class Person(name: String, age: Int)
case class Company(会社名: String)
case class Country(`country-code`: String)
case class Planet(`star.cluster`: String)

object GettingParameter {
  val paranamer = new CachingParanamer(new BytecodeReadingParanamer)

  def main(argv: Array[String]): Unit = {

    println("Inspect Person's parameter by Paranamer")
    inspectByParanamer[Person].foreach(println)

    println("Inspect Person's parameter by Java 8 reflection")
    inspectByJava8Reflection[Person].foreach(println)

    println("Inspect Company's parameter by Paranamer")
    inspectByParanamer[Company].foreach(println)

    println("Inspect Company's parameter by Java 8 reflection")
    inspectByJava8Reflection[Company].foreach(println)

    println("Inspect Country's parameter by Paranamer")
    inspectByParanamer[Country].foreach(println)

    println("Inspect Country's parameter by Java 8 reflection")
    inspectByJava8Reflection[Country].foreach(println)

    println("Inspect Planet's parameter by Paranamer ")
    inspectByParanamer[Planet].foreach(println)

    try {
      println("Inspect Planet's parameter by Java 8 reflection")
      inspectByJava8Reflection[Planet].foreach(println)
    } catch {
      case e => println(e)
    }
  }
  
  def inspectByParanamer[T](implicit m: ClassManifest[T]): Seq[String] = {
    paranamer.lookupParameterNames(m.erasure.getConstructors.head).toSeq
  }

  def inspectByJava8Reflection[T](implicit m: ClassManifest[T]): Seq[String] = {
    m.erasure.getConstructors.head.getParameters().map(_.getName).toSeq 
  }
}

Paranamer

Paranamerは、Java8より前の時代に使われていたメソッドのパラメータ名を取得するためのライブラリです。当時のバイトコードにはパラメータ名は出力されていなかったので、LocalVariableTableから取得していました。ただし、LocalVariableTableに存在するのは正確にはパラメータ名ではなく、パラメータで引き渡された値が格納される変数名です。

github.com

Java 8 Reflection

Java8ではパラメター名を取得するためのメソッドが用意されました。また、パラメータ名を格納するようにバイトコードも拡張されました。

irof.hateblo.jp

Scalaにおける注意事項

Scalaではメソッドのパラメータ名に、Javaでは使用できない記号が使える。

www.ne.jp

ただし、バイトコード上では名前が変換される(scala.reflect.NameTransformerが使われている)。

しかし、パラメータ名には変換前の、Javaでは使えない識別子が格納される。先程のソースを実行すると、以下の結果となり、Java 8ではソースコードの記載の通りのパラメータを取得しているのが分かる。

Inspect Person's parameter by Paranamer
name
age
Inspect Person's parameter by Java 8 reflection
name
age
Inspect Company's parameter by Paranamer
会社名
Inspect Company's parameter by Java 8 reflection
会社名
Inspect Country's parameter by Paranamer
country$minuscode
Inspect Country's parameter by Java 8 reflection
country-code
Inspect Planet's parameter by Paranamer
star$u002Ecluster
Inspect Planet's parameter by Java 8 reflection
java.lang.reflect.MalformedParametersException: Invalid parameter name "star.cluster"

しかし、最後のパターンが例外を送出している。これは以下のコミットにより、パラメータ名に不正な文字が混入している場合は例外を送出するようにチェックがかかっている。

8020981: Update methods of java.lang.reflect.Parameter to throw corre… · openjdk/jdk@367fa5a · GitHub

チェック対象の文字として、.;[/の4文字…シグニチャで使われる文字が選定されているが…今ひとつ理由は分からない…パラメータ名にシグニチャで使われる文字列が混入するのか??その理由は探せなかった。

また、これ以外にもScalaのTypeTagというリフレクションの仕組みを使ってもパラメータ名は取得できるが、結果はParanamerと同じだったので、おそらくLocalVariableTableから取得しているように見える。

おわりに

Scalaでは有効なパラメータ名なのに、Javaのリフレクション経由だと不正と言われてしまうパターンが存在することが分かった。

Json4sの中でParanamerを使っている箇所をJava8のリフレクションで置き換えられないか、試してみると意味不明な例外が出たので解析したら、上記のことが分かった。とくにいますぐParanamerを止める理由も無いが、いつかJava8のリフレクションに置き換えたい、というソースコードに書かれてたコメントのとおりにやってみようと思ったけど、できなかった、という記録です。

Javaのチェックが意味不明なので、そっちを直すべきのように思えるけど、非常に特殊な状況でのみ顕在化するので、そのままでもいいかな。

一冊の本をじっくり読み込み、知識を吸収するためにはどうすればいいのか

blog.shibayu36.org

先日、id:shiba_yu36さんのブログで、同じジャンルの本を複数同時並行で読み、気になったキーワードを繰り返し選別していって、読書ノートにまとめることで知識の吸収速度を上げる、という内容のエントリが話題になっていた。


確かに同時並行で同じジャンルの本を読むことで、同じキーワードでも複数の視点で考えるきっかけになって、より理解し易いという効果が有ると思う。一方で、自分は元々一冊の本をじっくり最後まで読み切るのが苦手で、だんだんと読み方が雑になって、後半は流し読みくらいになってしまうことがよく有る。

では、どうすれば1冊の方をじっくり読み切って、かつ知識を吸収することができるか、ということを考えてみた。


  • 「目次」と、「はじめに」をじっくり読む
  • 前半部分は並列に読む
  • 中盤まで行ったら最初に戻る
  • メモできる環境を用意する
  • 分からないキーワードは都度調べる
  • 構造化しながら読む
  • 感想を雑に書く
  • 自分の経験とリンクさせる
  • 実践する
  • 誰かと一緒に読む、誰かに感想を話す
  • 一度に読み切ろうとしない
  • 関連書籍を読む

「目次」と、「はじめに」をじっくり読む

ある程度の分量の文章を書いた経験が有る人であれば分かるけど、いきなり最初から書き始めて最後まで一直線に書き切ることはまずない。全体の構成を考え、トピックの順序や、ボリュームを調整しながら書いていく。その工程が一番強く、ダイレクトに「目次」に現れるはずなので、まずは「目次」をじっくり読むことで全体を俯瞰する。

また、当然だけど「はじめに」みたいな導入部分には書かれている内容や、その書く上での背景・動機、想定読者など、色々な基礎情報が詰まっているので、それもしっかり読み込んでみる。

この時点で、「読めない」と思ったり、あまり読む気が起きなければその時点で読むのを打ち切る。

前半部分は並列に読む

特に技術書に多いのだけど、ある程度の種類の概念やキーワードの理解が揃って初めて最低限の理解が始まることがある。

いくら基礎的な所から始まっても、それらが統合して理解できていないうちは、どんな簡単な、基礎的なトピックでも頭に入ってこないときがある。こんな時は、元々そうゆうものだと割り切って、特に基本的な概念や、基礎的な機能が解説される前半は、各トピックを並列で少しずつ読み進める。

あくまで、本来並列で同時に理解すべきものが直列に並んでいる、書籍というフォーマットの特性、制約なんだと思い、決して理解力が不足しているなどと思わないこと。

中盤まで行ったら最初に戻る

前半を終わって、本を読み進めるための統合的な理解ができたら、最初に戻ってもう一度読み直してみる。理解度が変わった状態で読むと、最初に読んだときと全然違う理解や、吸収が発生する。

つまり…

1章→2章→3章→

ではなく…

1章  ->
2章  ->  -> 統合理解 -> 一度最初に戻る
3章  ->

という流れで読む。

どこまで行けば中盤かは、本によるけど、だいたい1/3過ぎまで一気に読むのは、進み過ぎと言える。

せいぜい2割くらいで一度止めてみる。

メモできる環境を用意する

手書きでも電子的にでも、必ずメモできる環境を用意する。一つのトピックが終わって、メモが無かったら、やり直し、くらいの気持ちで。

分からないキーワードは都度調べる

まずはその書籍の用語集は必ずチェックする。ネットの検索の前に、キーワードをメモに一度書き出すことを忘れずに。検索だけして分かった気になって、結局分かっていない、ということが多々ある。

構造化しながら読む

書籍自体はフォーマット上、直列に進むが、一定の分量が有れば、そこには必ず「構造」があるはず。

その構造を読み取り、例えば四角と矢印だけでもいいから、各トピックの「関係」に着目して図式化しながら読むことで理解が立体的になる。

関係を書き出せないとしたら、まだ理解が浅い、足りていないと言える。

感想を雑に書く

感想を雑にツイートしたり、ブログに書いたりすることで、理解度を自分で確かめることができる。

誰かに感想を話そうとすると、相手を意識しすぎて構えてしまうので、一方的に書ける方法が良い。

自分の経験とリンクさせる

メモや、感想を書き出すときに、なんでもいいので、必ず自分の過去の経験とリンクさせて書いてみる。

そうすると、より深く考えられるので、記憶に残り易い。

実践する

思想書でも技術書でも実践してみることで理解が深まる。実際の行動に落としてみる。技術書であればコードを書いてみる、サービスを動かしてみる。書かれている通りにやる、独自に変えてやる(別の言語で実装する、もある)……いろいろなアプローチがある。

ただ、マネージメント論みたいな、相手が有ってこその場合は、そのまま実践できないこともあるし、あからさまに書籍の通りにやることが逆効果になる場合もある(相手がネタバレで興醒めすることもある)。そのような場合は、敢えて「◯◯という本に書かれていたことを試しにやってみます」と宣言してやる方法もある。

そもそも全然実践できる環境ではないこともある(経営していないけど、経営論を読んでいる時とか)。そんな時は、自分だったらどうするか、想像してみるくらいか。

誰かと一緒に読む、誰かに感想を話す

読書会などを開いて、誰かと一緒に読むことで自分以外の視点や論点が引き出せる。ただし、その本に対する関心度合いは、人によって違う点に注意。

感想ブログや、書評ブログを眺めるだけでも、良い。

また、誰かに感想を話すために整理することで、より理解が深まることがある。読書会のような、その本に対する関心レベルが一定以上揃っていない場で話をするときは、相手の興味の具合に合わせてほどほどに。

本の感想を他人に話すだけの会話で、あまり長い会話にならないと思えるので、ほどほどが良いかも(一方的に本の内容だけを語られても…ということもある)。

一度に読み切ろうとしない

ここまで書いてきた内容を実践しようとすると、1冊の本を読むために相当の労力を費やすことになる。無理をせず、一度に読み切ろうとせず、少しずつペースを決めて読んでいく。

例えば300ページくらいの技術書だったら3ヶ月以上かかって当たり前、くらいの心構えで。

関連書籍を読む

結局一冊の本だけでその分野のことが深く理解できることはあまり無いし、重要なテーマであれば関連する書籍も多数出ているはずなので、1冊をじっくり読んだら、そこを出発点に関連書籍を拾い読みしていく。


読み返してみて、まずは本を読む時間を作ることが一番大事なのかもしれないなって思った。

皆さんの読書法をぜひ教えて下さい。コメントとか、引用ツイートとか、ブクマに書いてもらえると嬉しいです。片っ端から返信します。

OSSライブラリからの学びかた CPANからPerlのライブラリを探してソースコードを見つける編

ledsun.hatenablog.com

なるほど、これは素敵なアンサー記事。実際にどうやってOSSから学びを得るか、分かりやすい。

しかし、そうなると元々PerlCPANから学んだ、と書いた手前、CPAN & Perl編を書かない訳にはいかない。

ほかの言語用は誰か書いてください!!!!


CPANからPerlのライブラリを探してソースコードを読む

例えばHTMLエスケープの実装が知りたいとき

元の記事と同じようにPerlでHTMLをエスケープしたいけど、どう実装したら漏れなく対応できるかわからない状況を仮定しましょう。

CPANを検索

Perlのモジュールリポジトリといえば、おなじみCPAN(Comprehensive Perl Archive Network)です。

やはり元の記事と同じように、雑にhtmlescapeで検索してみます。

f:id:magnoliak:20210108234500p:plain

検索結果の最上位は、HTML::Escape - Extremely fast HTML escapingと出ています。名前も分かりやすいし、「極めて速い」という説明からも良さそうですね。 モジュール名の右側に有るメーターは、どれだけ他のモジュールから利用されているかを示しています。その横の数値は、所謂「いいね」の数です。

ざっと他のモジュールよりも評価が高そうです。早速、このモジュールを見てみることにしましょう(やはりnpmに比べると同じようなモジュールが出てくる率が低いですね)。

metacpan.org

GitHubで実装を探す

先程の検索結果の左側に、リポジトリへのリンクもあります。そこからたどりましょう。

https://github.com/tokuhirom/HTML-Escape

f:id:magnoliak:20210108235305p:plain

Perlのモジュールは、モジュールのディストリビューションのハイフンをスラッシュに置き換えてパスを作り、最初の単語の前にlib/をくっ付け、最後の単語の後ろに.pmをくっ付けると、エントリポイントが得られます。つまり、lib/HTML/Escape.pmがこのモジュールのエントリポイントになります。

あ、いきなりモジュールの中身を見る前に、ドキュメントはしっかり見ておきましょう。先程の検索結果のHTML::Escapeの部分をクリックするとドキュメントを参照できます。

https://metacpan.org/pod/HTML::Escape

f:id:magnoliak:20210108235930p:plain

ここがCPANの最高なところなのですが…「モジュールの作者名が一番最初に表示されている」「作者にアイコンが大きく表示されている」…作った人の顔が見えるのがCPANのいいところ。このモジュールは、Perlで数々の素晴らしいモジュールをリリースされているTOKUHIROMさんの作であることが分かります。

また、Perlのドキュメントはフォーマットが決まっていて、例えば、モジュール名の次は、必ずSYNOPSISです。これを見れば、一番基本的な使い方がすぐに分かるように工夫されています。

use HTML::Escape qw/escape_html/;
 
escape_html("<^o^>");

とっても分かりやすいですね(ちなみにエスケープの結果は、&lt;^o^&gt;になります)。

さて、使い方が分かったところで実装を見てみましょう。

escape関数の実装を読む

エントリポイントとなるlib/HTML/escape.pmは、ドキュメントを除くとわずか28行のモジュールです。

https://github.com/tokuhirom/HTML-Escape/blob/master/lib/HTML/Escape.pm

コードの箇所を抜き出してみました。

package HTML::Escape;
use strict;
use warnings;
use 5.008005;
our $VERSION = '1.10';
use parent qw/Exporter/;

my $use_xs = 0;
if(!exists $INC{'HTML/Escape/PurePerl.pm'}) {
    my $pp = $ENV{PERL_ONLY};
    if (!$pp) {
        eval {
            require XSLoader;
            XSLoader::load(__PACKAGE__, $VERSION);
            $use_xs = 1;
        };
    }
    if (!__PACKAGE__->can('escape_html')) {
        ## no critic.
        require 'HTML/Escape/PurePerl.pm' # not to create the namespace
    }
}
sub USE_XS () { $use_xs }

our @EXPORT = qw/escape_html/;

至るところにxsXSというキーワードが出てきます。XSとは、PerlC言語拡張のためのインタフェース記述言語で、この機能によりC言語で書かれたコードをPerlから呼び出すことができるようになります。

また、'HTML/Escape/PurePerl.pm'というパスが有ることも分かると思います。これは名前の通り、Perlのみで実装されたHTML::Escapeの実装です。

Perlのモジュールには、Cコンパイラが使える環境であればXS経由でC言語で実装された高速バージョンが、何らかの理由でCコンパイラが使えない環境であれば(比較すると低速な)Pure Perlバージョンがインストールされるものが多数存在します。現代では、あまりCコンパイラの環境制約が有ることは少ないと思いますが、この仕組みによりレンタルサーバのようにCコンパイラが使えない環境でもモジュールがインストールできる、というわけです。

しかも、このように同じ仕様に対して二つの実装が用意されることで、一度に複数の言語による実装を参照することができる、というメリットが学習用に有ります。

Pure Perl実装

https://github.com/tokuhirom/HTML-Escape/blob/master/lib/HTML/Escape/PurePerl.pm

中心となるのは以下のコードです。

our %_escape_table = ( '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', q{"} => '&quot;', q{'} => '&#39;', q{`} => '&#96;', '{' => '&#123;', '}' => '&#125;' );
sub escape_html {
    my $str = shift;
    return ''
        unless defined $str;
    $str =~ s/([&><"'`{}])/$_escape_table{$1}/ge; #' for poor editors
    return $str;
}

正規表現を使って、一気に変換していることが分かるでしょう。あらかじめ、ハッシュに変換パターンを用意しておき、正規表現の中からPerl変数を呼び出すことにより、全ての文字をまとめて変換をかける、というテクニックを使っています。この辺り、Perlの強力な正規表現をうまく使っていますね(eオプションを指定すると使える機能です)。

XS実装

次にXS実装を見てみます。

変換結果は同じですが、だいぶ様子が違います。

https://github.com/tokuhirom/HTML-Escape/blob/master/lib/HTML/Escape.xs

static const char unsafe[256] = {
    /*                 0 1 2 3   4 5 6 7   8 9 a b   c d e f */
    /* 0x00 .. 0x0f */ 0,0,0,0,  0,0,0,0,  0,0,0,0,  0,0,0,0,
    /* 0x10 .. 0x1f */ 0,0,0,0,  0,0,0,0,  0,0,0,0,  0,0,0,0,
    /* 0x20 .. 0x2f */ 0,0,1,0,  0,0,1,1,  0,0,0,0,  0,0,0,0,
    /* 0x30 .. 0x3f */ 0,0,0,0,  0,0,0,0,  0,0,0,0,  1,0,1,0,
    /* 0x40 .. 0x4f */ 0,0,0,0,  0,0,0,0,  0,0,0,0,  0,0,0,0,
    /* 0x50 .. 0x5f */ 0,0,0,0,  0,0,0,0,  0,0,0,0,  0,0,0,0,
    /* 0x60 .. 0x6f */ 1,0,0,0,  0,0,0,0,  0,0,0,0,  0,0,0,0,
    /* 0x70 .. 0x7f */ 0,0,0,0,  0,0,0,0,  0,0,0,1,  0,1,0,0,
...
/* This is essentially a version of standard strcspn() that (a) handles
 * arbitrary memory buffers, possibly containing \0 bytes, and (b) knows at
 * compile-time which characters to detect, rather than having to build an
 * internal data structure representing them on every call. */
static size_t safe_character_span(const char *start, const char *end) {
    const char *cur = start;
    while(cur != end) {
        unsigned char c = (unsigned char) *cur;
        if(unsafe[c]) {
            break;
        }
        cur++;
    }
    return cur - start;
}

コンパイル時点でインデックスを作っておき、与えた文字列の中に対象があれば、一旦走査を中断して、置換とコピーを行い、再度走査を始める、という構成になっています。

while(cur != end) {
        size_t span = safe_character_span(cur, end);
        Copy(cur, d, span, char);
        cur += span;
        d += span;
        if(cur != end) {
            const char c = *(cur++);
            if(c == '&') {
                CopyToken("&amp;", d);
            }
            else if(c == '<') {
                CopyToken("&lt;", d);
            }
            else if(c == '>') {
                CopyToken("&gt;", d);
            }

実際に置換している文字は、(当たり前ですが)Pure Perl版と変わりません。

こうしてpure perlとXSで(当たり前ですが)、全然アプローチが違うのが面白いですね。

置換対象の文字について

と、書いてて気づいたのですが、よく見ると置換対象の文字が元記事のnpmモジュールと違いますね。

'{'、'}'、'`'の3文字が多い。

この差分は何でしょう?

調べて見ると、moznion氏が書いたC言語で書かれたHTMLのエスケープライブラリのコメントに良い情報がありました(これもまた違うアプローチで高速化されてて興味深いです)。

なるほど、これは知りませんでした、学びがありますね。

github.com

case '`':
  // For IE. IE interprets back-quote as valid quoting characters
  // ref: https://rt.cpan.org/Public/Bug/Display.html?id=84971
  memcpy(dst, "&#96;", 5);
  dst += 5;
  break;
case '{':
  // For javascript templates (e.g. AngularJS and such javascript frameworks)
  // ref: https://github.com/angular/angular.js/issues/5601
  memcpy(dst, "&#123;", 6);
  dst += 6;
  break;
case '}':
  // For javascript templates (e.g. AngularJS and such javascript frameworks)
  // ref: https://github.com/angular/angular.js/issues/5601
  memcpy(dst, "&#125;", 6);
  dst += 6;
  break;

おわりに

HTMLエスケープはいろいろな手法が有る分野らしく、先程のmoznion氏のライブラリに関連して、こんな記事が有ることを思い出した。奥が深い。

mattn.kaoriya.net