2014-10-28 89 views
4

讓我們有以下的數據類型:如何爲幻像類型創建返回幻像類型的實例?

data Foo1 a = Foo1 
data Foo2 a = Foo2 (Foo3 a) 
data Foo3 a = C1 (Foo1 a) | C2 Int 

而現在我們希望能夠從Foo1或int得到Foo3。 一個解決方案是使用類型類:

class ToFoo3 a where 
    toFoo3 :: a -> Foo3 b -- Here start the problems with this phantom type b... 

instance ToFoo3 (Foo1 b) where 
    toFoo3 foo1 = C1 foo1 

instance ToFoo3 Int where 
    toFoo3 int = C2 int 

在這裏,編譯器會抱怨它不能匹配B與B1,因爲在類定義Foo3的「B」是不一樣的(正確!)例如Foo1中的一個。

有沒有辦法解決這個問題?

+0

我試圖用多參數型類和函數依賴來解決它。但是,我遇到了Int實例,因爲它沒有幻像類型,因此未定義類型的第二個參數。 – leo 2014-10-28 06:25:54

回答

0

多次嘗試後時失敗,我終於拿出一個滿意的答覆! 訣竅是將函數依賴關係與Int類型的類型同義詞一起使用。

{-# LANGUAGE FlexibleContexts  #-} 
{-# LANGUAGE FlexibleInstances  #-} 
{-# LANGUAGE FunctionalDependencies #-} 
{-# LANGUAGE MultiParamTypeClasses #-} 

data Foo1 a = Foo1 
data Foo2 a = Foo2 (Foo3 a) 
data Foo3 a = C1 (Foo1 a) | C2 (PhInt a) 
data Foo4 = Foo4 
type PhInt a = Int -- We use now a PhInt type instead of a type. 

class ToFoo3 a b | a -> b where 
    toFoo3 :: a -> b 

instance ToFoo3 (Foo1 a) (Foo3 a) where 
    toFoo3 foo1 = C1 foo1 

-- The PhInt type allows us to specify that Foo3 must be generic as is 
-- PhInt a. 
instance ToFoo3 (PhInt a) (Foo3 a) where 
    toFoo3 int = C2 int 

test1 = toFoo3 Foo1 
test2 = toFoo3 (3::Int) 
test3 = toFoo3 (Foo1 :: Foo1 Foo4) 

{- 
This trick allows us to write a function which can take benefit of the 
type class. The important point is that if you would try to do this 
without having the "PhInt a" type instead of "Int", when using an integer 
you would get as final result a value of type Foo3 Int. 
-} 
coerce :: ToFoo3 a (Foo3 b) => a -> (Foo3 b, String) 
coerce a = (toFoo3 a, "hello") 

注意:所有這些複雜因素都在這裏,因爲實例必須將「非幻像」類型的Int轉換爲幻像類型。如果我們處理的只是幻象類型,我們可以做一些簡單得多,例如:

class ToFoo3 a (Foo3 b) where 
    toFoo3 :: a b -> Foo3 b 

instance ToFoo3 Foo1 Foo3 where 
... 
+0

這絕對有效,但我完全沒有線索,如果這是偶然的,或者如果有一些很好的理由。所以,如果你有任何光線穿上這個,歡迎! – leo 2015-01-12 18:19:54

3

我不是100%肯定,如果這是你想要的, 但你可以讓編譯器接受像你使用type families試圖 什麼:

{-# LANGUAGE TypeFamilies #-} 

module Stackoverflow where 

data Foo1 a = Foo1 
data Foo2 a = Foo2 (Foo3 a) 
data Foo3 a = C1 (Foo1 a) | C2 Int 

class ToFoo3 a where 
    type T a :: * 
    toFoo3 :: a -> Foo3 (T a) 

instance ToFoo3 (Foo1 b) where 
    type T (Foo1 b) = b 
    toFoo3 foo1 = C1 foo1 

instance ToFoo3 Int where 
    type T Int = Int 
    toFoo3 int = C2 int 

,如果你想獲得通用Foo3從您可以添加另一個NEWTYPE/ToFoo3 -instance整數:

newtype AInt a = AInt Int 

instance ToFoo3 (AInt a) where 
    type T (AInt a) = a 
    toFoo3 (AInt int) = C2 int 

這裏是一個簡單的測試:

λ> :t toFoo3 (AInt 5) :: Foo3 Char 
toFoo3 (AInt 5) :: Foo3 Char :: Foo3 Char 

的情況下,你是好奇 - 使用錯誤Int而不是應該是這樣的:

λ> :t toFoo3 (5 :: Int) :: Foo3 Char 

<interactive>:1:1: 
    Couldn't match type `Int' with `Char' 
    Expected type: Foo3 Char 
     Actual type: Foo3 (T Int) 
    In the expression: toFoo3 (5 :: Int) :: Foo3 Char 
+0

我懷疑這不是我們想要的,因爲即使所有的'Foo3 a'都有'C2'構造函數,它不能將'Int'轉換爲任意'Foo3 a'。 – 2014-10-28 09:00:50

+0

@ØrjanJohansen可能是真的 - 沒有想到這一點 - 也許我可以*救援*這與一些更多的黑客 - 你已經給出了使用多參數類型的解決方案;) – Carsten 2014-10-28 09:05:18

+0

謝謝你的黑客和編輯!這工作確實很好。然後,處理起來並不總是很容易,因爲在某些情況下,您可能會看到函數簽名中出現的「T」類型。但是,它爲限制複雜類型的代碼量提供了很大的優勢! – leo 2014-10-28 18:19:42

6

多參數類型類沒有函數依賴編譯對我來說:

{-# LANGUAGE MultiParamTypeClasses #-} 
{-# LANGUAGE FlexibleInstances #-} 

data Foo1 a = Foo1 
data Foo2 a = Foo2 (Foo3 a) 
data Foo3 a = C1 (Foo1 a) | C2 Int 

class ToFoo3 a b where 
    toFoo3 :: a -> Foo3 b 

instance ToFoo3 (Foo1 b) b where 
    toFoo3 foo1 = C1 foo1 

instance ToFoo3 Int b where 
    toFoo3 int = C2 int 

我理解它的方式,你不能在任何方向有功能依賴,因爲Int需要能夠轉換爲任何Foo3 a類型和Foo1 a需要能夠轉換爲相同Foo3 a類型。

當然這意味着你不能期望toFoo3的任何參數或結果類型來幫助推斷另一個,所以你有時可能需要一個令人討厭的類型註釋來使用它,但除此之外這應該工作。

編輯:我假設你希望能夠從Foo1 a轉換爲Foo3 bab不同。如果我錯了,然後用單參數類的OP代碼應該如果你改變一個實例

instance ToFoo3 (Foo1 b) where 
    toFoo3 Foo1 = C1 Foo1 
+0

正如你指出的那樣,這個解決方案在使用toFoo3函數時會給編譯器帶來惱人的提示:尤其是如果你保留了「Foo1 a」的幻像類型。所以,具體而言,你不能寫:test = toFoo3 Foo1。 – leo 2014-10-28 17:32:11

4

哇工作,其他兩種方法是複雜的。

簡單的解決方案是記住這些是幻影類型,你可以重建它們,只要你認爲合適。因此,例如,如果您有data Phantom x y = Phantom x,則存在cast :: Phantom x y -> Phantom x z類型的功能cast (Phantom x) = Phantom x,該功能再次使幻像類型變爲泛型。方法是:

  1. 將對象解構爲非幻影參數。
  2. 重建對象。
  3. 利潤。

在這種情況下,整個溶液是簡單的:

instance ToFoo3 (Foo1 b) where 
    toFoo3 _ = C1 Foo1 

類似地,對於Foo2Foo3,這是下一個邏輯步驟:

instance ToFoo3 (Foo3 a) where 
    toFoo3 (C1 x) = C1 Foo1 
    toFoo3 (C2 i) = C2 i 

instance ToFoo3 (Foo2 a) where 
    toFoo3 (Foo2 x) = toFoo3 x 
+0

這麼容易,我什至沒有想到...非常優雅的解決方案。唯一不便的是我可以看到,如果數據類型複雜,需要很多類和實例(例如,如果Int將用於不同類型的主類型部分)。但是非常強大的方面是,你不需要任何特殊的擴展,它使代碼簡單! – leo 2014-10-28 17:49:06

1

我回答我自己的問題,因爲我發現了GHC 7.8.1最近的一個解決方案,它使用了Coercible類中的函數coerce。

它提出了以下優點:

  1. 是更少的代碼編寫;
  2. 它並不意味着類型簽名中有任何其他類型;
  3. 它是「安全的」(通過反對不安全的Coerce在這種情況下也可能是一個解決方案);
  4. 它沒有運行時間成本。

的文檔可以在這裏找到: https://www.haskell.org/haskellwiki/GHC/Coercible

更多細節可以在出版物中找到: http://www.cis.upenn.edu/~eir/papers/2014/coercible/coercible.pdf

注意幻象類型的強制是一些東西,是明確要挾解決(見出版物第2.2段)。

在目前的情況下,它只需要一個調用強制函數,就是這樣!

-- We need to import Data.Coerce (no extensions are required). 
import Data.Coerce 

data Foo1 a = Foo1 
data Foo2 a = Foo2 (Foo3 a) 
data Foo3 a = C1 (Foo1 a) | C2 Int 

class ToFoo3 a where 
    toFoo3 :: a -> Foo3 b 

{-| 
We just need to apply the coerce function to the returned value. 
Note: we could simplify this equation by adopting point free style: 
> toFoo3 = coerce.C1 
-} 
instance ToFoo3 (Foo1 b) where 
    toFoo3 foo1 = coerce $ C1 foo1 

instance ToFoo3 Int where 
    toFoo3 int = C2 int 

我們現在可以運行一些測試(這不會在這個問題上顯示的代碼編譯):

test1 = toFoo3 Foo1 
test2 = toFoo3 (3::Int) 
+0

該解決方案的問題在於,函數toFoo3處理的值的類型在任何情況下都會再次變爲泛型。因此,如果您有類型數據Foo4 = Foo4,則以下等式test3 = toFoo3(Foo1 :: Foo1 Foo4)將具有類型Foo3 a。這在某些情況下可能是可以接受的,但大多數情況下,如果使用幻像類型,則希望非泛型類型保持如此。 – leo 2015-01-12 17:58:42