我们有以下数据类型:
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 :(得分: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
和{{1}从Foo3 b
转换为a
不同的。如果我错了,那么如果将一个实例更改为
b
答案 1 :(得分:4)
哇,另外两种方法很复杂。
简单的解决方案是记住这些是幻像类型,您可以根据需要重建它们。因此,例如,如果您有data Phantom x y = Phantom x
,则存在cast (Phantom x) = Phantom x
类型的函数cast :: Phantom x y -> Phantom x z
,这会使幻像类型再次成为通用。方法是:
在这种情况下,整个解决方案就像:
一样简单instance ToFoo3 (Foo1 b) where
toFoo3 _ = C1 Foo1
同样适用于Foo2
和Foo3
,这是下一个合乎逻辑的步骤:
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
答案 2 :(得分:3)
我不是百分百确定这是否是你想要的, 但你可以让编译器接受你尝试过的东西 使用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
- 实例:
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
答案 3 :(得分:1)
我正在回答我自己的问题,因为我找到了GHC 7.8.1提供的最新解决方案,即强制类强制函数的使用。
它具有以下优点:
文档可在此处获得: 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)
答案 4 :(得分:1)
经过多次尝试而失败后,我终于得到了满意的答复! 诀窍是将函数依赖项与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")
注意:所有这些复杂情况都在这里,因为实例必须转换&#34;非幻像&#34;键入Int到幻像类型。如果我们只处理幻像类型,我们可以做一些更简单的事情,例如:
class ToFoo3 a (Foo3 b) where
toFoo3 :: a b -> Foo3 b
instance ToFoo3 Foo1 Foo3 where
...