Ruby 2.4 の新機能, Array#sum

Ruby 2.4 で追加されている新機能のひとつに Array#sum があります。

(参: ドキュメントは http://ruby-doc.org/core-2.4.0/Array.html#method-i-sum

共立『アルゴリズム辞典』でも「総和計算」を立項しているように、情報落ち*1*2による「積み残し」が場合によっては著しいのが、対象が浮動小数点の時の総和計算の問題点です。

Array#sum は、Kahan summation algorithm(wikipedia:カハンの加算アルゴリズム、summation なので -総和アルゴリズム ないし -総和計算アルゴリズム、としたほうが訳としては正確)の改良版などの手法を実装しており、かなりの高精度な結果が得られます。

次のようなスクリプトで試してみると(実験に適した浮動小数点乱数を生成するため、拙作の RandomFloat を使用)、

# sum sample
require_relative 'random_float'

r_f = RandomFloat.new gt:1, lt:2**38

loop {
  arr = []
  (1000000).times {
    x = r_f.rand
    arr << x
    arr << -x
  }
  arr.shuffle!
  p arr.sum
}

そこそこ過酷な条件の総和計算ですが、次のように、ほぼ正確な結果を得られています(完全に正確ではない結果も、時折出ています)。

$ ruby-trunk -v sum_sample.rb
ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-freebsd10.3]
0.0
0.0
0.0
0.0
0.0
-1.3322676295501878e-15
0.0
1.4654943925052066e-14
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0

Python の math.fsum との違い

ネットを捜すと、Python の math.fsum との関連で議論されていることがありますが、両者は基本的なコンセプトの点で別物です。今回実装された Array#sum は、あくまでも「高精度」であり、誤差が発生する可能性自体はあります。一方(現在の?)Python の math.fsum は、たとえて言うなら、入力や途中の計算を全て Rational に変換してから行い(実際の実装は異なります*3)、最後に浮動小数点に戻したような「正確」な結果を返すもので、前述で見たような誤差が発生することはありません。

もしそのような「正確な総和」が Ruby でどうしても必要な場合は、Rational を使ってユーザが実装すれば良いでしょう。そのような sum も(私見では、現在の sum と別の実装として、違う名前でメソッドを追加すべきだと思っています)もしかしたら今後実装されるかもしれません(関連するチケットは存在しています)。

*1:正負の要素があると、桁落ちが発生することもありますが、総和計算における主要な問題は桁落ちではありません。むしろ、正しく計画された桁落ちであれば情報は欠けないのであり、情報が積み残されて消えてしまった、という「結果が露呈する(ことがある)」のが、総和計算における桁落ちです。

*2:この桁落ちに関しては、以前のPythonのドキュメントの和訳に、原文には相当するcancellationという語は実のところ無いのですが、手違いにより「桁落ち」という表現があったため、誤解している人ももしかしたらいるかもしれません。

*3:色々な手法が提案されています。

拡張ライブラリでキーワード引数を処理する定番プラクティス(ベストじゃないかも)

ruby 2.4 リリースおめでとうございます。つい先日まで「拡張モジュール」と「拡張ライブラリ」の、どちらの呼称がobsoletedなのかいまいちはっきり認識していなかったきしもとです(ClassクラスのスーパークラスであるModuleのほうのモジュールがあるので、「拡張モジュール」がobsoletedです)。

いつのまにやら内蔵ライブラリでも結構いろいろ使われているなど、一般的になっていたRubyのキーワード引数ですが、じつはつい先日まで、C API で提供されているユーティリティ関数にバグがあり、だいぶ悩ませられていた人もあったようです。

さいわい、良いタイミングで修正されて、本日リリースされた 2.4 からは普通に使えるようになっていますので、拡張ライブラリでキーワード引数を処理する定番プラクティス(ベストじゃないかも)をまとめておきます。

1. Rubyレイヤで処理できないか検討

いきなりタイトルから外れるようなことを書きますが、doc/extension.rdoc の英語版にはあるように(日本語版では省略されている)、

  Be warned, handling keyword arguments in the C API is less efficient
  than handling them in Ruby.  Consider using a Ruby wrapper method
  around a non-keyword C function.
  ref: https://bugs.ruby-lang.org/issues/11339

