この付録では Nemerle 以外のプログラミング言語として C# や F# を取り上げ, Nemerle との比較を行います。
目次
言語比較
まとめ,あるいは言語選択の指針
以下でそれぞれの言語の違いについて気の向くままに述べて行きますが,先に結論を述べておくと,いずれの言語を選ぶかは概ね次のようになると思います。
Visual Studio のサポートを最大限に活かしてインテリセンス指向のコーディングを行いたい場合は C# を選びます。これは C# の最大の強みです。特に言語の礎となる .NET Framework との相性は抜群です。
型安全指向で,堅牢なプログラミングを行いたい場合は F# を選びます。型の定義が容易で,静的型付け言語のメリットを最大限に活かせると思います。アクティブパターンや判別共用体を中心としたパターンマッチの軽快さは魅力です。
メタプログラミング指向で,コンパイル時に様々な処理を行わせたい場合は Nemerle を選びます。コンパイル時に様々な複雑な問題を解決出来るのは非常に大きなメリットになります。また,関数型プログラミングとオブジェクト指向プログラミングの両スタイルに柔軟に対応出来ます。
プログラミングスタイル
C# の変数はデフォルトで可変です。一方 F# と Nemerle はデフォルトで不変です。
C# や Nemerle は .NET Framework のオブジェクト指向ライブラリーを自然に拡張・利用できます。 F# も拡張・利用は容易ですが,他言語との相互運用を考える場合, protected
が宣言出来ないのは割と不都合です[A]。
型
概ね F#, Nemerle, C# の順番で利便性があります。
C# 3.0 で型推論のキーワードとして var
が導入され,従来のように変数の型を明示する必要がなくなりました。しかし型推論の範囲は狭いです。例えばラムダ式では, Func
の型パラメーターを明示する必要があります[B]。また,コレクションを初期化する際に, Nemerle や F# ではその後の使用方法から型パラメーターが型推論されるため型パラメーターを省略できますが, C# では型パラメーターを省略することは許されません。
// C# var x = Func(); Func<int, int, int> add = (x, y) => x + y; // Func の型は省略できない var dictionary = new Dictionary<int, string>(); // 型パラメーターは省略できない
// F# let x = Func () let add = fun x y -> x + y let dictionary = Dictionary ()
// Nemerle def x = Func(); def add = _ + _; def dictionary = Dictionary();
F# ほとんどの場所で型推論が働くために,多くの場合に型を省略できます。一方 Nemerle の型推論は局所的で,型のメンバーはフィールドの初期化の場合を除いて省略できません。
// F# type Foo () = let c = 2 member val X = 0 with get, set member this.Increment (value) = this.X <- this.X + c * value
// Nemerle class Foo { private c = 2; public X : int { get; set; } public Increment(value : int) : void { X += c * value; } }
型パラメーターの制約として C# では列挙型やデリゲートを利用することができません。 Nemerle は列挙型やデリゲートを型パラメーターの制約に使えますが,列挙型の内部型を指定したり,デリゲートの関数の型を指定することはできません。 F# は列挙型やデリゲートの型も指定できる上に,明示的なメンバーの制約,等価性の制約,比較の制約, null
制約,アンマネージの制約が利用できます。記法については文法比較「型パラメーターの制約」を参照ください。
C#, Nemerle では既存の型を操作する場合,あらかじめ定義されたメンバー以外では拡張メソッドによりメソッドを追加するように見せかける事ができます。 Nemerle では public
でない拡張メソッドを定義する事はできませんが, C# では可能です。 F# は型拡張によりメソッド以外のメンバーも追加する事が可能です。
// C# public static StringExtensions { public static string Foo(this string s, int value) { // ... } } "foo".Foo(42);
// F# type System.String with member this.Foo (value : int) = // ... member this.Bar = // ... "foo".Foo (42) |> ignore "bar".Bar |> ignore
// Nemerle public module StringExtensions { public static Foo(this s : string, value : int) : string { // ... } } _ = "foo".Foo(42);
C# では列挙型を使った制限された値の列挙が可能ですが,型安全ではありません。 F# や Nemerle ではバリアント[C]により型安全で強力なパターンマッチが利用できます。さらに F# はアクティブパターンが利用できます。 Nemerle にもマクロにより実装されたアクティブパターンがありますが, F# はより簡潔に表現できます。
C# には dynamic
キーワードによる動的型のサポートがあります。ダックタイピングを可能にしたり,他の環境との相互運用を容易にします。もし相互運用において,利用される環境のスキーマが存在するのであれば, F# の型プロバイダーや Nemerle のマクロが問題を静的に解決してくれます。また, Nemerle には C# の dynamic
程便利ではありませんが,遅延バインディングマクロがあり,ダックタイピングのようなことを実現することは可能です。変わり続ける新しい環境であれば C# の dynamic
が,枯れた環境であれば F#
の型プロバイダーや Nemerle
のマクロが便利でしょう。
C# はオブジェクト指向プログラミングスタイル, F# は関数型プログラミングスタイルに向いているでしょう。 Nemerle はいずれのスタイルでも柔軟に切り替える事ができます。
メタプログラミング
.NET Framework のメタプログラミング機能を利用するという点では, C#, F#, Nemerle いずれの言語でもそこまで大きな違いはないでしょう。
F# と Nemerle はコードクォートによって式をそのままオブジェクトとして扱う事ができます。 Nemerle は完全な式だけでなく,関数宣言,パターンマッチのケース,関数のパラメーターのような種々のコード断片もオブジェクトとして扱う事ができます。
// F# let e1 = <@ 1 + 1 @> let e2 = <@@ 1 + 1 @@>
// Nemerle def e = <[ 1 + 1 ]>; def caseOne = <[ case: | 1 => "one" ]>;
Nemerle のマクロはとにかく強力です。コンパイル時に複雑な計算を行ったり,コンパイル時に修正したりできます。しかしマクロはマクロライブラリーとして独立したプロジェクトを作成する必要があるため,小回りがきかないのが難点です。
F# にはコンパイル時の型プロバイダーによる型情報のメタプログラミング機能があります。また, inline
キーワードや Literal
属性による簡易的な最適化が可能です。 inline
は Nemerle のマクロと違いプロジェクト内でも有効です。しかし Literal
属性は完全に定数でなければならず,例えば四則演算のような単純な処理すら許容されません。
C# は const
で定数を定義することができます。関数のインライン展開などの言語サポートはありません。しかしコンパイラーの .NET Framework サポートが厚く,一部の .NET Framework の属性は,コンパイル時に影響を及ぼします。
開発環境
開発環境は C# がダントツにぬきんでています。 Visual Studio はもちろん, MonoDevelop や SharpDevelop といった他の IDE でも第一にサポートされています。また, ReSharper のような優れたサードパーティー製のツールの多くが C# を基本としてサポートしています。他に特筆すべき点として,コンパイル速度が他の言語に比べてかなり速いです。
F# は今でこそ Visual Studio サポートもごくわずかですが, Microsoft 製の言語ということもあり,近い将来ますます便利になっていくことでしょう。
Nemerle は現在 Nemerle プロジェクトで Visual Studio サポートの開発が行われていますが,今後どの程度改良されていくかは未知数です。 ReSharper を開発している JetBrains が Nemerle のコア開発者を起用して何かしらやっているらしいので,もしかするとその副産物して ReSharper の Nemerle サポートが出てくるかもしれません。
文法比較
型パラメーターの制約
制約 | C# | F# | Nemerle |
---|---|---|---|
キーワード | where |
when |
where |
複数の条件 | 複数の where を記述 |
and で列挙 |
複数の where を記述 |
実装・継承 | : T1, T2, ... |
:> T (基底型が複数ある場合は上記の複数の条件にしたがい and で列挙する) |
: T1, T2, ... |
参照型 | : class |
: not struct |
: class |
値型 | : struct |
: struct |
: struct |
null 許容[D] |
: class |
: null |
: class |
列挙型 | なし | : enum<underlying-type> |
: enum |
デリゲート | なし | : delegate<parameter-tuple -> return-type> |
: System.Delegate |
デフォルトコンストラクター | : new() |
: (new : unit -> 'T) |
: new() |
明示的なメンバー | なし | : (type-parameter or ... : (member-signature)) |
なし |
等価性 | なし | : equality |
なし |
比較 | なし | : comparison |
なし |
アンマネージ | なし | : unmanaged |
なし |
脚注
- 生成された IL の
public
宣言をprotected
宣言に書き換えるといった特別な処理をすることもできますが。 [↩] - ただし,変数束縛をしないのであればほとんどのケースで型を明示する必要はありません。 [↩]
- F# の用語では判別共用体。 [↩]
- C# と Nemerle は
null
を許容しない参照型がないので,参照型とnull
許容は同じ。 [↩]