4

次のコードがあるとしましょう。

import Data.List.Ordered

data Person = Person String String
     deriving (Show, Eq)

main :: IO ()
main = print . show . sort $ [(Person "Isaac" "Newton"), (Person "Johannes" "Kepler")]

そして、同じモジュールで、名と姓の両方でリストをソートできるようにしたいと考えています。明らかに私はこれを行うことはできません:

instance Ord Person where
         compare (Person _ xLast) (Person _ yLast) = compare xLast yLast

instance Ord Person where
         compare (Person xFirst _) (Person yFirst _) = compare xFirst yFirst

それで、私のオプションは何ですか?

このページには、「型を newtype でラップし、必要なすべてのインスタンスをその新しい型に持ち上げることでこれを実現できます」と記載されています。誰かがその例を挙げてもらえますか?

より良い方法はありますか?

4

4 に答える 4

18

newtype メソッドは次のようになります。

newtype ByFirstname = ByFirstname { unByFirstname :: Person }

instance Ord ByFirstname where
  -- pattern matching on (ByFirstname (Person xFirst _)) etc
  compare [...] = [...]

newtype ByLastname = ByLastname { unByLastname :: Person }

instance Ord ByLastname where
  -- as above

次に、ソート関数は次のようになります。

sortFirstname = map unByFirstname . sort . map ByFirstname

についても同様ですByLastname


より良い方法は、姓名を取得する関数でsortBycompareおよびを使用することです。onすなわち

sortFirstname = sortBy (compare `on` firstName)

Person(その点については、 のレコード型、つまりを使用する価値があるかもしれませんdata Person = Person { firstName :: String, lastName :: String }。アクセサ関数を無料で取得することさえできます。)

于 2012-08-31T14:49:11.097 に答える
9

Ord異なる順序でソートするためだけに複数のインスタンスを定義する必要はありません。sortBy明示的な比較関数を引数として取る関数を使用するだけです。

巧妙なトリック: レコード型を使用して型を定義する場合Person、 import Data.Function(on関数を提供します) およびData.Monoid、いくつかの巧妙なトリックを使用して、これをより簡潔かつ簡単にすることができます。

import Data.Function (on)
import Data.Monoid (mappend)
import Data.List (sortBy)

data Person = Person { firstName :: String, lastName :: String }

instance Show Person where
    show p = firstName p ++ " " ++ lastName p

exampleData = [ Person "Mary" "Smith"
              , Person "Joe" "Smith"
              , Person "Anuq" "Katig"
              , Person "Estanislao" "Martínez"
              , Person "Barack" "Obama" ]
-- 
-- The "on" function allows you to construct comparison functions out of field
-- accessors:
--
--     compare `on` firstName :: Person -> Person -> Ordering
--     
-- That expression evaluates to something equivalent to this:
--
--    (\x y -> compare (firstName x) (firstName y))
--
sortedByFirstName = sortBy (compare `on` firstName) exampleData
sortedByLastName = sortBy (compare `on` lastName) exampleData

--
-- The "mappend" function allows you to combine comparison functions into a
-- composite one.  In this one, the comparison function sorts first by lastName,
-- then by firstName:
--
sortedByLastNameThenFirstName = sortBy lastNameFirstName exampleData
    where lastNameFirstName = 
              (compare `on` lastName) `mappend` (compare `on` firstName) 
于 2012-08-31T18:01:06.800 に答える
4

ページが言及している理由newtypeは、特定の型に対して同じクラスのインスタンスを 2 つ持つことができないためです。できますが、同じ範囲内ではできません。一般的に、それは本当に悪い考えです。詳細については、 Orphan Instancesを参照してください (CA McCann が指摘したように、これは孤立したインスタンスに関するものではありません。リンクを含めたのは、表面的には機能する場合でも、重複するインスタンスが悪い考えである理由がよく説明されているためです)。

例はnewtype次のとおりです。


newtype SortFirst = SF Person

instance Ord Person where
         compare (Person _ xLast) (Person _ yLast) = compare xLast yLast

instance Ord SortFirst where
         compare (SF (Person xFirst _)) (SF (Person yFirst _)) = compare xFirst yFirst

次に、名前で並べ替えたい場合は、次のようにする必要があります。

sort (map SF persons)

あまり便利ではないのでsortBy、比較関数を引数にとる which を使ったほうがいいかもしれません。

于 2012-08-31T14:45:09.237 に答える
3

のすべての関数には、インスタンスData.List.Orderedを使用するのではなく、比較関数を提供できる「by」バリアントがあるようです。Ord明確な「標準」順序が 1 つも存在しない場合、それは悪い選択肢ではありません。

次に、特定のリストが常に一貫した比較関数で使用されるようにするのはあなたの責任になりますが、これはあらゆる種類の楽しいバグの潜在的な原因です.

ラップとアンラップに煩わされるのではなく、さまざまな順序付けを使用して多くの順序付けられたリストを操作する必要がある場合newtype(これは非常に面倒です)、リストをその比較関数にバンドルするデータ型を検討し、次にプロキシ関数を定義します。関連付けられた比較で「by」関数を使用します。

于 2012-08-31T14:46:13.777 に答える