11

C++ から来た私には、ジェネリック プログラミングが不可欠であることがわかりました。Haskell では、人々はどのようにそれに取り組んでいるのだろうか?

Haskell で汎用スワップ関数を記述する方法を教えてください。

Haskell に部分的な特殊化の同等の概念はありますか?

C++ では、O(1) コンテナー スワップ用の特別な swap メソッドを持つ汎用 map/hash_map コンテナー用の特別な関数を使用して、汎用 swap 関数を部分的に特殊化できます。Haskell でそれを行う方法、または Haskell でのジェネリック プログラミングの標準的な例は何ですか?

4

5 に答える 5

31

これは、Haskell とクイックソートに関する他の質問と密接に関連しています。少なくとも、 Haskell に関する本の紹介を読む必要があると思います。既存の変数の値を変更することを禁止するという重要なポイントをまだ把握していないようです。

スワップ (C++ で理解され、使用されている) は、その性質上、既存の値を変更することがすべてです。これは、名前を使用してコンテナーを参照し、そのコンテナーを完全に異なるコンテンツに置き換え、その操作を特定のコンテナーに対して高速 (かつ例外なし) に特化して、変更して公開するアプローチを実装できるようにするためです。 (例外セーフなコードを書いたり、ロックフリーのコードを書こうとする場合に重要です)。

Haskell でジェネリック スワップを記述できますが、おそらく値のペアを取り、位置を逆にした同じ値を含む新しいペアを返すか、またはそのようなものです。実際には同じものではなく、同じ用途もありません。Haskell ではそのようなことを行うことは許可されていないため、そのマップの内部を掘り下げて個々のメンバー変数を交換することにより、マップに特化しようとしても意味がありません (特殊化はできますが、そうではありません)。変数の変更)。

Haskell でリストを「測定」したいとします。

measure :: [a] -> Integer

それが型宣言です。これは、関数measureがすべてのリストを取り (aは小文字で始まるため、ジェネリック型パラメーターです)、Integer を返すことを意味します。したがって、これは任意の要素型のリストに対して機能します。これは、C++ では関数テンプレート、または Haskell では多態性関数と呼ばれるものです (C++ の多態性クラスとは異なります)。

興味深いケースごとに特殊化を提供することで、それを定義できるようになりました。

measure [] = 0

つまり、空のリストを測定するとゼロになります。

他のすべてのケースをカバーする非常に一般的な定義を次に示します。

measure (h:r) = 1 + measure r

LHS の括弧内のビットはパターンです。それは次のことを意味します:リストを取り、頭を壊してそれを h と呼び、残りの部分を r と呼びます。これらの名前は、使用できるパラメーターです。これは、少なくとも 1 つのアイテムを含むすべてのリストに一致します。

C++ でテンプレート メタプログラミングを試したことがある場合、まったく同じスタイル (ループを実行するための再帰、再帰を終了させるための特殊化) が含まれているため、これはすべて古い帽子です。Haskell では実行時に機能することを除いて (特定の値または値のパターンに対する関数の特殊化)。

于 2008-12-18T08:01:27.857 に答える
9

Earwicker が言うように、この例は Haskell ではあまり意味がありません。とにかく絶対にそれが必要な場合は、インタラクティブなセッションからの c&p と同様のもの (ペアの 2 つの部分を交換) を次に示します。

GHCi, version 6.8.2: http://www.haskell.org/ghc/  :? for help
Loading package base ... linking ... done.
Prelude> let swap (a,b) = (b,a)
Prelude> swap("hello", "world")
("world","hello")
Prelude> swap(1,2)
(2,1)
Prelude> swap("hello",2)
(2,"hello")
于 2008-12-18T08:28:06.870 に答える
6

Haskell では、関数は可能な限りジェネリック (ポリモーフィック) です。コンパイラは「最も一般的な型」を推論します。たとえば、TheMarko の例の swap は、型シグネチャがない場合、デフォルトでポリモーフィックです。

*Main> let swap (a,b) = (b,a)
*Main> :t swap
swap :: (t, t1) -> (t1, t)

部分的な特殊化に関しては、ghc には 98 以外の拡張子があります:
file:///C:/ghc/ghc-6.10.1/doc/users_guide/pragmas.html#specialize-pragma

また、用語の不一致があることに注意してください。C++、Java、および C# でジェネリックと呼ばれるものは、Haskell ではポリモーフィックと呼ばれます。Haskell の「ジェネリック」は、通常
多型を意味します。

于 2008-12-19T23:21:24.097 に答える
5

Haskell では、型クラスを作成します。型クラスはオブジェクト指向言語のクラスとは異なります。Numeric 型クラスを取り上げます。クラスのインスタンスであるすべてのものが特定の操作を実行できると言われているので (+ - * /)、Integer は Numeric のメンバーであり、Numeric と見なされるために必要な関数の実装を提供し、任意の場所で使用できます。数値が必要です。

Int と String を foo できるようにしたいとします。次に、Int と String を型クラス Foo のインスタンスとして宣言します。これで、タイプ (Foo a) が表示される場所ならどこでも、Int または String を使用できるようになりました。

ints と floats を直接追加できない理由は、add の型が (Numeric a) a -> a -> aa であり、通常の変数と同じように 1 回しかバインドできないため、バインドするとすぐにバインドできるためです。 it to Int リスト内のすべての a は Int でなければなりません。

于 2009-09-01T16:35:13.340 に答える
2

Earwicker の答えを本当に理解するために Haskell の本を十分に読んだら、型クラスについても読むことをお勧めします。「部分的な専門化」が何を意味するのかはわかりませんが、近づく可能性があるように思えます。

于 2008-12-19T22:38:33.510 に答える