因此,如果我有两种大致相同的数据类型,我可以像这样编写它们:
data A t = A1 | A2 | A3 | A4 (B t)
data B t = B1 | B2 | B3 | B4 t
type AX = A X
type AY = A Y
现在通过AX和AY编写函数很容易,或者如果他们不关心则编写'A t'。但那我该怎么写转换?
convert :: AX -> AY
convert (A4 (B4 x)) = A4 (B4 (xToY x))
convert ax = ay -- type error
所以现在我需要手工编写所有其他案例,即使它们都不依赖于类型参数。更糟糕的是,虽然我可以在不依赖于“A {}”的参数的情况下匹配构造函数,但如果我需要这些参数来重构数据,那是不可能的。
有更好的方法吗?我觉得GADT应该能够表达这一点,但很难看出如何从不依赖它的术语中消除类型变量。我想我必须有A1 A2等单独的类型,然后我会失去封闭和案例检查...此外我不想手写Show,Eq和Typeable!我能想到的另一种方法是重新排列整个结构以隔离变化的部分,即
data A t = Independent | B4 t
data Independent = A1 | A2 | ...
但是这使得数据的每次其他使用都变得笨拙,以获得一个转换函数。
当然,另一种选择是忘记类型安全并包括'B4(X Y)'并在它有错误的值时输入一些不错的运行时错误。
也许有更好的方法来处理“几乎相同”的数据类型?
更新:所以我通过编写伪fmap来解决这个问题:
convert :: (a -> b) -> A a -> A b
至少允许我将转换部件和包装拆包样板分开。我想一个足够先进的TH可能会为我产生其中一个。我仍然对其他方法感到好奇。我觉得这种方式仍然不准确,因为它允许用任何填充洞,而我真正的意思是两件事之一。
答案 0 :(得分:2)
基本上你想要的是A
和B
Functor
。幸运的是,您可以使用Functor
允许Haskell为这些类型派生明显的-XDeriveFunctor
实例。然后,使用以下代码:
data A t = A1 | A2 | A3 | A4 (B t) deriving Functor
data B t = B1 | B2 | B3 | B4 t deriving Functor
...
convert :: AX -> AY
convert a = fmap xToY a
以下是为X
,Y
和xToY
添加一些虚拟定义后的GHCi会话示例,以及导出Show
的所有内容:
*Main> convert $ A4 B1
A4 B1
*Main> convert $ A4 (B4 X)
A4 (B4 Y)
*Main> convert $ A2
A2
如果您真的只想让A
和B
“填充”X
或Y
类型的内容,您可以这样约束它们:< / p>
class XorY t where
instance XorY X
instance XorY Y
data XorY t => A t = A1 | A2 | A3 | A4 (B t)
data XorY t => B t = B1 | B2 | B3 | B4 t
当然,这将排除制作A
和B
Functor
的可能性,因此上述技巧将不再适用。