2014-02-08 40 views
2

我似乎已經陷入了這裏一種有趣的語言邊緣情況。這是棘手解釋什麼,我試圖做的,所以讓我把它寫在代碼來代替:Coerce幻影類型

data Foobar x = 
    Foo1 {field1 :: x, field2 :: String} | 
    Foo2 {field1 :: x, field3 :: Int} | 
    Foo3 {    field4 :: Bool} | 
    Foo4 {    field2 :: String, field4 :: Bool} 

正如你所看到的,一些構造取決於x,但別人不一樣。我想類似的功能寫入fmap

transform :: (x -> y) -> Foobar x -> Foobar y 
transform fn foobar = 
    case foobar of 
    Foo1 {} -> foobar {field1 = fn (field1 foobar)} 
    Foo2 {} -> foobar {field1 = fn (field1 foobar)} 
    _  -> foobar 

正如你所看到的,記錄語法巧妙地讓我避免重建整個構造,應用fn只在需要的地方。不幸的是,當fn需要在的地方。在這種情況下(即最後的選擇),該表達式不能進行類型檢查。我很清楚爲什麼它失敗 - 但我很困惑,至於如何修復這個。

很顯然我可以長時間寫出整件事情。這對於這個簡化的例子來說很有效,但是我想寫的真正的應用程序要大得多。 (大約25名建設者,其中一些具有15個以上的領域)。

有沒有人有任何關於如何解決這個問題的簡潔想法?

+0

你可以添加'派生Functor'並讓編譯器爲你編寫所有的25個案例嗎? –

+0

@DanielWagner真正的類型有多個類型參數 - 我打算爲每個參數寫一個'transform'函數。我不知道有什麼辦法可以推導出來。 – MathematicalOrchid

+0

[這個問題](http://stackoverflow.com/q/4069840/485115)可能是你感興趣的。 –

回答

4

一解決方案(以節省打字)是使用記錄通配符:

{-# LANGUAGE RecordWildCards #-} 
-- ... 
case foobar of 
    Foo4 {..} -> Foo4 {..} 
+0

Ooo,這很有趣...似乎是編譯指示也會打開一些我不想要的東西,但如果我只是將它放在定義轉換函數的模塊中,它應該是安全的。 – MathematicalOrchid

2

如果您不幸手動執行此操作,則必須進行模式匹配。如果你想避免這麼多的工作,一個簡單的解決方法是

{-# LANGUAGE DeriveFunctor #-} 
    data Foo a = ... 
    deriving(Functor) 

現在我們可以寫的unsafeCoerce

coerceFoo :: Foo a -> Foo b 
    coerceFoo = fmap (error "This shouldn't be used on a phantom type") 

一個更安全的形式,並且在你的榜樣使用這個

_  -> coerceFoo foobar 
+0

使用'newtype'來翻轉參數順序...多麼巧妙的想法。我想知道這是否真的有效?我會試試看... – MathematicalOrchid

+0

@MathematicalOrchid Drat它不,因爲我們沒有辦法在Foo中變換'b' ..沒關係 – jozefg

+0

Aww。 :-(哦... – MathematicalOrchid