Haskell 多态函数用于在代数数据类型之间进行转换

7

我有两个Haskell函数,用于在两个代数数据类型之间进行转换。

data Ab = A | B
data Cd = C | D

fromAb :: Ab -> Cd
fromAb A = C
fromAb B = D

toAb :: Cd -> Ab
toAb C = A
toAb D = B

但我希望创建一个多态函数,它可以接受代数数据类型并在它们之间进行转换。

foo A = C
foo B = D
foo C = A
foo D = B

但 Haskell 会推断出 "foo A = C" 这个函数是什么意思。
foo :: Ab -> Cd

我试图将数据类型实例化为一个类,使得foo具有多态性,但是并未成功。

class Abcd a
instance Abcd Ab
instance Abcd Cd

foo :: Abcd a => a -> Ab

有什么建议吗?
3个回答

14

使用TypeFamilies非常自然。您定义了一个类型级别的函数。

type family Converted a
type instance Converted Ab = Cd
type instance Converted Cd = Ab

那么您的签名就变成了

foo :: a -> Converted a

如果你只是随意尝试更改类型,那么你完成了,但是由于你想在值层面上有不同的行为(从C返回A等),我们实际上需要将我们的情况分散在一个新类型类的实例中:

class Convertable a where
    foo :: a -> Converted a

instance Convertable Ab where
    foo A = C
    foo B = D

instance Convertable Cd where
    foo C = A
    foo D = B

(演示)

最后,如果使用最近的 GHC,您可能要考虑将 Converted 定义为一个封闭的类型同义词族,或者通过将实例移动到 Convertable 实例声明内部来使其“关联”。


4
好的,你最后那段代码片段中的签名仍然是错误的。它不应该是“foo :: Abcd a => a -> Ab”,因为如果“a ~ Ab”,那么函数应该返回“Cd”,而不是“Ab”。
有几种不同的方法可以实现你想要做的事情。首先,要认识到你试图表达的是一组通用行为,这些行为不是基于一个类型,而是基于两个类型之间的关系。这基本上是多参数类型类的目的(这可能是实现这一点最简单的方法)。
{-# LANGUAGE MultiParamTypeClasses #-}
data Ab = A | B
data Cd = C | D

fromAb :: Ab -> Cd
fromAb A = C
fromAb B = D

toAb :: Cd -> Ab
toAb C = A
toAb D = B

class Iso a b where
  to :: a -> b

instance Iso Ab Cd where
  to = fromAb

instance Iso Cd Ab where
  to = toAb

编辑:请注意,我的答案与jberryman的完全相同,都使用了类型家族。这就是我所说的“几种实现你想要的方法”。


Fundeps可能是op想要的:class Iso a b | a->b,这将是此问题的传统方法(在类型族之前)。 - jberryman
1
在这种情况下,由于“to”使用实例的两种类型,因此实际上不需要使用fundeps。它将防止您向此“可互换”类型组添加新类型。 - Mark Whitfield
抱歉我的评论表达不够清晰。我意思是说使用基金依赖性的类型推断会更好,其中 a -> b (相当于 TypeFamilies)或者 a -> b, b -> a。如写成 to A 可能会有歧义。 - jberryman
不,你是正确的,添加fundeps并不是“错误”的,但它表达了一个属性,这个属性取决于应用程序是否需要。如果我们稍后添加data Ef,那么我们可以通过声明适当的实例来使用to在3种类型之间任意转换。然而,如果我们使用fundep,这将变得不可能,但如果我们不需要这种灵活性,则可以获得更简单的类型推断。 - Mark Whitfield

1
另一种方法是使用扩展 MultiParamTypeClassesFunctionalDependencies:
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}

data Ab = A | B deriving (Show)
data Cd = C | D deriving (Show)

class Convert a b | a -> b where
  convert :: a -> b

instance Convert Ab Cd where
  convert A = C
  convert B = D

instance Convert Cd Ab where
  convert C = A
  convert D = B

演示:

λ> convert A
C
λ> convert B
D
λ> convert C
A
λ> convert D
B

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接