2016-08-01 67 views
3

創建奧德情況下,給定的數據類型,如與NEWTYPE包裝

data Foo = Bar | Baz | Qux 

,我希望有多個不同的排序爲這種類型,是下列最常見/標準來實現這種方式?

newtype FooPriorityA = FooPriorityA { unFooPriorityA :: Foo } 

instance Ord FooPriorityA where 
    compare (FooPriorityA x) (FooPriorityA y) = f x `compare` f y 
     where f :: Foo -> Int 
       f Baz = 1 
       f Bar = 2 
       f Qux = 3 

newtype FooPriorityB = FooPriorityB ... and so on 

委託給Int的Ord實例就像那樣瘋狂?與寫出compare的n^2比較,感覺更安全,而且工作量更少。

我可能忽略了這個「黑客」的任何明顯的缺陷?它甚至是「黑客」?

回答

5

你只需要O(ñ)(2 ​ ñ - 1,要準確)定義來指定一個總訂單,只要你按照優先順序增加指定情況:

instance Ord FooPriorityA where 
    compare (FooPriorityA x) (FooPriorityA y) | x == y = EQ -- Use the Eq instance 
               -- Bar is the smallest 
               | x == Bar = LT 
               | y == Bar = GT 
               -- Baz is the next smallest 
               | x == Baz = LT 
               | y == Baz = GT 
               -- Proceed in the desired order. 
               -- You don't need to say anything 
               -- explicit about the largest value 

第一種情況(x == y)涵蓋了O(n)種可能性。接下來的每個案例可能看起來都不完整,但它帶有暗示的信息,即前面的每個案例都是錯誤的。例如,x == Bar = LT並不意味着其中x == Bar評估爲LT;已經處理了x == Bar && y == Bar的情況,所以第二種情況實際上是暗含x == Bar && y /= Bar。同樣,案例4(x == Baz)假定y /= Baz(由案例1的失敗暗示匹配)和y /= Bar(暗示情況3的匹配失敗)。因此,y的任何剩餘可能值實際上大於Baz

你走下的列表越多,未處理的案例就越少。到最後,你不需要對最大的項目做任何明確的說明;所有關於它如何與其他對比的信息n - 上述情況已經捕獲了1項。


另外,請記住,你的f定義爲最小執行Enum類,然後你可以用它來定義Ord實例的實例中的一半(fromEnum)。

instance Enum FooPriorityA where 
    fromEnum Bar = 1 
    fromEnum Baz = 2 
    fromEnum Qux = 3 
    toEnum 1 = Bar 
    toEnum 2 = Baz 
    toEnum 3 = Qux 

instance Ord FooPriorityA where 
    compare x y = compare (fromEnum x) (fromEnum y) 
    -- compare = compare `on` Data.Function.fromEnum 
+0

這是一些很棒的信息,謝謝。關於我的'f'和'Enum'類型之間關係的觀察很有意思。 – SimonF

7

這是絕對合理的。事實上,你可以使用the comparing function from Data.Ord使這更是直接:

instance Ord FooPriorityA where 
    compare (FooPriorityA x) (FooPriorityA y) = comparing f x y 
    where f :: Foo -> Int 
      f Baz = 1 
      f Bar = 2 
      f Qux = 3 

雖然根據自己的喜好,與compare版本可能會根據自己的喜好。

0

如何做一個函數來生成映射從Foo基於表示要訂購名單上Int

import Data.List 

data Foo = Bar | Baz | Qux deriving Eq 

newtype FooPriorityA = FooPriorityA { unFooPriorityA :: Foo } deriving Eq 

fooOrdering :: [Foo] -> Foo -> Foo -> Ordering 
fooOrdering orderedList a b = f a `compare` f b 
    where f x = elemIndex x orderedList 

instance Ord FooPriorityA where 
    compare (FooPriorityA x) (FooPriorityA y) = fooOrdering [Baz, Bar, Qux] x y 

你甚至可以添加斷言,以確保所有元素出現一次在列表中。

+0

我想我更喜歡的模式匹配內部的情況,主要是因爲當你的'fooOrdering'節省一些字符,這使得它可以提供一些相當無意義的值,例如,[酒吧,酒吧,酒吧,酒吧]。而模式匹配會給出這種事情的警告。我想你可以完全填補Ints。 – SimonF

+0

@SimonF:我想你會通過添加一個斷言來防止非感性的值,以確保所有的元素只出現在列表中一次。但是你是對的,編譯器已經可以做到模式匹配。 – Thilo

+0

我不喜歡我的解決方案的另一件事是它必須在運行時爲每次比較迭代列表。在編譯時構建一個靜態映射(或者至少在首次使用後記憶)會很好。 – Thilo