このスイッチ/パターンマッチングのアイデアには何か利点がありますか? 質問する

このスイッチ/パターンマッチングのアイデアには何か利点がありますか? 質問する

最近、F# を調べていますが、すぐには移行するつもりはありませんが、C# (またはライブラリ サポート) によって作業が楽になる可能性のある領域がいくつかあることは確かです。

特に、私は F# のパターン マッチング機能について考えています。これは非常に豊富な構文を可能にし、現在の C# のスイッチ/条件付き構文よりもはるかに表現力に富んでいます。直接的な例を挙げるつもりはありませんが (私の F# はそれには適していません)、簡単に言うと、次のことが可能になります。

  • 型による一致(識別された共用体の完全なカバレッジチェック付き)[これにより、バインドされた変数の型も推測され、メンバーアクセスなどが付与されることに注意してください]
  • 述語による一致
  • 上記の組み合わせ(そしておそらく私が知らない他のシナリオも)

C# が最終的にこの豊かさの一部を借用できれば素晴らしいのですが、その間、実行時に何ができるかを検討してきました。たとえば、いくつかのオブジェクトを組み合わせて次のことを実現するのはかなり簡単です。

var getRentPrice = new Switch<Vehicle, int>()
        .Case<Motorcycle>(bike => 100 + bike.Cylinders * 10) // "bike" here is typed as Motorcycle
        .Case<Bicycle>(30) // returns a constant
        .Case<Car>(car => car.EngineType == EngineType.Diesel, car => 220 + car.Doors * 20)
        .Case<Car>(car => car.EngineType == EngineType.Gasoline, car => 200 + car.Doors * 20)
        .ElseThrow(); // or could use a Default(...) terminator

ここで、getRentPrice は Func<Vehicle,int> です。

[注記 - ここでの Switch/Case は間違った用語かもしれませんが、アイデアは示しています]

私にとっては、これはif/elseを繰り返したり、複合三項条件(これは、重要な式では括弧が多用されるため、非常に面倒になります)を使用するよりもずっと明確です。また、多くキャストの、そしてより具体的な一致への簡単な拡張 (直接または拡張メソッド経由) を可能にします。たとえば、VB Select...Case "x To y" の使用に匹敵する InRange(...) 一致などです。

私は、(言語サポートがない場合でも)上記のような構造から多くの利益が得られると人々が考えているかどうかを判断しようとしているだけです。

さらに、私は上記の 3 つのバリエーションを試してきたことに注意してください。

  • 評価用の Func<TSource,TValue> バージョン - 複合三項条件文に相当します
  • Action<TSource> バージョン - if/else if/else if/else if/else に相当
  • Expression<Func<TSource,TValue>> バージョン - 最初のバージョンと同じですが、任意の LINQ プロバイダーで使用できます。

さらに、式ベースのバージョンを使用すると、式ツリーの書き換えが可能になり、繰り返し呼び出しを使用するのではなく、基本的にすべてのブランチを単一の複合条件式にインライン化できます。最近は確認していませんが、初期の Entity Framework ビルドでは、InvocationExpression があまり好まれなかったため、これが必要だったように思います。また、デリゲートの繰り返し呼び出しを回避するため、LINQ-to-Objects をより効率的に使用することもできます。テストでは、上記のような一致 (式形式を使用) は、同等の C# 複合条件ステートメントと比較して、同じ速度 (実際にはわずかに高速) で実行されています。完全を期すために、Func<...> ベースのバージョンは C# 条件ステートメントの 4 倍の時間がかかりましたが、それでも非常に高速であり、ほとんどのユースケースで大きなボトルネックになる可能性は低いです。

上記についてのご意見、ご感想、ご批判などを歓迎します (または、より豊富な C# 言語サポートの可能性について... 期待しています ;-p)。

ベストアンサー1

C# でそのような「機能的な」ことを実行しようとした後 (さらに、それに関する本を書こうと試みた後)、いくつかの例外を除いて、そのようなことはあまり役に立たないという結論に達しました。

主な理由は、F# などの言語がこれらの機能を真にサポートすることで大きな力を発揮するからです。「できる」のではなく、「シンプルで、明確で、期待どおり」です。

たとえば、パターン マッチングでは、不完全な一致がある場合や、別の一致が見つからない場合、コンパイラが通知します。これは、オープン エンド型ではあまり役に立ちませんが、判別されたユニオンまたはタプルをマッチングする場合には非常に便利です。F# では、パターン マッチングが期待されるため、すぐに理解できます。

「問題」は、関数の概念を使い始めると、そのまま使い続けたくなることです。しかし、C#でタプル、関数、部分的なメソッド適用とカリー化、パターンマッチング、ネストされた関数、ジェネリック、モナドサポートなどを活用すると、とてもすぐに醜くなります。楽しいですし、とても賢い人たちがC#でとてもクールなことをやっていますが、実際は使用して重く感じます。

C# で私が頻繁に (プロジェクト間で) 使用するようになったもの:

  • IEnumerable の拡張メソッドを介したシーケンス関数。ForEach や Process (「適用」? - 列挙されたシーケンス項目に対してアクションを実行する) などは、C# 構文で適切にサポートされているため、適しています。
  • 一般的なステートメント パターンを抽象化します。複雑な try/catch/finally ブロックまたはその他の複雑な (多くの場合、非常に汎用的な) コード ブロック。LINQ-to-SQL の拡張もここに当てはまります。
  • ある程度はタプル。

** ただし、自動一般化と型推論がないため、これらの機能の使用さえも妨げられることに注意してください。**

とはいえ、他の誰かが言ったように、小規模なチームで特定の目的のために C# で行き詰まっている場合、確かにそれらは役立つかもしれません。しかし、私の経験では、それらは通常、価値よりも面倒だと感じました - YMMV。

その他のリンク:

おすすめ記事