【问题标题】:Haskell polymorphic function to convert between algebraic data types用于在代数数据类型之间转换的 Haskell 多态函数
【发布时间】:2014-10-01 06:28:03
【问题描述】:

我有两个 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

有什么想法吗?

【问题讨论】:

    标签: function haskell types


    【解决方案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
    

    【讨论】:

      【解决方案2】:

      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
      

      (live demo)

      最后,如果使用最近的 GHC,您可以考虑将 Converted 设为封闭类型同义词系列,或者通过在 Convertable 实例声明中移动实例使其“关联”。

      【讨论】:

        【解决方案3】:

        好吧,您最后一个代码片段中的签名仍然是错误的。不会是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,这将是解决此问题的传统方法(预类型家庭)。
        • fundeps 对于这种情况实际上不是必需的,因为to 使用这两种类型的实例。它会阻止您向这个“可相互转换”的类型组添加新类型。
        • 对不起,我的评论不是很清楚。我的意思是使用fundeps 进行类型推断会更好,a -> b(相当于TypeFamilies)或a -> b, b -> a。如所写,例如to A 会模棱两可,我相信。
        • 不,你是对的,添加fundeps当然没有错误,但它表达的属性可能会或可能不会取决于应用程序。如果我们稍后添加data Ef,我们没有理由不能通过声明适当的实例来使用to 在3 种类型之间进行任意转换。但是,如果我们使用fundep,这将变得不可能,但如果我们不需要这种灵活性,当然还有更简单的类型推断的额外好处。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-05-01
        • 1970-01-01
        • 2019-06-16
        • 2017-02-19
        • 2012-06-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多