matarillo.com

The best days are ahead of us.

6 F# を作成する決定

2021-09-25 00:00:21

MSRでは、Project 7がSML.NETプロジェクトにもつながりました[Benton et al. 2004]。 SML.NETは前述のMLjの続きであり、.NETを対象に変更されたものでした。 SML.NETは、洗練されたプログラム全体のオプティマイザーを使用し、オブジェクトプログラミングの拡張機能を備えたStandard MLの忠実な実装でした。 システムは高品質でしたが、社外のマインドシェアを多く得ることはありませんでした。 2001年に、私はSML.NETに不満を抱いていました。SML.NETは、.NET自体がすでに公開されても、まだリリースされていませんでした。 同僚の研究目標は十分に尊重してはいましたが、私は1998年にWadlerによって特定された7つの主要なテーマ[Wadler 1998]に取り組むために、強く型付けされたFPが多くのプログラマーによって容易に受け入れられる方法で出荷されるのを見たかったのでした。 その際、OCamlの実装が私に影響を与えました:OCamlは比較的直接的で簡単なコンパイル戦略を使用していて、許容できる範囲で安定した性能を達成するためにプログラム全体のコンパイル戦略が必要であるかは明確ではありませんでした。 一方で、SML.NETは.NET Genericsをターゲットにしておらず、そのようにする明確な計画もありませんでした: SML.NETコンパイラーは、パフォーマンスとコンパクトなコードを取り戻すことを目的として、プログラム全体のコンパイルと広範なモノモーフィゼーションの活用を前提としていました。 研究室ではありがちなことですが、意見の相違が起こりました。

当初、2000年後半のことですが、私はReuben Thomasと共同で、Glasgow Haskell Compiler(GHC)の「コア」中間表現から.NETバイトコードへ直接変換することで、Haskell for .NETの実装を試みました。 この経験は部分的に成功しました:小さなプログラムなら動きました。 しかし、私はSimon Peyton Jonesの助言を受け、Haskell.NETはいくつかの技術的および文化的な理由で成功することはできないと信じるようになりました1

  • 他のProject 7言語と同様、Haskellを.NET上で「単独に」実行するだけでは十分ではありませんでした。 主な目的は、.NETライブラリーと完全に相互運用できる、.NETエコシステムの一部でとなる関数型言語を作成することだったからです。
  • 完全な相互運用とは、すべての.NET関数がHaskellではHaskellの型を用いるようにレンダリングされる必要があることを意味するので、型変換が必要です。 両者の型システムは同じではなかったので、変換は面倒で、多くの場合は単に変換不能です。
  • さらに、変換を容易にするためには、何らかの形式のサブタイピングとオブジェクトプログラミングに対応できるようにHaskell自体を改造する必要があったでしょうし、 最終的には既存の.NETクラスを拡張する機能も必要となったでしょう。 Haskellコミュニティは、特定のプラットフォームの要件によって引き起こされる言語の大幅な変更は、検討することすら消極的でした。
  • 当時、ほとんどすべてのHaskellコード(ライブラリーを含む場合)には、対応する.NET側のサポートが欠けている技術的な機能が必要でした。 高カインド型変数や、軽量スレッドや、例外(Haskellの例外セマンティクスをもつもの)や、 エフェメロン(訳注:弱参照キーのテーブル)や、ソフトウェアトランザクショナルメモリーなどです。 したがって、相互運用はさておくとしても、どんなHaskellプログラムも.NET上でうまく動くと主張するのは無理があり、サブセットしかうまく動かなかったでしょう。

そのため、Haskell.NETの開発は2000年後半に中止されました。

OCamlとJVM/.NETの問題もこの頃のCamlメーリングリストで議論されていました。 たとえば、次のメッセージは2001年2月6日に私が送信したものです[Syme 2001a]。

件名: OCaml on CLR/JVM? (Was RE: OCaml <–> ODBC/SQL Server)

ちょっと探しても見つからないのは、OCamlでODBCデータソースを使って問い合わせたりインターフェースしたりする簡単な方法です…

