matarillo.com

The best days are ahead of us.

5 Project 7 と .NETのジェネリクス

2021-09-25 00:00:21

Project 7がMicrosoftで開始されたとき、MSR Cambridgeの研究者は学術界の流れを取り込むために次の言語を推奨しました: Eiffel, Mercury, Standard ML, OCaml, Scheme, AliceそしてHaskellです。 これを見ると、MSRの研究グループの偏向ははっきりしています:推薦した7つのうち6つは強く型付けされた言語であり、7つのうち3つは狭い意味で「強く型付けされた関数型言語」です。 狭い意味というのは、例えば Hindley-Milner型推論を取り入れ、ファーストクラスの値としての関数を持つということです。 Project 7の商用言語には、Perl、Python、Cobol、Adaが含まれていました。 それぞれに学術的または商業的なパートナーが見つかり、Microsoftから資金が提供され、ワークショップがMSR Cambridgeなどで開催されました。

振り返ってみると、Project 7には欠陥があったものの壊滅的ではありませんでした – プロジェクトに従事しない研究者が何人かいましたし、よく利用された言語実装はほとんどありませんでしたし、すべての言語処理系を維持していくのは高コストでした。 現在でもCOBOL.NETを購入して使用することができるとはいえ、.NETプログラミングはMicrosoftがサポートする言語であるC# とF# によって支配されており、JVMの多言語エコシステムの方がより活気があります。 しかし、Project 7は明確な技術的影響を与えました。 たとえば、この段階でGordonとPeyton Jonesは.NETの設計者と協力し、これらの言語のいくつかをサポートするため、および.NETバイトコードをJVMから差別化する方法として、 末尾呼び出しをファーストクラスの操作として含めるという議論に勝ちました(.NETバイトコードには “tail.” 命令が含まれています)。 これにより、.NETはプラットフォームにもたらされた言語の要求に導かれる、技術革新と差別化という長い技術的道筋をたどり始めました1

Project 7は、「言語の相互運用性」の問題を提起するという影響も及ぼしました。 つまり、共通の基盤をターゲットとする言語を複数得ることと、それらを相互運用させることはまた別の問題だということです。 1999年に、私とその同僚は、次のように主張している内部ホワイトペーパー「COM+ VOSに対する拡張機能の提案」[Syme 2012]を執筆しました。

COM+ランタイムの主な目的は、別のバックエンドランタイムとなり得る環境よりも技術的に明らかに優れたサービスとパフォーマンスを提供することです。2

そしてホワイトペーパーは、Microsoftが「言語革新に真剣に取り組むべきだ」とも主張しました。 5つの技術的特徴が提案されましたが、その中では「一般化されたデリゲート」(すなわち、ファーストクラスの値としての関数)と「強化されたパラメトリック多相」がとりわけ重要でした。 そこではPizzaとGJの影響が強く、この2者は競争相手として明確に言及されていました。 私はまた、これらの機能を組み込んだ.NETバイトコード拡張のプロトタイプであるILXも開発しました。 これは、他のProject 7言語で採用されることを期待して、当初は型消去を行って既存の.NET ILにコンパイルするという方法で.NET上に実装されていました[Syme 2001d]。

このホワイトペーパーは、C# とEiffel、OCaml、Haskellなどの他のProject 7言語の両方で機能する方式のジェネリクスを.NETに提供することを目的として設計された「.NET Generics」プロジェクトのきっかけとなりました。 .NET Genericsとその歴史は他の場所[Warren 2018]でも取り上げられていますが、 その後4年間でSyme、Kennedy、Russoは.NET GenericsをC# と.NETで提供するために多大な努力を払ってきました[Kennedy and Syme 2001]。 この機能はMicrosoftのさまざまな部署からの熱意や敬意や無関心に遭遇しましたが、2001年のGatesのレビューで好評を博したことが、物事を好転させ始めました[Syme [n.d.]b]。 意思決定の鍵を握っていたのはAnders Hejlsbergでしたが、私の記憶では、C# 言語の設計作業の多くは、どうすれば承認を得られるような機能を実現できるかを先読みすることに費やされていたように思います。 最終的に、この機能は2005年に .NET 2.0「Whidbey」リリースの一部として提供されました。 同時に、Microsoftはオープンソースを採用するための最初の非常に暫定的なステップを開始し、.NETコードベースの「シェアードソース」版リリースであるRotorが作られました。 それとともに、.NET Generics実装を含んだ、Gyroと呼ばれるRotor拡張も作成されました。 MSRの社内展示会「Tech Fest」のポスターを図2に示します。

