Archive for the ‘Computers and Internet’ Category
共変性(Covariance)/反変性(Contravariance)の話
ずっと思ってたことではあるけどいよいよ気になってきたんで、昔取ったメモを見返したり、圏論とか代数幾何を調べてみたりしながら書いてみる。
C#(に限った話じゃないんですけど)デリゲートとかジェネリクスの共変性・反変性、言いたいことはわかるんだけど、名が体を表してなくてものすごく気持ち悪いんですよねぇ。
元々、共変性・反変性って言葉の意味は「共に変化する」「反対に変化する」って意味だったんですけどね。デリゲートとかの共変性・反変性は何が共に変化して、何が反対に変化するか微塵も意味が残ってない。たどっていくとつながってないこともないんですけど、紆余曲折経てなんかよく分からないことに。
どうたどられていくかというと、デリゲートの共変性・反変性 → 圏論: 共変関手・反変関手 → 代数幾何学 → 多様体論における共変ベクトル。最後の共変ベクトルまで行くと、「基底と同じ変化の仕方をするベクトル」って意味になってるはず。
共変ベクトルと反変ベクトル(数ベクトルの場合)
直交座標しか使ってなかったり、座標変換ってものに触れないうちは気にしなくてもいいことなんですけども、ベクトルには座標変換のルールによって2つのタイプのものがあります。
- 共変ベクトル
- 基底を e1, e2 とでも置くと、a = p・e1, b= p・e2 となる (a, b) を作って、この数ベクトルで点 p を表す方法
- (a, b) の座標変換ルールは e1, e2 の座標変換ルールと同じになる(e1 を x 倍すると a も x 倍になる)
- 反変ベクトル
- 基底を e1, e2 とでも置くと、p = a e1 + b e2 と書ける (a, b) を探してきて、この数ベクトルで点 p を表す方法
- (a, b) の座標変換ルールは e1, e2 の座標変換ルールと逆になる(e1 を x 倍すると a は 1/x 倍になる)
ちなみに、共変ベクトルと反変ベクトルは互いに内積を取ると座標変換ルールが打ち消しあって、座標の取り方によらない量(スカラー)になります。こういう関係を「双対(dual)」といいます。
参考: 反変座標と共変座標
共変ベクトル(微分幾何学の場合)
力学(特に相対性理論とか)では、曲面上に拘束された物体の運動を微分方程式で表したりします。その過程で、微分可能な曲面とか立体の理論が発達しました。
概要だけいうと、以下のように共変ベクトルが定義されます。
- 曲面上の任意の関数は、接線を1つ与えれば、その接線に沿った微分が定義できる
- この「接線に沿った微分」のことを「接ベクトル」と呼ぶ
- 実際、微分演算子ってのは線形性を持つ(線形空間をなす)ので、何らかの基底を与えてベクトルとして表すことができる
- 例えば、偏微分演算子 ∂/∂x, ∂/∂y を基底として、a ∂/∂x + b ∂/∂y って感じのベクトルを作る。この微分演算子は接線に沿った微分を表す
- こうやって作った「接ベクトル」は共変性を持つ(微小変分 dx とか同じ変換ルールになる)ので、共変ベクトルと呼ぶこともある
ちなみに、この場合、反変ベクトルの方はというと以下の通り。
- 共変ベクトルの双対として定義
- 共変ベクトルが接ベクトルになるのに対して、反変ベクトルは微分形式として理解できる
- a dx + b dy みたいなの
参考: 共変ベクトルと反変ベクトル
共変ベクトル(多様体)
近代数学の進化の方向性の1つに「座標系の取り方によらない理論を作る」というのと「そもそも何らかの座標系の存在を仮定しない内在的な(intrinsic)な定義を目指す」というのがあります。で、曲面とか立体を、intrinsic な定義を目指して抽象化したものを多様体(manifold)といいます。
具体的にどうするかというと、
- 「共変ベクトル = 微分演算子」という考え方はそのまま使う
- 微分演算子の定義の仕方を、∂/∂x とかせず、以下のように行う
- 多様体(曲面とか立体)上の任意の関数 f, g に対して以下の条件を満たす写像 v を接ベクトルと呼ぶ
- v(f + g) = v(f) + v(g)
- v(a f) = a v(f)
- v(f g) = v(f) g + f v(g)
- 多様体(曲面とか立体)上の任意の関数 f, g に対して以下の条件を満たす写像 v を接ベクトルと呼ぶ
要するに、共変ベクトル = 接ベクトル = 微分演算子を以下のようにとらえる。
- 微分演算子ってのは 関数 → 関数 の変換(こういうのを、圏論的には関手(functor)と呼ぶ)
- 微分演算子は線形性を持つ(1 と 2)
- 微分演算子は積の法則(3)を持つ
こういう定義の仕方なら、座標(x とか y とか)が一切出てこないので、座標の存在を一切仮定しない = 内在的な定義って言われる。
参考: 接ベクトル・微分形式の内在的定義
代数幾何とか
上述の通り、共変ベクトルってのは微分演算子のことで、それは結局のところ、関数 → 関数 の変換の一種。
で、代数幾何学って分野において、「この 関数 → 関数 の変換って実は圏論(category theory)ってやつでいうところの関手(functor)よね」ということに気付いた(実際には代数幾何の発展の過程でそういう理論が整備された)らしく、共変ベクトルの性質を圏論を使って調べられるようになったとか。
多分、そういう歴史的背景から、共変ベクトルに相当する関手が共変関手、その双対(反変ベクトル)に相当するのが反変関手って名前になったんじゃないかと。(この辺りからちょっと自信ない。誰か詳しい人はいないものか・・・。)
ここまで来ると、こういう歴史的背景というか、順を追って理論知ってる人でないと元の言葉の意味(共に変化する・反対に変化する)が全く意味不明。
プログラミング言語における型の共変性・反変性
で、ようやくプログラミング言語における型の共変性と反変性って言葉。
- 型の継承関係的な順序を保つのが共変
- string ―[inherits]→ object なのに対して、IEnumerable<string> ―[assignable]→ IEnumerable<object>
- 戻り値にだけ型が使われてる
- 共変関手を使って説明できるとかなんとか
- string ―[inherits]→ object なのに対して、IEnumerable<string> ―[assignable]→ IEnumerable<object>
- 型の順序を逆にするのが反変
- string ―[inherits]→ object なのに対して IComparable<string> ←[assignable]― IComparable<object>
- 引数にだけ型が使われてる
- 反変関手を使って(以下同文)
- string ―[inherits]→ object なのに対して IComparable<string> ←[assignable]― IComparable<object>
参考: 共変性と反変性(圏論由来関連のとこにある説明はたぶん誤訳。正しくは「圏論における定義によって、計算機科学におけるこの用語の定義とベクトルにおけるこの用語の定義が統一的に理解できる。」とかかと。)
一応、「型の順序関係を反対にする方が反変」って覚え方はできるものの、もともとの「共に変化・反対に変化」ってニュアンスとはずいぶん変わってるんですよねぇ。
正直、最初にこれを共変性・反変性と呼んだ人は結構罪深いと思う。あと、C# で covariance/contravariance とかそれに類するキーワードじゃなくて in/out ってキーワードにしたのは非常にいい判断だと思う。(これに対して、MSIL の仕様上は covariant フラグと contravariant フラグって名前になってる。いまいち。)
なんかもっといい呼び名はなかったものかなぁ・・・
Go Language 色々
go language、発表から一日経って、そこそこネットで話題になってきてるみたいで。
インターフェースの仕組み
コードまで追っかけてる人にあれの仕組み聞いてみた。
- interface 自体が、仮想関数テーブル的な構造体らしい
- インスタンスを interface に代入するときにボクシング的なことしてる(参照+仮想関数テーブルな状態を作ってる)
うーん、それで多重ディスパッチ( (a, b).Method() みたいな構文)できないのか。
やってくれてもよさそうなもんなんだけどなぁ、あの文法なら。V8 の開発者もかかわってるって話なんで期待してたんだけど。
C++に代わる
- 「C/C++に代わる新言語」って過多書きじゃないかなぁ。
- さよなら絶望先生のネタで、「面白い面白いって言われすぎるともう面白いって思えないじゃないですか!」ってやつ
- おもしろまんが 絶望先生、抱腹絶倒 絶望先生
- 危険な恋のカリスマ
- 「C/C++ と同じように、システムソフトウェアの作成を目的として作られた次世代の」って感じのニュアンスの言葉が、和訳とマスコミフィルター通った結果か
みんな、過度な期待をしすぎなのかもなぁ。「C++ も Python も駆逐するようなすっげー言語期待したたんだけど」みたいな目で見られたら、「もうすごいって思えないじゃないですか!」な状態になりますとも。
Golang の変わったところ
Golang の独特な点っていうと、
- インターフェースがダックタイピング的なところ
- 型の継承やめて、全部コンポジションと mix-in でやるところ
じゃないかなぁ。それで、既存の(継承ベースの)OOP 言語とぱっと見似たような文法にできてるんだから、そこはなかなか頑張ってると思う。
あとは、現状、Erlang とかの関数型ベースな言語でしか見かけないアクター型同時実行のための構文を、C 系文法の中に組み込んだこと。(研究レベルなら MS にも Axum があるけど。)
世の中が欲してるのは、「レガシーを捨てた C++」あるいは「型安全な Python」だった気もするんですけどね。ところが、出てきたものが独特の路線でみんな困惑。
Ericsson Language 略して Erlang、読みはアーラン
Go っていうと「PSP Go のことかと思った」とかさんざん言われるんで、もう公式に Golang でゴーランでいいんじゃね?とか思ったり。随所に Erlang 臭さを感じるし。
Go Language
さて、こんなのが出たわけですが。
どんな言語?
さて、まず、公式ページの FAQ のとこの説明を意訳。
- 背景
- コンピューターはずいぶん早くなったのにソフトウェア開発はそんな早くなってない
- C 言語のヘッダー、いい加減死ねよ
- Java とか C++ みたいな型システムだけじゃなくて、動的言語の型システムもあるよね
- ガベコレとか同時実行(parallel)プログラミングとか、ちゃんとサポートしないとね
- マルチコアな時代来た
- ってことでこんな言語(Go Language)作ったんだ
- コンパイル速いよ
- ヘッダー?何それ?
- 継承の階層持たないライトウェイトな感じの型システムを型安全に実現
- ガベコレ持ってるし、並列処理を書くための機能を提供
- マルチコアマシン上で動くシステムソフトウェアを作るために設計されてる
要点
- 型システム
- 仮想関数テーブルじゃなくて動的な型を見ながら動的ディスパッチしてるっぽい
- C#er な人向けの説明すると:
- メソッドは全部、C# でいうところの拡張メソッド
- dynamic を使って動的多態性を実現してるようなイメージ
ちょうどこないだ清書したばかりの記事に動的ディスパッチの話もあるんで、そこを参照仕様見てる感じ、多分ね。内部実装まで見てないんで自信持ってはいえないですが- 追記:中身追った人に聞いたらこっちらしい → ひょっとすると、頑張って VTable 作って Boxing してくれてる可能性もありますが。
- 基本的にダックタイピング
- ただし、C++ 0x の(正確には 0x に入るはずだったけど仕様から漏れた)Concept っぽい機構で型安全性を確保してる
- interface for duck typing って感じ
- 匿名フィールド(メンバー変数)ってのを持つことで、そのフィールドのメソッドを mix-in できる
- Mark & Sweep っぽい
- GC 持ちのくせにポインターがある!
- まあ、C# 的にいうと、& は Boxing Operator なのかも
- 基本は値型、*Type で Nurable<Type>、&x で x を Boxing
- Erlang 系の、いわゆるアクター型の同時実行
- MS 風に言うところのエージェント型
- Axum が採用してるやつ
- スレッド間でデータを直接共有せずに、通信はチャネルを通したメッセージ送受信だけで行う
個人的な感想
全体的な感想としては、Scala とかの関数型言語の文化を、きれいに C 系文化に移植したって感じかなぁ。(型システムの辺りだけね。変数の immutability とか関数の再帰地獄とかはない。)
C++ や Java、C# の競合っていうよりは、個人的には Erlang とか Scala、F# の競合に見える。
「関数型言語の発想を、C 系の OOP 言語の文化に馴染ませた」って意味では発想はなかなか素晴らしいと思う。
Q. C# の競合になる?
正直ないなぁ。
まだまだ詰めが甘いってのもあるけども、その問題は時間が解決する可能性もあるんで今は責めない。
それよりは、なんか立ってる土俵が違いすぎる。
- なんか、思想の裏にあるのが基本的に Erlang 的な関数型言語
- それを C 系統の OOP 言語の文化に馴染ませた感じ
- C# は当然 .NET な言語で、Go はネイティブな言語
Q. じゃあ、他の言語との競合関係は?
C++ の置き換えは無理だろうなぁ。それは頑張って欲しい気もするけど、できたとして10年後ってところか。
Erlang よりは流行ると思う。
D はきわどい。C# と同じ理由で D と Go も微妙に土俵が違うんだけど、D は C++ と Go の間で人知れず忘れられていきそうな気も。
面白そうって思うところ
こいつの型システムと同時実行機能はなかなか興味深いと思う。それ以外だと:
- var x int; とか x := 0; って書き方は結構好き
- メソッドはもっといい
- func (x InstanceType) MethodName (arg1 T1, arg2 T2) (ret1 U1, ret2 U2) { Body }
- 最近、「自分で文法考えるならこうする」って思ってたのにかなり近い
- 多重代入
- a, b = b, a; でスワップになる
- これ、右辺値が短いうちは可読性も失われないしいいんだけどなぁ。長くなってくるとどうだろう・・・
- ちょっと不格好だけど
- ただし、Statement としては Increment/Decrement Statement はある
キモいって思うところ
- Go って名前はないと思うなぁ・・・
- 日常語として頻繁に使われる動詞と同綴りとかやめて
- マスコット・・・
- これはない。むしろいない方がいいだろうに
- メンバーのアクセス制限が緩い(public, protected, private とかがない)
- C++ よりもむしろ Erlang だから・・・
- 配列型・・・
- int[] じゃなくて []int
- map との兼ね合いなんだろうなぁ
- 例えば、「map の配列」の書き方は [] map [string] int
- string の要素が byte、すなわちマルチバイト文字にはつらい
- 日本じゃ致命的
- 始まる前からオワタ\(^o^)/
- UTF8 だって
- 日本じゃ致命的
- map の仕様キメぇ
- r, ok := a[x]; で、キーが x の要素を持ってるかどうかが ok に、もし持ってるならその値が r に入るんだって
- RangeClause 持ちの for キメぇ
- for i, s := range a { body }
- i にインデックス、s に値
- C# な人間としてこれだけは言いたい:
- switch だけは C 系文化を引きずっちゃダメだ!
- まあ、break とか書かなくてもデフォルトで break してくれるところはまだマシか
- でも、fallthrough 文・・・それはそれでキメぇ
- まあ、例外に否定的なわけじゃなくて「複雑なんで当面勘弁」って感じっぽい
- 基本的に duck typing な言語なんで、ジェネリクスは不要かも
ネイティブってそんないいかねぇ
あと、言語の文法の話から離れると、やっぱネイティブより Managed な方がいいなぁ。
- Common Type System 持ってない = ライブラリの互換性ない = 言語用にライブラリ作らないとダメってのがきつすぎる
- その辺りは C/C++ との interop で何とかするんだろうけど
- あと、処理をランタイム側に投げれないせいで、実行ファイルサイズ肥大化する
- Hello World 書くだけでもコンパイル結果が結構でかくなるみたいだし