今、私は言うまでもないことを言わなければなりません: CamlがJavaまたは.NET Common Language Runtimeとシームレスにインターフェースをとることができれば素晴らしいことではないでしょうか? そうすれば、既存のライブラリーを利用するだけでよくなるので、このような質問や問題に直面し続ける必要がなくなるでしょうから。

私は、.NETバージョンのOCamlを目指して私と一緒に仕事をしてもいいと思う、時間に余裕のある人がいるかどうかをすごく知りたいのです。 私はこの件についてこれまで何度もXavierと話し合ってきましたし、Haskellの.NETコンパイラーを作ったときに、コア言語のための基礎となる作業をたくさん行いました。 .NET上でCamlを動かして相互運用することに興味がある人は、あるいはその件について議論するメーリングリストに参加したいだけでもかまいませんので、連絡をください!

これは、OCamlの.NETをターゲットにしたバージョンを作成したいという私の願望を初めて公にしたものです。 2001年2月8日にLeroy(訳注:Xavier Leroy、OCamlの開発リーダー)が返信しました[Leroy 2001]。

私は、OCaml/Javaのインターフェースに取り組んだり取り組まなかったり(最近はほとんど取り組んでいませんが)しています。 その仕組みは、外部関数インターフェース(JavaはJNI、OCamlはCインターフェース)を介して2つのシステムをCレベルで結合するというものです。 これは、Haskell/Javaの同様なインターフェースに関するErik Meijerらの研究に強く影響されたものです (彼らHaskellの人たちは間違いなく言語の相互運用性の最先端にいます。これは、私が2番目に盗んだ相互運用性のアイディアです。最初はIDL/COMバインディングでした)。

低レベルの結合は驚くほど簡単です。2つのガベージコレクターを協調させることもできます。 というのは、JNIとOCamlのCインターフェースは、両者の実装を全く変更しなくても、結合を機能させるのに十分な機能を提供しているからです。なんて素敵なことでしょう。 唯一の制限は、クロスヒープサイクル(Javaオブジェクトを指すCamlブロックを指すJavaオブジェクト) は決して回収できないということですが……(これを指摘してくれたMartin Oderskyに感謝します)。

もちろん低レベルのインターフェースは型安全ではありませんから、本当にやりがいがあるのは、 JavaのクラスやオブジェクトをCamlのクラスやオブジェクトとして、あるいはその逆に見せる、型安全なビューを構築することです。 この件については、私はいまだにいくつかの問題に苦しんでいます。 たとえば、JavaのオブジェクトをCamlの抽象型の値にマッピングし、メソッドをそれら抽象型の関数として扱う方が、 JavaのオブジェクトをCamlのオブジェクトにマッピングするよりもはるかに単純(実装上のことで、エンドユーザーの使用感のことではありません!)だとわかりました。 それは全く予想外でした!

私が学んだことの1つは、言語の相互運用性に関する本当の問題は、 言語Xを仮想マシンYで動くようにコンパイルする方法(効率は良かったり悪かったりするでしょうが、これは常に行うことができます)ではなく、 Xのデータ構造とオブジェクトを、Yで動くようにコンパイルされる他のすべての言語Z1 … Znのデータ構造とオブジェクトへマッピングする方法です。 今思えばそれは明白なのですが、多くの人(私自身も含む)がしばしばこの点を見落としており、 相互運用性のためには同じ仮想マシンにコンパイルすることが必要かつ十分であると信じているように思います。 実際にはそれは必要でも十分でもありません……

この作業はJVMで始めたものですが、.NET CLRがJNIと同等の機能を持つ外部関数インターフェースを持つようになれば、.NETでも動くようにできることは間違いありません (そして、いずれそうなるだろうと確信しています。そうする意味があるという理由だけではなく、Javaが持っている機能なら.NETも持ってないといけないだろうから :-) です)。 今後の開発をお待ちください。

