2012-05-27 72 views
5

因此,我正在研究一個有趣的實驗,並且我正在碰壁。我試圖定義一個數據類型,它既可以是一個基元,也可以是一個從一個構造函數轉換到另一個構造函數的函數。在簽名中約束構造函數

data WeaponPart = 
    WInt Int | 
    WHash (Map.Map String Int) | 
    WNull | 
    WTrans (WeaponPart -> WeaponPart) 

instance Show WeaponPart where 
    show (WInt x) = "WInt " ++ (show x) 
    show (WHash x) = "WHash " ++ (show x) 
    show (WTrans _) = "WTrans" 
    show WNull = "WNull" 

cold :: WeaponPart -> WeaponPart 
cold (WInt x) = WHash (Map.singleton "frost" x) 
cold (WHash x) = WHash $ Map.insertWith (+) "frost" 5 x 
cold (WTrans x) = cold $ x (WInt 5) 
cold (WNull) = cold $ (WInt 5) 

ofTheAbyss :: WeaponPart -> WeaponPart 
ofTheAbyss (WTrans x) = x (WTrans x) 

的問題是,ofTheAbyss簽名允許任何WeaponPart作爲參數,而我只想讓WTrans-constructred參數。你可以看到我只爲這種情況寫了一個模式匹配。

我試着用GADT做,但我擔心它是一個兔子洞。永遠無法讓他們做我想做的事。有沒有人有任何想法,我怎麼才能強制WTrans的論據進入TheAbyss?還是我完全錯過了一些東西。

謝謝。

貝斯特,埃裏克

回答

10

可以做這樣的事情與GADTs。從我來判斷,究竟兔子洞是否有什麼結果,但至少讓我看看配方。我正在使用新的PolyKinds擴展名,但您可以使用更少的管理。

首先,決定你需要什麼類型的東西,並定義這些類型的數據類型。

data Sort = Base | Compound 

接下來,定義您的數據按其排序進行索引。這就像建立一個小型的語言。

data WeaponPart :: Sort -> * where 
    WInt :: Int ->         WeaponPart Base 
    WHash :: Map.Map String Int ->     WeaponPart Base 
    WNull ::           WeaponPart Base 
    WTrans :: (Some WeaponPart -> Some WeaponPart) -> WeaponPart Compound 

可以代表通過存在量詞任何形式’的‘數據,如下所示:

data Some p where 
    Wit :: p x -> Some p 

注意,x不逃避,但我們仍然可以檢查‘證據’是x ‘滿足’ p。請注意,Some必須是data類型,而不是newtype,因爲GHC反對存在newtype s。

您現在可以自由撰寫Sort-通用操作。如果您有通用輸入,則可以使用多態性,有效地將Some p -> ...修飾爲forall x. p x -> ...

instance Show (WeaponPart x) where 
    show (WInt x) = "WInt " ++ (show x) 
    show (WHash x) = "WHash " ++ (show x) 
    show (WTrans _) = "WTrans" 
    show WNull  = "WNull" 

需要用於Sort -generic輸出的存在:在這裏,我將它用於輸入和輸出。

cold :: Some WeaponPart -> Some WeaponPart 
cold (Wit (WInt x)) = Wit (WHash (Map.singleton "frost" x)) 
cold (Wit (WHash x)) = Wit (WHash $ Map.insertWith (+) "frost" 5 x) 
cold (Wit (WTrans x)) = cold $ x (Wit (WInt 5)) 
cold (Wit WNull)  = cold $ Wit (WInt 5) 

我不得不添加的Wit有關地方偶爾的觸碰,但它是相同的程序。

同時,我們現在可以編寫

ofTheAbyss :: WeaponPart Compound -> Some WeaponPart 
ofTheAbyss (WTrans x) = x (Wit (WTrans x)) 

所以這並不可怕與嵌入式系統的工作。有時會產生成本:如果您希望您的嵌入式語言具有子分類,您可能會發現您只是爲了更改某些數據類型的索引而執行額外的計算,而不會影響數據本身。如果你不需要排序,額外的紀律往往可以成爲真正的朋友。

+0

