月: 2016年10月

『シン・ゴジラ』北米初日に観た

img_20161011_191255

10月11日から一週間だけ北米で上映中の『シン・ゴジラ』をようやく観ました。

ともかく、満足。大満足。よかったよかった。観られてよかった。

内容や感想はともかく周辺状況を記録しておくと。

私のいったところはモール内の映画館でしたが、客の入りは4割ぐらいかな……。平日の夜だし、まあこんなもんじゃね?という気がします。日本人はぼくら以外いたのかなあ、普通に地元の人々が大多数でした。もちろん、地元といってもそういうのが好きなコアな人々が見に来たというところでしょう。

意外とアメリカネタのセリフがありましたが、そのたびに客席がいちいちウケていたのも面白かった。客層が、日本のものが好きな人々だからってのはあるのでしょうけれど。もともとのセリフでは「あちらさん」とか婉曲表現だったものが字幕ではほぼそのものズバリ The US とか America とか大変直裁的表現になっていたのもあったのがまたちょっと面白い、というのがあるかもしれないですが。

北米では不評、というニュースをいくつか目にしますが、現時点で Rotten Tomatoes は 78%、 IMdb では10点満点で7.7というスコアは決して悪いものではない……というかそれなりに高い評価だと言えると思います。もちろん今スコアをつけているのは相当コアなマニアたちなので、これから点数がどう推移していくかはわかりませんが、不評ということではないのでは。……とはいえ北米ではわずか一週間のみの公開、しかも上映は各館一日一回だけ、というスケジュールなので、これはまあ届くべきマニアに届けることを考えたものであるのもまた間違いないことでしょう。

てなわけで、当然ながら北米の配信はマニア向けでありまして、その当初の目論見どおりごく一部のマニアに届く感じになっていたということではないかなと思います。

はーそれにしてもこれでようやくネットの情報に触れることができるな。

TypeScriptのStructural Subtypingの話2

前回の記事ではNode.JSのCモジュールなどTypeScriptの世界だけで完結しない話について型システムがどう扱うか、ということを書いた。

今回は近いところでちょっと違う話。

前回の話を考えているとき、TypeScript自体のソースコードをちょっと調べていて気づいたパターンがある。それはたとえば https://github.com/Microsoft/TypeScript/blob/master/src/compiler/types.ts#L13 などに特有なのだが、 brand という特殊なフィールドを持つ型が何箇所かで定義されている。

この例では

export type Path = string & { __pathBrand: any };

という定義で、これはPathという型を定義しているのだけど、実質的にはPathはstring(文字列)だということを言っている。Node.JSではパスに対しては文字列が使われているという現状からそのようになっている。

そしてTypeScriptはインタフェースさえ合っていれば同じように引数に渡せるため、この型システムではPathとstringを区別することはできない(なぜなら同じ型だから!)。引数の順番を間違えたとか、そういうケアレスミスによって間違った文字列が渡されても間違いが検出できない。

そこで __pathBrand というフィールドを Path 型の定義に追加している。ただの文字列にはこのフィールドはない。またPath型を返す関数は文字列をType assertion (キャストのようなもの)によって変換しているため、実際のコードには __pathBrand は出現しない。

これによって全く同じ型の値をそのまま扱いつつ、些細なミスをコンパイラが検出してくれるようになる。

似たような仕組みは構文木にも現れていて、特定の構文要素をグルーピングするのにこのようなbrandのようなものを使い、間違ったものを渡すようなケアレスミスを防いでいる。

もちろんこのテクニックは完全ではなくて、やろうと思えば適当に __pathBrand のようなフィールドを追加してやることでチェックを迂回できる。でもいかにも意味のありげな名前だし、コメントにも明記されている(vscodeなどではこういうコメントも付随して定義されているし)。そして何よりこのテクニックは正確に検証するというよりはケアレスミスを減らすためのテクニックなので、意図的に回避したい人を止めるものではない、っていうところだろう。


こういうことをやりたければ、ふつうの関数型言語であれば新しい型を定義したり、 phantom type を使ったりするところだと思う。

Go言語であれば普通に新しい型を定義して元の型と互換性をなくすといったところかな。

こんな具合に「構造上は全く同じだが何らかの事情で処理系上は区別したい型」というのは、現実のプログラミングでは意外とほしくなることがあり、インタフェースの一致だけに限定したシステムだけではこういう区別はできない。そういう事情でTypeScriptにはこんなテクニックが使われている、という話でした。

Ruby 3ではこういう問題をどう扱うのかな、というのは気になっています。