上記は、これまでに多くの言語が直面してきた基本的な問題を示しています。 ある言語は、自身のランタイムを保ち、.NETやJVMとは間接的に相互運用するべきでしょうか、それとも.NETやJVMのランタイムを直接ターゲットにするほうがいいのでしょうか?2 Leroyの返信には意見の相違がみられました: Project 7ではとても密接な相互運用性を想定していました。メモリー、コード、リフレクション、JIT、GC、およびライブラリー機能を含む1つの仮想マシンを共有することで、 ホスト側エコシステムのオブジェクトシステムを対象言語に組み込める可能性があるものです。 Leroyが説明したアプローチは、技術的には、既存のOCaml実装にとって非常に賢明なものでしたが、いざ.NETを想定した場合はしっくりきませんでした。 私には、言語間の境界で、パフォーマンス、相互運用性、ツールなどの問題に本質的に遭遇しそうに思われました。 そしてこのアプローチの採用は、.NETとOCamlの両方の実装に依存することを厭わない、共通部分に限定されるだろうとも。

この議論には、Standard MLのプロプライエタリー実装であるHarlequinのMLWorks[Wikipedia 2019a]を実装したDave Berry(後にDaveはMSR Cambridgeと契約して.NET Genericsのオープンソース版を開発しました)も加わりました。2002年2月9日付けの投稿は以下の通りです[Berry 2001a]。

今、私は言うまでもないことを言わなければなりません: CamlがJavaまたは.NET Common Language Runtimeとシームレスにインターフェースをとることができれば素晴らしいことではないでしょうか? そうすれば、既存のライブラリーを利用するだけでよくなるので、このような質問や問題に直面し続ける必要がなくなるでしょうから。

この見方は理解できますが、少々単純ではないかと思います。 ……別の見方をすると、OCamlはすでにCと(少なくともネイティブコードのコンパイラーと)プラットフォームを共有しているので、すべてのCライブラリーはすでに利用可能です…… それでもCライブラリーとリンクするのは大変な作業です。なぜJavaと.NETがもっと簡単だと言えるのでしょうか。 それに、MLjを使ったML/Javaシステムの構築に向けてなされた取り組みを見てください……。 スレッドは、潜在的に問題となりえるもう1つの分野です。実際、スレッドは全くの地雷原になる可能性があります。

2002年2月10日に私が返信した内容は[Syme 2001b]:

このビジョンを実現するにはかなり大変なことを成し遂げなければなりませんが、 原則として、クリーンな相互運用のストーリーは、他の人が書いた言語Xのコードを言語Yのライブラリーとして際限なく書き直すことを確実に上回るものです。 Project 7に関わっている私たちは、この相互運用を実現するための1つのアプローチ、すなわちMLjのように言語を.NET MS-ILに直接コンパイルし、 時には相互運用性を改善するために言語に拡張を追加することに取り組んでいます。 また、.NETインフラストラクチャの改善にも取り組んでおり、MS-ILにおけるパラメトリック多相などの機能のサポートを提案しています。

Xavierが述べたように、彼もOCamlのための解決策に取り組んでいます。 しかし、実際にものを動かすためにあなたが取るアプローチが何であっても、オブジェクトモデルの構成体をMLやHaskellやOCamlにどう反映させるかという問題は変わりません。

私たちのアプローチの方が簡単になる理由はいくつかあります。 たとえば、JavaバイトコードやMS-ILにコンパイルした場合、何の努力もせずに相互運用境界を越えて例外を伝播できる可能性があります。 バイトコードにコンパイルする場合は、たとえばMLのint64をCのint64と表現的に等価にするなどといった、表現の互換性をより多く保証することもできます。 バイトコードにコンパイルしない場合は、Camlの相互運用境界を越えるために整数すらマーシャライズする必要があることを忘れないでください。 自動化することができるとはいってもです。

Javaと.NETのオブジェクトモデルのセマンティクスはCと比べて非常にシンプルなので、オブジェクトの転送により一貫性を持たせることもできます。 たとえば、ポインタを “in-out” や “in” や “out” パラメーターとして解釈するためのIDLは不要です。