這是一個奇妙的答案。我的意思是說我使用GADT的方式是一個兔子洞,但顯然有人明智地可以理解它。而我的光芒,有人呢!精彩。我永遠不會得到如何擁有它,即有武器部分化合物和一些武器部分。此外,這是嵌入式系統節省時間的一個很好的例子。 –

+0

最後一件事,我得到了它的工作,這對我來說是非常激動人心的。我唯一需要改變的是我不得不取出PolyKinds部分並使'data Sort = Base |化合物「轉換爲」數據庫「和」數據化合物「。它一直抱怨說,Base不是一個類型構造函數。有沒有什麼我與PolyKinds失蹤?最有可能的。 –

+2

啊,我會爲其他人留下我的錯誤:我需要DataKinds編譯指示,而不是PolyKinds編譯指示。所有這些奇特的新擴展。 –

1

你試圖通過類型沒有限制你的功能,而是通過構造函數。這不是一個可行的事情。事實上,它不應該是一個可行的事情 - 如果你正在編寫另一個函數,並且你有一些未知的WeaponPart,你必須能夠將它傳遞給ofTheAbyss或者 - 不是必須檢查或不。

我能想到的兩個選項是:

a)給出ofTheAbyss(WeaponPart -> WeaponPart) -> WeaponPart, 「拆包」 的構造。

b)ofTheAbyss在任何其他構造函數上給出運行時錯誤。

ofTheAbyss :: WeaponPart -> WeaponPart 
ofTheAbyss (WTrans x) = x (WTrans x) 
ofTheAbyss _ = error "Illegal argument to ofTheAbyss was not a WTrans" 
3

這是另一種可能的解決方案:將數據類型分成兩部分。我使用與其他答案一致的名稱,以便輕鬆查看相似內容。

data WeaponPartBase 
    = WInt Int 
    | WHash (Map.Map String Int) 
    | WNull 

data WeaponPartCompound = WTrans (WeaponPart -> WeaponPart) 
data WeaponPart = Base WeaponPartBase | Compound WeaponPartCompound 

cold :: WeaponPart -> WeaponPart 
cold (Base (WInt x)) = Base (WHash (Map.singleton "frost" x)) 
cold (Base (WHash x)) = Base (WHash $ Map.insertWith (+) "frost" 5 x) 
cold (Base WNull) = cold (Base (WInt 5)) 
cold (Compound (WTrans x)) = cold (x (Base (WInt 5)) 

ofTheAbyss :: WeaponPartCompound -> WeaponPart 
ofTheAbyss (WTrans x) = x (WCompound (WTrans x)) 

這可以稍微更方便通過聲明爲基本的東西一類進行:

class Basic a where 
    wint :: Int -> a 
    whash :: Map.Map String Int -> a 
    wnull :: a 

class Compounded a where 
    wtrans :: (WeaponPart -> WeaponPart) -> a 

instance Basic WeaponPartBase where 
    wint = WInt 
    whash = WHash 
    wnull = WNull 

instance Basic WeaponPart where 
    wint = Base . wint 
    whash = Base . whash 
    wnull = Base wnull 

instance Compounded WeaponPartCompound where 
    wtrans = WTrans 

instance Compounded WeaponPartCompound where 
    wtrans = Compound . wtrans 

,使得例如coldofTheAbyss可能看起來像這樣:

cold' (Base (WInt x)) = whash (Map.singleton "frost" x) 
cold' (Base (WHash x)) = whash $ Map.insertWith (+) "frost" 5 x 
cold' (Base WNull) = cold' (wint 5) 
cold' (Compound (WTrans x)) = cold' (x (wint 5)) 

ofTheAbyss' (WTrans x) = x (wtrans x) 
+0

這也是一個很好的解決方案。令人興奮的是,有多少種不同的方式可以讓貓變得像皮膚一樣光滑。我想知道與GADT相比,這樣做的優缺點。 –

+0

@ErikHinton主要的優點是它是H2010,所以它有機會在其他編譯器上工作。主要缺點是它需要額外的樣板文件:模式匹配比較麻煩,我計算了幾乎25行的純絨毛,定義並實現了'Basic'和'Compounded'類。 –