TypeScriptのStructural subtypingがうまくいかない場合

少し前にTypeScriptを勉強していて、この型システムだと実装を隠蔽した型というものをうまく表現できてないな、と思ったことがあったのでメモしておく。

TypeScriptはJavaScriptに型情報を与えられるように拡張した(それ以外にもいろいろ拡張のある)言語だけど、JSらしいゆるさを残した仕様になっていて、基本的には構造の同じものは全て受け入れるようになっている(Structural subtypingという)。

interfaceという定義がGoのinterfaceのような感じになっていて、継承関係は全くなくても同じインタフェースのオブジェクト(ようは特定のフィールドを持つオブジェクト)であればコンパイラは通してくれる。

interface Foo {
  foo: string;
}

function foo(f: Foo) {
  console.log(f.foo);
}

foo({foo: 'foo'});  // OK
foo({foo: 1});      // NG: foo is not string
foo({bar: 'foo'});  // NG: interface mismatch

で、世の中には実装を隠蔽したいことがあるのと、TypeScriptだけではうまく完結しないものがある。ある種のファクトリーから生成されるオブジェクトだけを受け入れたい、とか。Node.JSのCモジュールが関連するオブジェクトなので内部実装がJS/TSでは表現不可能だとか。

こういう状況をTypeScriptではうまく表現できないように思える。

内部的なデータ構造は表現できないので、

interface Foo {
}

のように書くと、Foo型の変数にはおおむねどんな値でも代入できてしまう(指定されている構造に一致するため)。そこで本当には存在しないけれども、内部的に何らかのものを持っていますよ、ということを意味する適当なフィールドを入れてみる。

interface Foo {
  _impl: ...
}

だがこの場合でも、_implの型はなんであれ、その値を適当に作って _impl に入れることで簡単にこのインタフェースを実現できてしまう。

まあ実用上はコメントでも書いておけばよいのだろうけれど……。

なお、TypeScriptにはクラスもあるが、クラスもインタフェースが一致していれば通すためこの目的には合致しない。

class Foo {
  foo() {
    console.log('foo');
  }
}

function fooer(f: Foo) {
  f.foo();
}

class Bar {
  foo() {
    console.log('bar');
  }
}

fooer(new Foo());  // No problem
fooer(new Bar());  // OK, Bar is compatible with Foo

ただ、プライベートなフィールドを勝手に設定すると、この問題は解消できる。

class Foo {
  private _impl: any;
  foo() {
    console.log('foo');
  }
}

function fooer(f: Foo) {
  f.foo();
}

class Bar {
  private _impl: any;
  foo() {
    console.log('bar');
  }
}

fooer(new Foo());  // No problem
fooer(new Bar());  // NG

これはTypeScriptではprivateなフィールドは互換でないと判断されるからだ。

ただしこの手法の場合でも、Fooに相当するクラスが(TypeScriptレベルでは)実在してしまうため、通常のクラス操作であるnewもできるし、Fooを継承したサブクラスも定義できる。Cモジュールなどから得られるデータ型はたいてい何らかのファクトリー関数から生成されるので、データ構造がうまくあわなくなることがある。けっきょく、こういう状況をTypeScriptで表現するのは難しい、ということだ。


……という理解だったのだけど、この記事の草稿を書いてから今に至るまでにTypeScript 2.0がリリースされて、この問題は解決された。

どういうことかというと、TypeScript 2.0からprivateなconstructorを作れるようになった。これにより、外からnewできないクラスというものが定義できるようになった。そしてprivateなconstructorを持つクラスは継承できないとされた。

class Foo {
  private foo: any;
  private constructor() {}
  static create(): Foo { return new Foo(); }
}

function foo(f: Foo) {
  console.log(f);
}

foo(Foo.create()); // OK
foo(new Foo());    // NG: private constructor
foo({foo: 1});     // NG: 'foo' should be private

// NG: cannot extend a class with private constructor.
class Bar extends Foo {}

というわけで、こういうクラス宣言により、うえで書いたような状況はうまく表現できるようになった。現実にはd.tsファイルにこういう定義を書いておき、内部実装はJSとCモジュールに適宜実装されることになる。

それにしてもプライベートなコンストラクタというのは、コンパイルされるとどうなるのだろうか。クラス名が手に入ればJSレベルではnewできるんだけど…………と思ったのだけど、どうもTypeScriptコンパイラは可視性の情報を落とすだけなようなので、JSからは普通にnewできるクラスに見えるらしい。まあそりゃそっか、という話であるし、それでいいよなという話でもあるのだった。