あるレベルまでは、Xavierのアプローチ、つまり2種類のランタイムやガベージコレクターなどを維持することが好きですが、 .NETアプローチの一部として想定されている(そして実際に現在C#、C++、VB.NETおよび他の.NET言語で使われている)多言語コンポーネントプログラミングにまで広げるのは困難です。 GCが2つあるだけでもすでに十分問題です(両方ともキャッシュを使い切るようにチューニングされているため、パフォーマンスが低下する可能性があります)が、 1つのプロセスに10言語のコンポーネントがある場合はどうなりますか?10個のGCが注意を払われるために競争するのですか? 機能するようにすることは不可能ではないでしょうが、1つのGCがコンピューティングインフラストラクチャの一部を形成し、そのサービスを共有することを受け入れるほうが、 概念的にはある種の明瞭さがあります。 こういった側面に関して、.NETアプローチには非常に説得力があると私は思います。

余談ですが、こういう言い方も面白い質問だとは思います。「OK、私たちの言語の最終目的はこんなコンポーネントを作成することだということに疑問の余地はないとしましょう。 そのインターフェースは、Javaまたは.NETの型システムで表現されていますが、OCamlとMLの機能と概念の単純さはできるだけそのままなのです。」というものです。 あなたが最終的に何を求めているのか私には正確にはわかりませんが、何かしらをC#やJavaから引き継ぐ言語になるのかもしれないと思います (あなたが興味を持っているものがそれであるなら……)。 ですが、Java/.NETコンポーネントの構築を最初から真剣に受け止めないと、ちょっとしたハックで終わってしまいかねないと感じているのです。 つまり面白くて、使えるハックかもしれませんが、それほど 良い 言語ではないというものに。

私が見てきた中では、この種の作業で繰り返し起こる技術的問題の中でも最大のものはおそらく型推論の問題であり、 そしてJavaと.NETのモデル化の両方が、APIを受け入れやすくするためにサブタイプとオーバーロードの両方に依存しているという点です。 型推論は、サブタイプやオーバーロードのどちらでもうまく機能しません。 このことはもう本当に、とても残念なことです。なぜなら型推論は、生産性を向上させるためにMLが提供しなければならない主要なものの一つだということが明白だからです。

追伸:スレッドに関して – あなたはスレッドの問題をまずいものと思っているかもしれませんが、私はその半分ほども悪いと思っていません。 最終的に、OCamlスレッドはある時点でWindowsスレッドにマッピングされます。 言語間でスレッドを共有することを意味なくさせるような、典型的なMLおよびCamlスレッドライブラリーの特別な論理プロパティがそれほど多いとは私には思えません (バイトコードにコンパイルするときに、非同期例外が物事を難しくしてしまうことは本当だとしても)。 とはいえ、この問題について私は専門家ではないことを認めます。

最後に、技術政治の論争も起こりました。今回は2002年2月12日のFabrice le Fessantからの返信を見てみます[le Fessant 2001]。

.NET VMはオープンソースなのですか?Microsoftに依存しない部分はどこですか?……

Microsoftが自社の新製品を使用してもらいたいのなら、自社のVMにもっと多くの言語を移植するのはMicrosoftの問題です。 こんな風に言うだけではだめです。「私たちは自家製の言語(C# 、C++、VB.NET)を《専用に設計された》VMに移植しました。 ですので、このVMがユニバーサルVMだと証明できたわけです。さあ、あなたたちの言語も同じようにしてください。 そうしないと、私たちの顧客はもうあなたたちの言語を使わなくなるでしょう……」

それで、OCamlの.NET移植版なんて本当に必要なのですか?OCamlはWindows上でも、他の多くのOS上でも問題なく動作しているのに……

オープンソース、標準、相互運用性、クロスプラットフォーム実行のメリットについて議論するスレッドが続きました。 それらはF# をもってしても13年間も解決できず、F# だけでなくC# と.NET Coreが最終的にオープンソースかつクロスプラットフォームになってやっと解決された問題でした。 Dave Berryによる2002年2月16日の投稿はもっとポジティブでした[Berry 2001b]。

Microsoftは、プログラミング言語研究者への働きかけに対し、褒められるべきだと思います。 私としては、MLをコンパイルするための優れたターゲットとなる、広く行き渡ったVMを歓迎するでしょう。 同じVM上の他の言語との相互運用性は、あれば嬉しい程度のおまけです…。 とは言っても、相互運用性は依然として困難ですが……

そこには正当な議論と用心深さがたくさんありました。 そして、私はOCamlとその既存のユーザーベースに対してきちんと敬意を払おうと、この時点で決心して進めることにしました。 私はOCamlと、OCamlが示すプログラミングへのアプローチを本当に気に入っていたからです。

.NETなどのソフトウェアインフラストラクチャやアーキテクチャーの将来の軌道を予測することも、決定を下す際の重要な要素でした。 たとえば、2002年3月3日のArturo Borquezによる最後の返信がこちらです[Borquez 2001]。

私は間違っているのかもしれませんが、本件について私が信じていることを述べさせてください。 ……C# がVBのように「多数派」にリーチすることは決してないので、C# はあまり重要ではありません。 ……本当の問題は……クライアント/サーバーモデルです。 ……私の意見では、このモデルには未来はありません。 ……多様で幅広いクライアントデバイス(端末)においては……クライアントは最小限になるからです。 私の結論は、……CLR/JVMはCamlの将来にとって重要ではありません。どちらも死んでしまうからです。 Camlは、通信技術のアップグレードに合わせてライブラリーの更新をいくつか行うだけで済むのです。

後から見ると、このような予測は正しくもあり間違ってもいました。 アプリケーションの構造は広範囲に進化し、.NETとJVMは最終的に「ミドルウェア」としての役割を強調しなくなりましたが、.NETもJVMも死んでいません。 言語とランタイムはアーキテクチャーよりも長持ちするようです。

さて、2001年中頃には、例の困りごとはまだ残っていました。 MSRはどうやって、強く型付けされた関数型プログラミングを多くのプログラマーが簡単に採用できるような形で.NETにもたらそうとしていたのでしょうか? 2001年10月10日に、私は確信を感じながら次のように返答しました。

時間があればCaml用の.NET CLRコンパイラーを実装する予定です。 最初は、私はコア言語、そしておそらく最優先のモジュールだけを実装し、それからその後でいろんなことを評価します。 既存のOCamlコンパイラーのソースを使用するのではなく、ゼロからコーディングして実装しようと思います。

これに着手する一番の理由は、私が既存のOCamlコードベースを持っていて、それを.NETライブラリーとして利用可能にしたいと思っているからです……。 加えて私はCamlが大好きなので、それが.NETでサポートされることを望んでいます。 そして、関数型言語間の相互運用性が.NETにおいて実用的であることを証明することにも興味があります。

この実装パスによって、オブジェクトイントロスペクション機能がただで手に入るでしょう。 ですが、間違いなく既存のネイティブコードのCaml実装より遅くなるでしょう。 代償なしで手に入るものはないのです。

Camlの.NETコンパイラーを開発する アクティブな 取り組みについては他にあるかどうか知りません。 SML.NETは近々公開される予定とのことですが、そうであればいいと思います。

そして2001年後半までこの道は継続しました:.NET自身をターゲットにしたOCaml言語の変種を生み出すことです。 OCamlに関連したProject 7の取り組みでは、Leroyによる上記のアプローチが行われるようになっていたのですが、継続する見込みはありませんでした。 これによって、.NET IL自体をターゲットにしているものの、新しいCaml.NETイニシアチブの余地が生まれました。 そして2001年12月に私は “Caml.NET” で前進することに決めました。 その後、Cedric FournetやGeorges Gonthierとした内部議論の結果、OCamlから大きく離れて言語実験をスコープに入れられるように、 “F#” とブランド変更されました。3


インデックスへ戻る


  1. 後のまとめも参照。.NET用やJVM上のGHCがないのはなぜですか? [Haskell Contributors 2017]. ↩︎

  2. 興味深いことに、この議論は、2000年代のC# やF# の設計作業の多くを占めることになる、データ統合の文脈でもそのまま起こりました。 ↩︎

  3. “F#” の “F” は、「関数型(Functional)」と「System F」の両方から取っています。System Fは単純型付きラムダ計算のエレガントな変種の一つです。今日では、F# コミュニティは「楽しみ(Fun)のF」と言っています。 ↩︎