といったように効率上は、Rubyで実装されたメソッドに対するキーワード引数の処理のほうが効率がよく(参照先によれば数倍?)、プログラムの見やすさのこともありますから、キーワード引数はRubyレイヤで受けて、拡張ライブラリ側で処理するのは避ける、というのも一つの手です。

2. アリティの確認

ここから拡張ライブラリの話に入ります。

まずメソッド定義の C API であるrb_define_methodなどには、キーワード引数の扱いは現れてきません。rb_define_methodの場合、第4引数( argc )を -1 にして、可変長引数のメソッドとして定義します。

そして、Rubyでキーワード引数付きのメソッドを定義した場合、アリティ(引数の個数)が、定義側(仮引数)と呼出側(実引数)で一致しているか否かは、どちらの側もキーワード引数が無いものとしてチェックされています(必須なキーワードがある場合も)。ですから、拡張ライブラリの場合もそれに合わせます。rb_define_methodで登録すべき C 関数の骨組みは次のようになります。

/* 必須な引数の個数が 1 個で、キーワード引数も取るメソッド */
static VALUE
my_kw_method(int argc, VALUE argv[], VALUE obj)
{
    VALUE h;

    /* アリティチェックよりも前にキーワード引数の存在をチェックする */
    h = rb_check_hash_type(argv[argc-1]);
    if ( ! NIL_P(h)) {
        --argc;
    }
    rb_check_arity(argc, 1, 1);  /* 必須な引数は1個で */
        /* (キーワード引数以外の)オプショナルな引数は無し */

    if ( ! NIL_P(h)) {
        /*
         *
         * ここでキーワード引数取得の処理
         *
         */
    }

    /*
     * ここにメソッドの処理の本体
     */
}

3. rb_get_kwargs

キーワード引数の処理のために用意されている C API のrb_get_kwargsですが、先日までバグっていた上に、(仕様の変遷があったため)ドキュメントがコードと一致しなくなっていました。ですので、以前にドキュメントを読んで実装してハマったというようなかたは、まずドキュメントを読み直してみてください。

「ここでキーワード引数取得の処理」となっている部分のコードは、次のようになります。

    /* 必須キーワードが 3、省略可能が 2 個、余ったキーワードがあったらエラー */
    VALUE foo, bar, baz,  quux, hoge;
     :
     :
     :
        ID table[5];
        VALUE values[5];
        table[0] = rb_intern("foo");
        table[1] = rb_intern("bar");
        table[2] = rb_intern("baz");
        table[3] = rb_intern("quux");
        table[4] = rb_intern("hoge");
        rb_get_kwargs(h, table, 3, 2, values);  // ここの「2」は、余ったキーワードを無視するなら -3 に変える
        foo = values[0];
        bar = values[1];
        baz = values[2];
        quux = ( (values[3] != Qundef) ? (values[3]) : デフォルト値 ) ;
        hoge = ( (values[4] != Qundef) ? (values[4]) : デフォルト値 ) ;

ここで、デフォルト値についてはあらかじめ変数をその値に設定(初期化)しておいて、3項演算子ではなく if による分岐で書く、というようなバリエーションもありえますが、デフォルト値にオブジェクトの生成などがあると、無駄に余計なオブジェクトを作ってしまうことになります。

一方で、必須のキーワードが無い場合に、キーワード引数がまるごと無いと、この例のように書いてしまうと変数が未初期化のままになります。そういうわけで、そのあたりはケースバイケースで書き分ける感じになるでしょう。

4. 余ったキーワードの扱い

上記のコードでは、余ったキーワードがあると "unknown keyword" ArgumentError の例外が上がります。第4引数(省略可能なキーワードの個数)を 2 から -2-1 に変えると(-1は、ゼロ個の時のための調整用(でしょう))、余ったキーワードがあっても無視されるようになります。その場合、余ったキーワード(と値のペア)はハッシュ h の中に残されます(rb_get_kwargsは、処理できたキーワードを h から取り除きます)。

余ったキーワード(意図的に任意のキーワードを、意味をもって受け付けるような場合を除いて)の扱いをどうするかは、cruby開発陣の中で、まだプラクティスが確立していません(内蔵ライブラリや、標準添付ライブラリでもまちまちになっています)。もしこの件に関心があるようであれば、時々気にしてみてください(確かチケットには(まだ)なっていません)。

5. その他

