2014-10-28 59 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