はい、問題は、Haskellが任意の要素を比較して等しいかどうかを比較できないことです。つまり、型クラスの一部である型しか比較できませEq
ん。これは理にかなっています。関数などの特定のものを比較することは決定できません。他の言語には「参照の平等」の概念がありますが、これはHaskellでは意味がありません。したがって、基本的に値が等しいかどうかを比較できないタイプがあります。2つの値が等しいかどうかを比較する方法がない限り、何かがすでにリストにあるかどうかを確認することはできませんEq
。
Eq
これは、セット(またはマルチセット)の実装が(または他の明示的な比較関数)に依存することを意味します。実際には、セットはパフォーマンス上の理由からも依存する傾向がありOrd
ますが、リストを使用しているだけなので、心配する必要はありません。これはまた、マルチセットをファンクターまたはモナドにすることはできませんが、c'estlavieにすることはできないことを意味します。
つまり、タイプをに制限する必要がありますEq
。したがって、に変更a -> Bag a -> Bag g
しEq a => a -> Bag a -> Bag a
ます。
あなたは言語を学ぶためにいくつかの練習をしているように見えるので(これが見下すようなものにならないことを願っています)、2番目の質問のヒントをいくつか示します。
再帰的に考えます。まず、基本的なケースを考えてみましょう。空のマルチセットに要素を追加するにはどうすればよいですか?別の基本ケース:リストの先頭に要素を含むマルチセットがある場合、新しい増分マルチセットをどのように作成しますか?最後に、再帰的な場合:ヘッドがインクリメントしたい要素ではないリストがある場合はどうしますか?これらすべての質問に答えたら、リストのパターンマッチングによってそれぞれを単一のケースとして記述し、それらを組み合わせてadd
関数を取得できます。
もう1つの注意:コンストラクターを持つことEmptyBag
は冗長です。リストはすでに空にすることができます!とどうListBag []
違うのEmptyBag
?この場合、コンストラクターは1つだけです。
したがって、add関数は次のようになります。
add :: Eq a => a -> Bag a -> Bag a
add x (ListBag []) = ...
add x (ListBag [(x', n)]) = ...
適切なケースを入力するだけで、...
準備は完了です。
あなたのコメントによると、これはあなたがそれを繰り返している間リストを保持する方法についてのいくつかのサンプルコードです。
基本的に、主な考え方は単純です。再帰的な場合、関数に渡されたリストの残りの部分を返すのではなく、現在の要素の後にリストの残りの部分を返します。基本ケースはまだ単純なままです:
add :: Eq a => a -> Bag a -> Bag a
add x (ListBag []) = ListBag [(x, 1)] -- first base case
add x (ListBag (x', n):xs)
| x == x' = ListBag $ (x', n + 1) : xs -- second base case
| otherwise = let ListBag rest = add x (ListBag xs) in ListBag $ (x', n) : rest
let
ステートメントを使用してリストListBag
を削除し、タッチしなかったタプルをリストの前に戻す必要があります。
このように再帰を考えるとき、私はそれを一連のステップのように考えるのではなく、それぞれのケースを別々に考えることを好みます。いずれの場合も、与えられたもの全体 を返却したいと思います。ListBag
したがって、作業中のタプルをリストの残りの部分と比較する必要があります。再帰的な場合、リストの残りの部分は再帰的な呼び出しから取得します。2番目の基本ケースでは、関数を再度呼び出す必要はありません。
したがって、各ステップでバッグ全体を返すことにより、すべての再帰の最後にリスト全体を維持します。
これがより明確になることを願っています。