values としてヌルポインタを渡すと、値を取り出さずにキーワード引数の存在のチェックのみを行います。

移植性が(ほとんど)あり性能問題が無い(かもしれない)、負の値に対応した算術右シフトのCコード

普通は、自分のマシンでどのようなコードが生成されるかを確認して、うまくいっていればそれで良しとする所ですが、右シフトは凝ったコードを書こうとするCプログラマにとって悩みの種です。

しかし、手元の環境( FreeBSD clang version 3.4.1 )で、次のような、「あらゆる環境で2の補数マシンをエミュレーションするコード」を、

(追記: ビットパターンが 0x800...000 のようになるような最小の負の値の時には、このコードでも「未定義」を踏んでいます)と思ったのですが、最初に +1 することで回避してますねコレ。それでも未定義に落とせる抜け穴あるかな?

int
signed_shift_right(int x, unsigned a)
{
    return ( (x < 0) ? (-(-(x+1)>>a)-1) : (x >> a) ) ;
}

-O3 オプション付きでコンパイルしてみたところ、次のように「完全に」最適化されました。

$ clang -W -Wall -Wextra -pedantic -O3 -S signed_shift_right.c
$ cat signed_shift_right.s
	.file	"signed_shift_right.c"
	.text
	.globl	signed_shift_right
	.align	16, 0x90
	.type	signed_shift_right,@function
signed_shift_right:                     # @signed_shift_right
	.cfi_startproc
# BB#0:
	pushq	%rbp
.Ltmp2:
	.cfi_def_cfa_offset 16
.Ltmp3:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
.Ltmp4:
	.cfi_def_cfa_register %rbp
	movb	%sil, %cl
	sarl	%cl, %edi
	movl	%edi, %eax
	popq	%rbp
	ret
.Ltmp5:
	.size	signed_shift_right, .Ltmp5-signed_shift_right
	.cfi_endproc


	.ident	"FreeBSD clang version 3.4.1 (tags/RELEASE_34/dot1-final 208032) 20140512"
	.section	".note.GNU-stack","",@progbits

static inline を付けて、インライン展開まで効けば、おそらく完全にオーバヘッド0のコードが生成されるものと思われます。

SICP的な状態を共有する複数のクロージャをJavaScriptでどう書くか

某展示会の学術系ブースで、このようなSICP的な状態を共有する複数のクロージャJavaScriptで書きたいわけだが、

"use strict"

var [getVal, setVal] = (function () {
    var v
    return [function () {
        return v
    }, function (new_v) {
        v = new_v
    }]
})()

setVal(42)
console.log(getVal())

JavaScriptの動作確認には node を使っています)

流石にこのように無名関数をバリバリ書こうとは思わないだろうので、それを解消する提案、というようなものを見ました。

提案されていた手法は、特別な記法でこの例の v のような変数をマークアップし、プリプロセッサのようなもので、もっとプレーンな書き方をしたJavaScriptから、このようなコードに変換する、というものでしたが、それについてはこの記事の本題ではありません。

Pythonで書くと、こんな感じになるでしょうか。

#! /usr/local/bin/python3

def mk_val():
    v = None
    def get_val():
        return v
    def set_val(new_v):
        nonlocal v
        v = new_v
    return get_val, set_val

get_val, set_val = mk_val()

set_val(42)
print(get_val())

Pythonではlamdaがあえてかなり使いにくく設計されており、このように明示的に手続きに名前を付けるスタイルになります*1

このPythonの場合を踏まえて、最初の例を考えてみると、こういうスタイルが出てきます。

"use strict"

function mkVal() {
    var v
    function getVal() {
        return v
    }
    function setVal(new_v) {
        v = new_v
    }
    return [getVal, setVal]
}

var [getVal, setVal] = mkVal()

setVal(42)
console.log(getVal())

SICP中でも、どうしてもlambdaが必要な場合は使っていますが、それよりもネストしたdefineのほうが自然な場合はそちらが多用されているような気がします。どうでしょうか?

*1:Python3以降ならこのようにnonlocal文でこのような変数の書き換えを共有できますが、2以前はJavaでやるようにboxingが必要で、そもそもこういうのはPythonではあまりencourageされていない、と言えばそういうことなのかもしれませんが。

「Trusting Trust」攻撃に対抗する