図2. RedmondのMicrosoft Building 33で開催されたTechFest 2002での.NET Genericsポスター(写真は筆者によるもの)

.NET Genericsの重要な前提は、ランタイム環境がジェネリックの具象化を「管理」できることです。 これには、実行時型情報の管理や、新たに発生した具象化用の新しいコードのJITコンパイルが含まれます。 これにより、多相と分割コンパイルを組み合わせる場合に通常必要となる手法である、整数などの「ボックス化されていない」値に対するタグ付けやボックス化が必要ではなくなります。 ランタイムがオンデマンドでコードを特殊化できるからです。

これは、たとえばC# などにおけるエンドプログラミングモデルが、プログラマーの観点から完全で非常にスムーズなジェネリックの形式をサポートできることを意味します。 つまり、実行時型情報は正確で、具象化された型の作成と管理のプロセスは邪魔にならず、具象化されたコードはポリシーに基づいて自動的に共有されます。 .NET Genericsは成功しました:まず、何百万ものC# やF# プログラマーによって広く受け入れられています。 次に、.NET GenericsはC# とJavaを差別化する重要な要因と見なされています。そして、F# 、C# 、および.NETで提供された、後の多くの革新の基礎となっています。 たとえば、ジェネリックコレクション(C# 2.0)、LINQ(C# 3.0)、タスク(C# 4.0)、async/await(C# 5.0)、Span(C# 7.2)は、すべてF# の機能と同様に.NET Genericsを多用しています。 .NET Genericsは.NETを時代の数年先に進め、現在においても、Java、Go、Scala、Swiftなどのシステムは、参照型と値型の両方で具象化をサポートするというような総称性の一面に苦戦しています[Cox 2019; Motroc [n.d.]].3。 同時に、ジェネリクスは、Microsoftの.NET実装を発展させるのに多大なコストがかかる技術的機能でもあります。 ジェネリクスは最も簡単な手段としてJITで実装されましたが、.NETコードの完全に静的なコンパイルを行おうとする試みはこの機能に苦労しています。

F#(まだ存在していませんでした) の歴史の観点からは、.NET Genericsのデリバリーが成功したことで、 .NETは意図的に、強く型付けされた関数型言語から.NETバイトコードへ「直接」コンパイルするのに適した基盤に変わりました。 これは偶然ではなく設計判断によるものです。 たとえば、Hindley-Milner型推論によって推論される総称性を、実行時のオーバーヘッドをほとんどまたはまったく伴わずに.NET Genericsに直接コンパイルすることが簡単にできました。 MLのような方言で書かれた、次の単純なコードを考えてください。

let keyAndData getKey x = (getKey x, x)
let data = [| 1 .. 100 |]
let add x = x + 1
let y = Array.map (keyAndData add) data

ここでは、ジェネリックなコードが整数型で具象化されています。 GJのような多くのジェネリックシステムでは、keyAndData の引数 x のようなジェネリック型の値は、ボックス化(ヒープ割り当て)された形式で表されるでしょう。 したがって、他の最適化がない場合、上記のコードは、(ジェネリックな) keyAndData 関数に入るときに整数のボックス化を引き起こし、(非ジェネリックな) add 関数に渡されるときにボックス化が解除されます。 基本的なコレクション型に対するこのような暗黙のコストは耐え難いものであり、Hindley-Milner型推論言語を.NET上で本質的に低パフォーマンスにしてしまいかねません。 .NET Genericsを使えば、これらの特定のパフォーマンスの問題は解消されます。 .NET Genericsは、後にF# を生み出すための非常に重要な基礎を築きました。


インデックスへ戻る


  1. .NETにおける “tail.” 命令のサポートは、割と長い間、無理やりのパッチ状態で実装されていました:チームに「革新」することを納得させた半面、出荷と保守のためには継続的に手を入れなければならず、コストがかかっていました。 ↩︎

  2. 当時、Project Lightning(すなわち.NET)は “COM+” と呼ばれていました。VOSとはVirtual Object System(仮想オブジェクトシステム)の略で、当時の.NETオブジェクトシステムのことです。 ↩︎

  3. [Alsh 2019]では、Goの設計者であるRob Pikeの言葉が引用されています。過去10年間に本番環境で使用して学んだことを踏まえ、Goを変える時が来ました。この問題が改善されるには長い時間がかかるでしょう。何かが本当に解決するまでには何年もかかるかもしれません。ですから…気長にお待ちください。 ↩︎