「トロン―国産OSが世界標準になる」とかいう見出しの記事について

結論

いつものアレなのになんで今回はこう騒ぎになってるの?

事実関係

2017年11月のニュースとして、μT-Kernel(従来のμITRONにおける小さめのプロファイルに相当)を、IEEE標準の「2050」にするために、著作権の移管などがあった。(IEEE 754(浮動小数点)とかIEEE 802(LAN)などと同様なワーキンググループのひとつ。他にどういうものがあるかは http://standards.ieee.org/develop/project/computer_technology.html で見られる)

この件に関する情報としては、

はあるが、日本語でちゃんとした内容を伝えるものは、なぜか(いつものことだが)出ていない。

今後何が変わるか(予想)

以下の内容は私の予想だが、そう大きくは外れないだろう。

結論としては、何も変わらない。

例えば ISO/IEC 14576 (STbus) は、元はTRONプロジェクトで開発されたTOXBUSであり、「TRONプロジェクト発の世界標準」のひとつだが、それで何が変わっただろう?

IEEE P2050のページ( http://standards.ieee.org/develop/project/2050.html )の右の「PAR」(Project Authorization Request)と書かれている所にあるPDFに、プロジェクトのスコープやミッションなどが書かれているが、要するに、今あるμT-Kernelの仕様書をレビューし、IEEE標準のひとつとする、ということだけしか書かれていない。つまり、μT-Kernel以外の、(μでない)T-Kernelをはじめとするその他のTRON全てについては、全く無関係な話でしかない。

また、最も強く懸念されることとして、以前の(1.0の頃の)ITRONの仕様書がそうであったように、1万円以上の価格で購入しない限り、仕様を見ることができなくなるかもしれない、という点があるが(実際に現状で「IEEE P2050 IEEE Draft Standard for Real-time Operating System (OS) for Small-scale Embedded Systems IEEE P2050/D2, October 2017」を入手するには、268ドルで購入する必要がある)、2017年のうちに移管されている(はずである)μT-Kernel2.0の仕様が、2018年4月現在、英日版とも、tron.org のウェブページから閲覧可能であることから(単に更新されてないだけにも見えるが)、もし、著作権の移管により見られなくなるものならば、現状がそれに違反していることになるので、恐らく問題ないものなのであろうと、強く推測される。

その他

坂村先生の風呂敷はいつものことだし、アンチが「Linuxという世界標準があるのに」とか意味のわかんないこと言うのもいつもの後継なんですが、なんで今回はこんなにバズってるのというか。読売新聞効果ですかね?

先生の威勢のいい談話が出るのは結構ですが、教育用キットT-Car(リンク先画像 http://ascii.jp/elem/000/001/089/1089654/img.html )とか出るんですかどうなんですか。

TypeScriptにおけるobject typeのunionの、あまり期待されてないのではないかと思われる挙動について

サンプルコード

'use strict';

type A = { a: string }
type B = { b: number }
type C = { c: boolean }
type T = A|B|C

const v1: T = { a: '1', b: '1', c: '1' }
const v2: T = { a: 1, b: 1, c: 1 }
const v3: T = { a: true, b: true, c: true }
const v4: T = { a: false, b: '0', c: 0 }

何が「期待されてないのではないかと思われる挙動」なのか

ここで(v4ではエラーになるのはともかく)、v1〜v3に代入している値は、型 A, B, C のどれにも代入できないにもかかわらず、それらの union である A|B|C 型には、コンパイル時のエラーにならず、代入できてしまう。これは、(静的な)強い型付けを期待しているプログラマの想像に、多分、反している。

仕様ではどう言っているのか

仕様 ( https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md ) では(以下、2018年4月現在のバージョンである Version 1.8 を前提とする)、§3.4 Union Types に、次のようにある。

A type T is assignable to a union type U if T is assignable to any type in U.

(訳: 型Tの値は、union型Uに、Uのどれかの型に代入可能である場合に、代入可能である。)

ここで、次のようではないのは意図的なものなのではないか、と思われる。

***NOT*** / A type T is assignable to a union type U if and only if T is assignable to any type in U.
(訳(こうなってはいない): 型Tの値は、union型Uに、Uのどれかの型に代入可能である場合、かつその場合に限り、代入可能である。)

つまり、このあたりの挙動については、型によって安全を保証することを諦めている(のではないか)と思われる。理由としては、最初に結論として述べたように、Discriminated Union を使えば、こういった問題は避けられるからである。

想像される実装

以下は tsc の実装を読んだわけではなく、いくつかの例で試した結果からの想像である。

複数個の object type のある Union型 U への object 型 T の代入可能かのチェックでは、

  1. まず、 T の属性名の中に、U の型のどれにも現れない属性名がないか確認する。どれにも現れない属性名があったら代入できない。この時、その属性の名前のみがチェックされ、型はチェックされない。
  2. 次に、U の型について、左から順番に、T が代入可能かどうかについて試してゆく。この時、通常の代入可能のチェックとは異なり、T の側に属性が余ってもエラーにはしない。
(これで、前述の仕様は満たされているはずです。また、以上の規則で説明できない例が確認できましたら、よろしければコメントください)

「Windowsの標準電卓で4の平方根が2でなかった仕様がようやく修正」の件について

Windowsの標準電卓で4の平方根が2でなかった仕様がようやく修正」というニュース( pc.watch.impress.co.jp )がありました。現時点では私の手元にそんなに情報が無いので、

  • 以下で述べる内容
    • 以前の仕様のあらまし
    • 修正に関する憶測と心配事
  • 以下で述べない内容
    • 以前の仕様の正確な理由はなぜか
    • 具体的にどう修正されたのか

という感じになります。

以前の仕様について

プレスリリース( https://blogs.windows.com/windowsexperience/2018/04/04/announcing-windows-10-insider-preview-build-17639-for-skip-ahead/ )の中の Windows Calculator Improvements という節の中でリンクされているMSDNで開発者が書かれたブログエントリ( https://blogs.msdn.microsoft.com/oldnewthing/20160628-00/?p=93765 )と、そこからさらにリンクされている、もっと前のエントリ( https://blogs.msdn.microsoft.com/oldnewthing/20040525-00/?p=39193 )にあるように、大昔はもっとエエカゲンだった「電卓」ですが、近年は「an arbitrary-precision arithmetic library」により、「四則は無限精度」「それ以外の関数などは32桁の精度」で計算する、という仕様になっていました。

(明確には書かれていませんが、挙げられている例から見て、四則演算の間は除算の結果などは内部で分数として保持するようなライブラリ(すなわち、有理数演算の機能も含んでいるライブラリ)なのだと思われます)

そして、平方根は四則演算ではないので、有限精度で計算され、(後述の追記参照)、また、正の値であればどんな場合であれ全て exp(ln(x)/2) で計算していることから、√4 のような場合でも誤差が発生する場合がある(そして、それは気にしないでしょ?)、となっていました。

これ自体には問題はありません。コンピュータで以上のような計算のしかたをすれば、必ず起きる可能性があるものとして、受け取らねばならないものです。

なお、1.99999999999999999989317180305609 という数がどうやって出てくるのかについては、いくつか検討してみましたが、まだ糸口がつかめていません。(どうやら見つかりました。次の追記を参照)

追記: 平方根の計算に使っている ln と exp については、拡張浮動小数点演算を使っている(のだろう)と、小飼さん(@dankogai)から指摘がありました。十六進小数にすると 1.ffff_ffff_ffff_fffe0784a26b6c0c... ですので、上位の1の連続が、ちょうど拡張浮動小数点の64ビット(ケチ表現無し)と一致します。確かに!

憶測

以下は、これの修正に関する、あくまでも「憶測にもとづいた」議論です。具体的にどう修正されたのかがはっきりすれば、心配の必要は全てなくなります。

最も想像される修正

おそらく最も可能性が高いと思われるのは、入力の値が整数になっていて(これは前述のライブラリであれば、正確に判定できるはず)、整数平方根により正確な値が得られたならば(これも微妙な点は無いはず)、それを結果とする、という処理が、平方根の前処理として追加された、というもので、基本的に安全な範囲の修正と言えます。

確率は低いと思われるが、懸念される修正

以前のMicrosoftにはあった悪癖として、数値を扱うアプリでアドホックな手当をする、というものがありました(詳しくは、奥村先生が2001年2月に書かれた(と思われる)、「Excelの演算誤差」という記事( https://oku.edu.mie-u.ac.jp/~okumura/software/excel/roundoff.html )の、「恐ろしくて数値計算に使えたものではない。」と閉じられている最後の段落あたりを参照)。

元々のこの問題のチケットが、単に放置されていたのではなく、もし「WONTFIX」になっていたものを*1、わざわざ修正したのだとして、なぜ今更修正したのかがはっきりしてなかったとすると、「なんとなく直した」のではないかという懸念が出てきます。

アドホックに手当する、というのは、こういった問題で一番やってはいけないことで、もしそうだったら、という不安があります。

こうすればもっと良かったんじゃないかという気もする修正

徳丸さんのツイートによれば、

とのことなので、以下で私が考えるような「さいきょうの修正」は、されていない模様ですが、参考のために書いておきます。

平方根には「荒っぽい近似が得られれば、ごく簡単に精度の桁を倍近く伸ばせる」という性質があります。具体的には、ニュートン法の反復の1回の追加、すなわち:

  • 平方根を求めたい値を x
  • 得られている sqrt(x) の近似値を y

とした時、

  • y := (y + x/y) / 2.0

とすれば、1回でもかなり改善されます。また、見ての通りこの演算には四則演算しか使いませんから、Microsoftが「電卓」の実装に使っているライブラリで任意精度で計算できますので、これを1回(か2回)実行してから、例の「32桁」に四捨五入で丸めれば、

  • 全ての場合で精度が改善される
  • 問題だったような、「トリビアルに自明な結果」を外すことも無くなる(はず)

だと思うのですが、どうでしょうか。

*1:後述のように、平方根の計算には、簡単かつ確実に精度を上げる方法がありますから、それなりに意図的にそのままにしたものではないか、という気がします。

タッチパッド機におけるブラウザのJavaScriptの、エミュレートされたマウスイベントと、インタラクティブなコンテンツとの干渉について

サンプルを https://github.com/metanest/mobileBrowserMouseEmulationInteraction に置いてあります。

仕様としては、W3Cによるタッチイベントの仕様 https://www.w3.org/TR/touch-events/#mouse-events に含まれているのですが、タッチパネル操作のスマートフォンタブレットのウェブブラウザのJavaScript では、タッチ操作のイベントの後に、(既存アプリ用の)マウス関係のエミュレートされたイベントを発生させることが、仕様で考慮されており、多くの実装がそのような挙動をします。

仕様中に、

If the contents of the document have changed during processing of the touch events, then the user agent may dispatch the mouse events to a different target than the touch events.

とあるように、先に発生したタッチイベントでコンテンツを書き換えた場合などは、同じ場所で後から発生する(マウス)イベントは、同じ場所に新しくできた違う対象に対して発行されるかもしれない、ということになっています。

これは実際のところ、厄介な挙動になり得ます。サンプルは、あるエリアをクリックするとオーバーレイが表示され、そのオーバーレイをクリックするとオーバーレイが消える、というものですが、スマートフォンのブラウザに表示させて試してみると、オーバレイがすぐ消えてしまうような挙動をする(場合が多い)はずです。実際にどんなイベントが起きているかは JavaScript コンソールに出力しているので、そちらで確認できます。

なお、仕様では、

If the preventDefault method of touchstart or touchmove is called, the user agent should not dispatch any mouse event that would be a consequential result of the the prevented touch event.

となっているので、(あるべしとされている通り実装されていれば)それに従うことで抑止できるはずです。

特に、各種のフレームワークの内部でこれが起きている場合、把握するのはなかなかに厄介です。

例えば、こちら( https://github.com/callemall/material-ui-webpack-example/tree/master/src/app )にある(2018年2月末現在)Material-UI のサンプルでは、react-tap-event-plugin というプラグインを利用して onTouchTap というハンドラで処理をしています。そしてこのサンプルを、onTouchTap の利用をやめ onClick のみに変更されている、バージョン 0.19.0 及び、それよりも後の Material-UI で動かすと、開いたダイヤログの背景の onClick が呼ばれて、ダイヤログがすぐに消えてしまう、という現象が起きます。*1

JavaScriptのモジュールのimportの循環が必要な場合について

サンプルを https://github.com/metanest/jsMutuallyRecursive に置いてあります。

実行するには、package.json と package-lock.json を同梱してありますので*1、まず npm を次のように実行して必要なモジュールをインストールした後*2

$ npm install .  # 最後の . (ピリオド) を忘れないように

run_webpack.sh を実行すると、webpack が実行されて、ディレクトリ dist1/ と dist2/ の中に NodeJS で実行可能なスクリプトが生成されます。

用語の定義

以降の説明の都合で、JavaScriptスクリプトの実行の流れに関して、少し用語の定義をします。

次のようなスクリプトを実行すると、

console.log(1);
function f() {
  console.log(4);
}
console.log(2);
console.log(3); f(); console.log(5);
console.log(6);

1 から 6 まで順番にコンソールに出力されますが、このような場合の 4 以外の実行の流れを「上から下への実行」、4 の流れを「関数内の実行」と、以下では呼ぶことにします。

循環があっても、関連が疎であり、特に問題が無い場合

src1/ にソースが置いてあり dist1/ に生成されるサンプルは、循環があっても特に問題が無い場合です。

具体的には、mainが次のようで、

import isEven from './isEven';
import isOdd from './isOdd';
console.log(isEven(4));
console.log(isEven(5));
console.log(isOdd(6));
console.log(isOdd(7));

実行結果は次のようになります。

$ node dist1/main.js
This is in isOdd
undefined
========
This is in isEven
[Function]
========
true
false
false
true

また、それぞれの子モジュールの内容は次のようになっているのですが、このように「関数内の実行」からのみの参照であれば問題はありません。undefined が出力されている場所があるように、「上から下への実行」の途中では、import の結果が undefined になっている時はありますが、関数が実行される時には「中身が出来て」います。

// isEven.js
import isOdd from './isOdd';
console.log('This is in isEven');
console.log(isOdd);
console.log('========');
export default x => (x === 0) || isOdd(x-1);
// isOdd.js
import isEven from './isEven';
console.log('This is in isOdd');
console.log(isEven);
console.log('========');
export default x => ! ((x === 0) || ! isEven(x-1));

対称的になっている通り、mainにおける isEven と isOdd のインポートの順序も、どちらでも構いません。

関連が密で、循環による問題がある場合

src2/ にソースが置いてあり dist2/ に生成されるサンプルは、問題がある循環の場合です。

具体的には、mainが次のようで、

import yin from './yin';
import yang from './yang';
yang.yin = yin;
console.log('yin');
console.log(yin);
console.log('========');
console.log('yang');
console.log(yang);
console.log('========');

(このmainにある yang.yin = yin; という代入は、以下のような結果を得るために、どうしても必要)

実行結果は次のようになります。

$ node dist2/main.js
yin
{ yang: { yin: [Circular] } }
========
yang
{ yin: { yang: [Circular] } }
========

各モジュールは次のようになっています。

// yin.js
import yang from './yang';
export default { yang: yang };
// yang.js
import yin from './yin';
export default { yin: yin };

(yinとかyangというのは、古代中国思想の陰陽のことで、米国の古いハッカーが相互再帰的なものを説明する時にたまにこのような名前を使っているのを見ます)

main → yin → yang のようにインポートが進んで、そこからさらに → yin のインポートは循環として検出されて阻止されますから、yangの中での「上から下への実行」の途中では yin は undefined になっていて、先に示したようにmainのほうで補完してやっています(あれこれいじってみましたが、他に方法は無いようでした)。

「上から下への実行」の途中で実際に参照ができる必要がある、現実的なコードの例としては、classのextendsでの親クラスの指定などがあります。

サンプルで意図的にやっているような、双方向に直接の参照が必要な場合は、この例の yang.yin = yin; のように外部から操作して循環を完成させないといけません。一方、例えば親クラスと子クラスの関係のように*3、「上から下への実行」中の参照は片方向で循環しない場合は、この例の yin の側のように、mainで(上位側で)インポートする順番に注意する必要があり、直接に参照している側を先にインポートしてやればうまくいきます。

*1:package-lock.json は、内部に記述されている多数の依存先のバージョンのどれかに脆弱性があると GitHub から警告が来るので外しました。

*2:ピリオドを忘れると、npmの、ディレクトリを遡る特性で変なことになる場合があります。

*3:メソッド中などで必要なために、importは循環になっていても

NKF.guess はもう古い。国際化時代のコードセット(エンコーディング)推測法

柄にもなく釣りっぽいタイトルですが、本文中には特にそんな派手なものはありません。

はじめに

Rubyの多言語化に関してまず読むべき、るびま記事である「Ruby M17N の設計と実装」が2009年の記事ですから四捨五入すればもう、ひと昔前のことですが、それでもなかなか移行は進まないもののようなので(あと少々クセがある)、エンコーディングの推測についてまとめた記事を書いてみます。

エンコーディングの変換

エンコーディングの変換については、例の記事の「エンコーディングの変換」の節( http://magazine.rubyist.net/?0025-Ruby19_m17n#l76 )と、詳細は最新のマニュアルの String#encoding についてのページ( https://docs.ruby-lang.org/ja/latest/method/String/i/encode.html )から追ってください。

NKF.guess はもう古い?

確かに NKF.guess は便利です(便利でした)。しかし K がおおもとは「漢字」に由来していることが暗示するように、現代の状況への対応には限界があります。

そもそもエンコーディングの推測ということ自体が、実はかなり無理なシロモノで、過去において NKF.guess の主な仕事だったと思われる EUC-JP と シフトJIS の判別についても、「あまり長くない EUC-JP 文字列と、半角カナばかりのシフトJIS文字列」というような(少々人為的ですが)判別が絶対に不可能な例があるほどです。

また現代ではとりあえず、最初に「UTF-8のように見えるならばUTF-8だとみなす」という判定を入れたい、などと思うことがありますが、そういう調整などを入れることも、オートマジカルな「NKF.guess 任せ」では難しいでしょう。

現代的なエンコーディング推測

((この1文だけ余談)専ら通用しているエンコーディングという表現を使っていますが、EUC-JP などは正確には EUCエンコーディングで、EUC-JP などを指す場合は厳密には「コードセット」となります)

現代的なやりかたは、ファイルの内容を最初から最後まで全部確認することでしょう。

全て読み込むとスラッシングが起きる巨大ファイルであるとかいう場合には、何か別の方法を使う必要がありますが、そうでなければ、ファイルをまず全部 String として読み込みます。

その際、エンコーディングを指定しないことが明示的なメソッドである、IO.binread メソッドを使うのが良いでしょう。

そして、その String オブジェクト s のオクテット列が、目的のエンコーディング e による文字列として valid かどうかは次のように、String#valid_encoding? メソッドでチェックできます。文字列に「付いているエンコーディングの情報」だけを強制的に設定する force_encoding メソッドを使うのがミソです。

s = IO.binread 'file.txt'
e = Encoding::UTF_8  # 例として UTF-8 についてチェック

s.force_encoding e
if s.valid_encoding? then
  print "UTF-8\n"
else
  print "not UTF-8\n"
end

後はこれを、Stringオブジェクトとエンコーディングを渡すと判定してくれるメソッドとして分離して(force_encodingによる副作用の存在に注意)、上位層で例えば、

  1. まず UTF-8 のようであれば、UTF-8 だとみなす
  2. そうでなければ、シフトJISEUC-JP を試す
  3. シフトJISEUC-JP のどちらでもなければ、とにかく UTF-8 だとする
  4. シフトJISEUC-JP の、どちらか片方で valid なら、それを選ぶ
  5. シフトJISEUC-JP の、両方で valid なら、特製の判定関数を使う

とでもいったような手続きとして実装しておけば、仮に将来、なにか誤判定で困るようなパターンがあっても、柔軟に対応できるでしょう。

「James Clark 記法」に関するメモ

備考1

備考2

これは「ときに要素間ホワイトスペースの量を最小化するために使われる一般的でないスタイル」などと呼ばれるような手法のことを指している。

<!DOCTYPE Html>
<Html
 ><Head
   ><Title
     >2022年夏季卒業式</Title
   ></Head
 ><Body
   ><H1
     >卒業</H1
   ><Section
     ><……
 …………
 …></Section
   ><Section
     ><H2
       >卒業生</H2
     ><Ul
       ><……
    ………
 ……></Ul
   ></Section
 ></Body
></Html>
http://www.html5.jp/tag/elements/section.html

備考3

なお、「James Clark notation」というフレーズは、James Clark によるXML名前空間に関するメモ「XML Namespaces」( http://www.jclark.com/xml/xmlns.htm )などで使われている、「プレフィックス : 」の部分を実際の URI に展開して波括弧で囲んだ、

{http://relaxng.org/ns/structure/1.0}element

というような記法のことを指して使われていることが(英語圏では)ある( http://dvreeze.github.io/yaidom-and-namespaces.html )。