シュナイアーさんによるblogエントリ( https://www.schneier.com/blog/archives/2006/01/countering_trus.html )は、もう10年も前ですので、その世界では特に新しい話題というわけではないようですが、私が知ったのは今年のはじめのことでしたので*1、この機会に紹介する文章を書いてみたいと思います。

Unixを作ったケン・トンプソンさんがチューリング賞を受賞された際の講演「信用を信用することができるだろうか」( Reflections on Trusting Trust )は、その暴露的な部分(UNIXのloginコマンドにバックドアがあった、とする)の真偽はさておき、もしコンパイラのバイナリが「信用できない」ものだったら、という場合における、潜在的な脅威の可能性を否定することの難しさの指摘でした。*2

以下で述べる話題は、直接的な「コンパイラのバイナリを精読する」といった手段と比べると、大幅に省力的で、また、そのような可能性に対してある種の「相対的」安全性という考え方を示すものにもなっています。

詳細は、デイヴィッド・ホイーラーさん*3D論 Fully Countering Trusting Trust through Diverse Double-Compiling( http://www.dwheeler.com/trusting-trust/dissertation/html/wheeler-trusting-trust-ddc.html )となっていて、だいぶ大部ですが(私も全部は追いきれていません)、基本的なアイディアのスケッチは、けっこう以前からUSENETで議論されていたという程度には、簡単なものです。

まず基本的な問題について確認

トンプソンさんの講演の詳細を知っている方は、この節は飛ばして問題ありません。

しばしば、オープンソースのプロダクトは、たくさんの目でソースコードがチェックされているのだから安心だ、と主張されます。その当否はともかくとして、その大前提に、コンパイラは絶対に信用できるものだ、という仮定があります。

しかし、本当にコンパイラは信用できるのでしょうか? 噂では、性能比較に使われるベンチマークプログラムはその目的上、共通のものが広く公開されているので、そのパターンを検出して特別な最適化を掛ける、というチートが仕込まれていたコンパイラがあった、といいます。もしそれが単なる最適化ではなく、対象にバックドアを仕込むようなものだったら?

コンパイラだってオープンソースコンパイラなんだから大丈夫! でもそのコンパイラコンパイルするコンパイラに何か仕込まれていたら? 理屈では、一種の自己再生プログラム(いわゆるクワイン)のテクニックを利用して、ソースコードには何の痕跡も残さずに、コンパイラのバイナリから、それによってコンパイルされた、次の世代のコンパイラのバイナリへ、と、悪意を持ったコード片を「感染」させ続けることが可能です(プログラムのテクニック自体としては、一種のパズルのようなものとして知られていたものではあるものの、そのようにセキュリティに対する深刻な脅威として、可能性があると(広く注目されるような場で)示したのが、トンプソンさんのチューリング賞講演でした)*4*5

すぐに思いつく対抗法

コンパイラのバイナリを精読してチェックする(ブラックボックスチェック)、あるいはソースコードとの対応が取れているかチェックする(ホワイトボックスチェック)というあたりが、まず思い浮かぶでしょう。それができればそれに越したことはありませんが、たとえばGCCなど、実際に使われているコンパイラはそれなりに大きく、大変な作業です。プログラムでできないか、と考えるかもしれません。いい着目点ではありますが、やはりそのプログラムは信頼できるのか、ということになります。

あるいは、コンパイラではなくインタプリタを使おう、と思うかもしれません。しかし、そのインタプリタコンパイルするコンパイラが、ということになります。問題の場所は移動しますが、必要な作業はそんなに変わらない、ということになります。

ここで、GCCの昔話を

今では多くのUnixライク環境で、バイナリパッケージによるインストールは当たり前のものとなりました。何らかのプログラミング言語の処理系を「野良ビルド」するにあたり、いわゆるセルフホスティングであったり、そうでなくても自分自身のビルドに自分自身が必要であっても、まずバイナリパッケージを使って簡単に必要な環境を作ることができます。

昔は、なんでも野良ビルドが当然であり、わざわざ「野良」などと付けるようになったのもある時代以降ではなかったか、というような記憶があります。そして、難しさの点で大物*6のひとつが「プロプライエタリUNIX環境へのGCCの導入」でした。

普通のやりかたとしては、GCCのバイナリを、同じ環境を使っている他のユーザからGNUの助け合いの精神にもとづきコピーしてもらってくれば良いわけですが、全く同じ環境というのもなかなか無かったりするわけで、いわゆる「言語処理系のブートストラップ」の一種のような作業で「最初のGCCのバイナリ」を得ることになります。

今でもGCCのビルドの説明には、ステージという表現が使われていますが、この時代には次のような意味がありました。

stage1で得られるのはGCCによるコンパイル結果ではありませんから、バイナリ的にも違ったものなはずです(一種のキメラ的存在と言えるかもしれない)。一方、stage2とstage3は、どちらもGCCによるコンパイル結果ですから、結果は一致するはずです(理論的には一種の「不動点」)。結果が一致しても、必ずしも問題が無いとは言い切れませんが、結果が一致しなければ高い確率で問題が存在するはずです。

ここで注目すべきなのは、このプロセスは「何らかの既存のGCCバイナリ」に依存していない、という点です。これを利用したのが Diverse Double-Compiling による、Trusting Trust 攻撃への対抗だ、ということになります。

図法の導入

伝統的な「T図式」( T diagram )というものもありますが、ここでの議論には少し向いてない面があるので、次のような図法を使います( Fully Countering Trusting Trust through Diverse Double-Compiling の、§4.1 にある図)。

中央の四角がコンパイルプロセスを表現していて、上からの矢印がそのコンパイルに使用するコンパイラのバイナリ、左からの矢印がコンパイル対象のソースコード、右からの矢印がコンパイルオプションなどの「ソースコード以外」のコンパイル結果に影響を与える要素(以下では省略します)、下に出ている矢印がコンパイル結果であるバイナリ生成物、という感じになります。

この図法で、前節で話題にした、プロプラUNIX環境へのGCCの導入をあらわすと、次のようになります。

Diverse Double-Compiling

提案手法では、次の図のような手続きで、調査対象のコンパイラの2種類のバイナリを作ります。

右側は、自分自身をコンパイルできる(セルフホスティングな)コンパイラの、通常のコンパイル手続きです。対称性のために2回コンパイルしていますが、原理を示す上では1回でかまいません(2回とも、同じ結果が得られているはずです。説明のため、ここではGCCとします)。

左側は、GCCの導入において「プロプライエタリUNIX付属のcc」であった部分を「何らかの信頼できるコンパイラ」に変えたもの、という感じになります。最初のコンパイラが信頼できるコンパイラですから、stage1のコンパイラソースコードときちんと対応しているはずです。ですからstage2のコンパイラは、(ソースコードGCCだとすれば)最初の「信頼できるコンパイラ」と「GCCソースコード」を信じられる限りにおいて、信じられるはず、ということになります。

そして、この信じられるコンパイラは、右のほうのプロセスで作られたコンパイラと、バイナリ的に一致するはずです。もし一致しなければ、何らかのおかしな仕掛けが仕込まれている可能性がある、ということになります(もちろん、これまで触れてきませんでしたが、コンパイルされた時刻を埋め込む、などといった特異な要因が無ければ*7*8)。

ここで大事なことは、GCCのような大きなCコンパイラのバイナリコードをチェックする、というような大変な仕事ではなく、「信頼できて、GCCをとにかくコンパイルできれば良い(コンパイルが遅くても、最適化が全く無くても)コンパイラを用意すること」と「2つのファイルの単なるバイナリ比較」という、(比較すればそれなりに)単純な仕事で、問題点の検出が可能になった、ということです。

相対的な信頼性

またこの手法は、LLVM clangでGCC(ここではコンパイラコレクションの意)のCコンパイラをビルドする、あるいはGCCのCコンパイラLLVM clangをビルドする、といったようなプロセスで、「絶対に信頼できるCコンパイラ」が無くても、ある程度相対的には信頼できると確認できる、ということも示しています。

まとめ

現実的には考えにくいとはいえ、その危険性から脅威と考えられる「Trusting Trust」攻撃に対して、(世界的なセキュリティ界隈では以前から知られていたもののようですが)有効な対抗手段があることを紹介しました。

世界にコンパイラの実装が1種類ではまずく、少なくとも2つ以上、できればもっと多数あるべき、という「多様性の善」を示すものにもなっているように思います。また、セルフホスティングができると、自分の非標準なオレオレ拡張を、自分自身の実装に使いたくなるんじゃないか、という気がしますが、それをやってしまうとこのような検証ができなくなる、ということもわかります*9。あと、極小Cコンパイラとして8ccという実装がありますが( https://github.com/rui314/8ccGCCコンパイルできれば、もしかして「最初の信頼できるコンパイラ」として有用だったりしないでしょうか? (これについては全く私は手を付けてません)

*1:追記1: どうも10年前に、こちら http://www.radiumsoftware.com/0603.html#060329 の最後の段落で、話題自体は読んでいたようです。

*2:(2018年8月追記)また、講演録を読むと最後のほうで、クラッカー(特に、今で言ういわゆる「スクリプトキディ」)について、きつい語が並んでいますが、それも背景の説明が必要なようです。受賞は1983年で、その当時は、本格的なパーソナルコンピュータが広まる前である一方、映画『ウォー・ゲーム』が公開された年でもあり、フィクションとないまぜの「コンピュータを操る少年」像がしばしば「神童」的に扱われ、マスコミを賑わしていた、というような背景があります。また、いわゆる「ハッカーはクラッカーじゃない」問題の源流の一つでもあり、講演後に(以下出典未確認)CACM誌でRMSと論争になった、という話もあります。

*3:BW変換のWであるデイヴィッド・ホイーラーさんとは特に無関係のようです。

*4:講演録では、トンプソンさんが知ったネタ元は、Multicsのセキュリティに関する空軍の文書とありますが、その後確認されたところによれば Karger, P.A., and Schell, R.R. Multics Security Evaluation: Vulnerability Analysis. ESD-TR-74-193, Vol II, June 1974, p 52. という文献だとネットの情報にはあります。

*5:余談ですが、Multicsは高水準言語をOSの記述に使おうとしたという点でも先進的でした(言語はPL/I)。日本でその影響を受けて開発されたHITAC 5020 TSSも、PL/IWというPL/IのサブセットをPL/IWで書いて、人力コンパイルでブートストラップしたと伝えられていますから、この記事とも少々関係があります。

*6:現代と違って、昔はC言語処理系の標準準拠もいろいろ怪しかったりすると、けっこう一筋縄ではなかったりした。

*7:あるいは、近年のセキュリティ技術という観点からは、アドレスを決め打ちするような攻撃に対抗するべくランダム化を掛ける手法がありますが、そういうのとも相性が悪いでしょう。

*8:追記2(参): Debianプロジェクトなどによる、ReproducibleBuilds日本語解説)として、バイナリ再現性の重視について推進するプロジェクトがあります。

*9:追記3: これも http://www.radiumsoftware.com/0603.html#060329 で指摘されているのを、どこかで覚えていたようです。

POSIXとのつきあいかた

コンピュータ関係の技術的なものや技術的でないもの(標準規格とかいったようなものの半分は技術仕様ですが、もう半分は業界政治で出来ていると言えるでしょう)との「つきあいかた」にはいろいろあります。

私のこれまでの経験などから、POSIXとのつきあいかた、といったようなことについて、いくつかの事例を示してみたいと思います。

Bashismとのたたかい

まぁ、Bashismとの戦いという点では、おまえらPOSIXを守ってくれ、と言いたい側ではあります。日常生活しているシェルはbashであるとはいえ、shebang が #! /bin/sh となっているのにFreeBSD/bin/sh (ash系)で盛大にコケるようなものが生態系として定着してしまうのはなんだかんだ言って脅威なわけで、過去にいくつかのプロダクトに向けてチケットを投げたりパッチを書いたこともあります。Bashismではないですがseqコマンドの代替をシェル関数として実装したりだとか。

とは言え、自分自身、FreeBSDの環境で動いてしまうものについては見逃しているだろうことも多く(シェル(シェルスクリプト)の構文では、微妙な所でashではエラーにならないが、bash含め一般にはエラーになる例、というのを引っ掛けていたこともある)、一応それなりにチェックしたい時には、FreeBSDのベースシステムの実装、GNUの実装、Plan9由来の実装(portsにいくつかある)、Heirloom Project(heirloomとは遺産のこと)がメンテしている伝統的Unix由来の実装(これもportsから入れられる)、それぐらいを確認手段にしています。

GNU AWK

と、言ったそばから逆のことを言うようですが、AWKの ** 演算子は非標準として有名ですけども、AWKは言語仕様上、言語処理系側で拡張されてないとなんだかんだで色々なことができませんから、必要であればGNU AWK依存だと割り切って書く、ということも必要でしょう。今の所自分がAWKで書いたプログラムにはありませんが、gawkを使いたくなりそうな点というとまずは多言語対応とかそのあたりでしょうか。

GNU Coding Standards

GNU/Linuxシステムでよく使われているcoreutilsのユーティリティには、POSIXLY_CORRECTという環境変数を設定することによって、(おそらく各ツールの製作者がrationalと考えたものである)通常の挙動から、POSIXにより厳格に従った挙動に挙動を変更するものがあります。

あるいはもっと一般に、GNU Coding Standards ( https://www.gnu.org/prep/standards/standards.html )の中には多くの場所で、POSIXへの言及があります(重複を除いてざっと二十数箇所ぐらい)。これはある意味で「GNUプロジェクトという立場からの『POSIXとのつきあいかた』」と言えるでしょう。

(以下余談。その「POSIX」という名前をsuggestしたのはリチャード・ストールマンさんだ、という話があります*1。また、GNUのコーディング規則は、すごく古いもの http://think-gnu-distribution.appspot.com/html/tga04.html#h.2 も比較して読むと、時代の変遷や「GNUのコアな所」が見えて面白い)

paxコマンド

アーカイブユーティリティのpaxコマンド( http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html )は、POSIXの性格を示すコマンドの一つだと思います。tarとcpioという乱立に乱立を重ねた状態にあったアーカイブユーティリティについて標準化するにあたり、既存のものを標準化するのは諦め、さらにもう一つの(願わくば、混乱の収拾に至る道となってほしい)種を播いた、と言えるでしょうか。その種は、混乱の収拾に向けて、育っていると言えるでしょうか。paxコマンド、使ってますか? ちなみに私は使って(使えて)いません。

dateコマンドのオプションの無さ

標準Cライブラリ(libc)にはいくつかの便利な関数がありますが、それをちょっと使いたい、といった時に、Cのコードを書いて試すのは少々ではありますが、手間です(Cインタプリタ、たとえばCINT*2などを使うことで少しは手間を減らせるかもしれませんが)。

そういった時、類似した機能が使えるコマンド、というのは結構便利です。たとえばちょっとした整数値の十六進表記を確認したい時には、

$ printf '%x\n' 12345
3039

といった感じです。

多機能なlibcの関数のひとつに、strftime( http://pubs.opengroup.org/onlinepubs/9699919799/functions/strftime.html )があります。そして、普通にドキュメントを読んだだけではいったいなんのことやらよくわからない、「 %G と %V 」「 %W と %u 」「 %U と %w 」という、あまりメジャーでないフォーマット文字列があります*3

以前、dateコマンドの機能を使ってあれこれと実例を試して確認したことがあるのですが( http://ksmakoto.hatenadiary.com/entry/2014/10/12/132048 )、実はこのような実験に必要な、任意の時刻を指定してその時刻の文字列表示を得るためのdateコマンドの機能は、POSIXでは全く決められていません( http://pubs.opengroup.org/onlinepubs/9699919799/utilities/date.html を見ればわかる通り -u オプションと、フォーマット文字列を指定する、という引数しか標準にはない)。後から指摘されたのですが、特に何かうまい代替手段も無さそうだったので、記事には注意書きを追加したのみです。

これはPOSIXを追求しなかった例、という話でした。

bcコマンドの曖昧なエラー対処

本題とは無関係ですが、私はMacr055*4というテキストマクロプロセッサを作っています。M4という標準的な(POSIXにもあります http://pubs.opengroup.org/onlinepubs/9699919799/utilities/m4.html )マクロプロセッサではevalという組込みマクロで四則演算などの算術式の計算ができるので、同様の機能を実装したのですが、自分で実装するのは面倒だったので、外部のbcコマンドを利用することにしました。

算術式の計算は、ベンチマークに利用するのに便利ですから、あまり重いのも嫌です。そんなわけでbcには、標準入力から入力を得て逐次その結果を標準出力に吐くモードがありますから、それを利用しました。

処理対象が数式なのですから、ゼロ除算のような意味的エラーは別としても、構文エラーに対してはエラーを検出したいものです。そして、bcがそのようなエラーを検出した時、そのメッセージが標準出力に出力されるのか、標準エラー出力に出力されるのか、実装によってまちまちになっています。手元で確認したところでは、FreeBSDのベースシステムに入っているbcはstdoutに、GNU bc( https://www.gnu.org/software/bc/ )はstderrに吐いてきます。

これは困った、ということでPOSIXで確認( http://pubs.opengroup.org/onlinepubs/9699919799/utilities/bc.html )したところ、この点に関して明示が意図的に存在しないような(としか思えない)書き方になっている、ということを見つけました。

上から順番に関係しそうなものを見てゆきます。まず STDOUT の節には、「正常の場合の出力」のことのみが書いてあります。次の STDERR の節では「shall be used only for diagnostic messages」とあります。ここで「diagnostic messages」とあるのは何か、ということになりそうです。

その後は、bcが処理できる言語の仕様が書かれた EXTENDED DESCRIPTION の節が長く続いた後、最後にわずかにエラーに触れた記述があります。

CONSEQUENCES OF ERRORS の節の1つ目の段落で、引数で指定された名前のファイルにアクセスできなければ「diagnostic message」をstderrに吐け、とあるので、前述の STDERR の節で出力されるとされているのは、こちらのエラーのみということになりそうです。

次の段落の記述が問題の、入力中のエラーについて触れている所ですが、「インタラクティブな起動中は should print an error message」とのみあります。それをstderrに吐くべきかstdoutに吐くべきか、を示すような情報はどこにもありませんし、「should」ですから、絶対にそうしなければならないわけではない、ということでもあります。また EXIT STATUS についても、0 になるのは「All input files were processed successfully.」とあって、標準入力からの入力についてはやはりボカしているような感じがあります。

unexpandコマンドに-tオプションを付けると-aを付けたような挙動が強制抱き合わせになる変な仕様

タブは行頭にだけ使い、expandコマンドとunexpandコマンドで展開したり戻したりすれば、タブ幅の変換は普通はちゃんとできるはずです...が、実際には4タブだと解釈してハードタブ化しようと unexpand -t 4 とすると、なぜか -a オプションを付けていなくても、付けられているかのように動作しろ、と POSIX にはあります( http://pubs.opengroup.org/onlinepubs/9699919799/utilities/unexpand.html )。POSIX内で回避する手段は無さそうだったので、GNU版を使って非標準のオプション --first-only で回避しました(この項、思い出したので追記)。

まとめ

そういったわけで、他には、カーネルとのAPIに関してはLKML周辺の人々をウォッチしていれば、やはり時々、「POSIXとのつきあいかた」が見られるような話題を見ることができるでしょう。POSIXは、明確な参照実装が存在したりするような「仕様」とは、違うタイプの「標準」ではないか、と私は捉えています、というのが本稿の結論めいたもの、となるでしょうか。日本語と英語がうまく対応しないのですが、「標準」「規格」「仕様」、「standard」「specification」、それぞれ微妙にニュアンスが違いますし、現実に存在しているものは、たとえば似たように「標準」と銘打っていても、それぞれ違った位置づけだったりするものです。

numo-lapack というブロジェクトを作りました(始めました)

numo-lapack というブロジェクトを作りました(始めました)。

Numo::Linalg( https://github.com/ruby-numo/linalg )からのフォークをベースに、column-major ←→ row-major 相互の変換をライブラリ側では行わないなど、LAPACKを直接使うのに近い方向性で、早い時期に、多くのLAPACKの関数を利用できる、動く実装を完成させることを目標とする予定です。
現状で、名前等の変更の他、linalgに実装されていた既存メソッドについて新方針で再実装したコードがありますが完全にドラフト版ですので、もし試す方は注意してください(LAPACKの、入力を壊すような関数を使っている場合は、入力のNArrayが壊れる、など)。それと、もしかしたらlinalgと同時にrequireするとどこかが壊れるかもしれません。

2016年11月21日追記

Ruby Associationのほうからアナウンスが出ましたので追記ですが、

こちらのアナウンスにありますように、当プロジェクトは「2016年度Ruby Association開発助成」に応募し、採択されました。約半年弱ですが、ご期待に応える結果を目指します。岸本へのコンタクトは各種可能ですが、多分最も反応が早いのは、SciRuby-JP の Slack からのメッセージになると思います。