2010-04-13 112 views
13

讓我們考慮一個數據類型有許多構造函數:「模式匹配」代數數據類型的構造函數

data T = Alpha Int | Beta Int | Gamma Int Int | Delta Int 

我想編寫一個函數來檢查是否有相同的構造產生了兩個值:

sameK (Alpha _) (Alpha _) = True 
sameK (Beta _) (Beta _) = True 
sameK (Gamma _ _) (Gamma _ _) = True 
sameK _ _ = False 

維護sameK沒有多少樂趣,也不容易檢查正確性。例如,當新構造函數被添加到T時,很容易忘記更新sameK。我省略了一行舉例:

-- it’s easy to forget: 
-- sameK (Delta _) (Delta _) = True 

問題是如何避免樣板sameK?或者如何確保它檢查所有T構造函數?


我發現的解決方法是使用單獨的數據類型的每個構造的,推導Data.Typeable,並聲明一個普通型類,但我不喜歡這樣的解決方案,因爲它是非常少可讀和否則只是一個簡單的代數類型的工作對我來說:

{-# LANGUAGE DeriveDataTypeable #-} 

import Data.Typeable 

class Tlike t where 
    value :: t -> t 
    value = id 

data Alpha = Alpha Int deriving Typeable 
data Beta = Beta Int deriving Typeable 
data Gamma = Gamma Int Int deriving Typeable 
data Delta = Delta Int deriving Typeable 

instance Tlike Alpha 
instance Tlike Beta 
instance Tlike Gamma 
instance Tlike Delta 

sameK :: (Tlike t, Typeable t, Tlike t', Typeable t') => t -> t' -> Bool 
sameK a b = typeOf a == typeOf b 
+0

謝謝大家回答。你所有的答案都很有用。 – sastanin 2010-04-14 08:59:03

回答

10

看看Data.Data模塊,特別是toConstr函數。隨着{-# LANGUAGE DeriveDataTypeable #-},這將爲您提供一個1線解決方案,適用於任何類型的實例Data.Data。你不需要弄清楚所有的SYB!

如果由於某種原因(與Hugs卡住?),這不是一個選項,那麼這是一個非常醜陋,非常緩慢的黑客攻擊。它只適用於您的數據類型爲Show(例如,通過使用deriving (Show)--例如內部沒有函數類型)。

constrT :: T -> String 
constrT = head . words . show 
sameK x y = constrT x == constrT y 

constrT通過顯示它,切碎它分成字,然後讓所述第一獲取一個T值的最外構造的字符串表示。我給出了一個明確的類型簽名,所以你不想在其他類型上使用它(並避免單態限制)。

一些顯着的缺點:

  • 這可怕的破壞,當你的類型有綴構造函數(如data T2 = Eta Int | T2 :^: T2
  • 如果一些構造都有一個共同的前綴,這會變慢,因爲必須比較大部分的字符串。
  • 不適用於具有自定義show的類型,例如許多庫類型。

這就是說,它哈斯克爾98 ......但這就是唯一的好處,我可以說一下吧!

+1

+1 for'toConstr' – 2010-04-13 14:46:09

+1

哇!這只是魔術。非常感謝! – sastanin 2010-04-13 16:33:39

14

另一種可能的方式:

sameK x y = f x == f y 
    where f (Alpha _) = 0 
     f (Beta _) = 1 
     f (Gamma _ _) = 2 
     -- runtime error when Delta value encountered 

運行時錯誤是不理想,但比默默地給錯誤的答案更好。

+3

我喜歡它。帶有'-Wall'的GHC會在'f'的定義中報告不明確的模式匹配,因此可以避免運行時錯誤。謝謝你的想法。 – sastanin 2010-04-13 11:21:17

10

您需要使用一個泛型庫,如Scrap Your Boilerplate或uniplate來完成此操作。

如果你不想這麼重的手,你可以使用戴夫韓丁的解決方案,連同空記錄的快捷方式:

... 
where f (Alpha {}) = 0 
     f (Beta {}) = 1 
     f (Gamma {}) = 2 

所以你不必知道每個多少ARGS構造函數有。但它顯然還有一些不足之處。

+2

使用'{}'的好方法。我不知道這件事。謝謝。 – sastanin 2010-04-13 11:22:15

+2

記錄大括號的綁定比其他任何事都強,所以在技術上你不需要括號。 'f Alpha {} = 0'會正常工作,雖然我不確定它有多可讀,它看起來像'f'有兩個參數。我有時涉及'f Alpha {} = 0' ... – 2010-04-14 08:12:02

1

您絕對可以使用泛型來消除樣板。你的代碼是一本教科書的例子,爲什麼我(和許多其他從不使用頂級通配符_)。儘管寫出所有這些情況是件繁瑣的事情,但與處理這些錯誤相比,這並不麻煩。

在這個快樂的例子中,我不僅會使用Dave Hinton的解決方案,還會在輔助函數f上打一個INLINE雜注。

+0

是的,這就是我問的原因。謝謝你的建議。 – sastanin 2010-04-14 08:58:09

相關問題