实现相同功能的不同类型的映射列表?

时间:2010-06-17 11:49:47

标签: haskell interface map types

我想将一个函数应用于列表中的每个元素(map),但是这些元素可能具有不同的类型,但是它们都像接口一样实现相同的功能(这里是“putOut”)。但是,我无法创建此“接口”类型的列表(此处为“可输出”)。

如何映射实现相同功能的不同类型的列表?

import Control.Monad

main :: IO ()
main = do
 mapM_ putOut lst
 where
  lst :: [Outputable] -- ERROR: Class "Outputable" used as a type
  lst = [(Out1 1),(Out2 1 2)]

class Outputable a where
 putOut :: a -> IO ()

-- user defined:

data Out1 = Out1 Int deriving (Show)
data Out2 = Out2 Int Int deriving (Show)

instance Outputable Out1 where
 putOut out1 = putStrLn $ show out1

instance Outputable Out2 where
 putOut out2 = putStrLn $ show out2

2 个答案:

答案 0 :(得分:10)

Haskell不允许异构列表。因此,您无法列出可输出项,因为Out1Out2是两种不同的类型,即使它们都属于同一类型类。

但是有一种解决方法可以使用ExistentialQuantification来模拟异构列表。 请参阅Haskell wikibook中的heterogeneous lists示例。

如何使用

  1. {-# LANGUAGE ExistentialQuantification #-}放在模块顶部

  2. 定义一个盒子类型,它隐藏了异构元素:

      data ShowBox = forall s. Show s => SB s
      heteroList :: [ShowBox]
      heteroList = [SB (), SB 5, SB True]
    
  3. 为框类型本身定义必要的类实例:

      instance Show ShowBox where
        show (SB s) = show s
    
  4. 使用方框列表。

  5. 一个例子

    您的示例可能会被重写为:

    {-# LANGUAGE ExistentialQuantification #-}
    
    main :: IO ()
    main = do
     mapM_ print lst
     putStrLn "end"
     where
      lst :: [Printable]
      lst = [P (Out1 1),P (Out2 1 2)]
    
    -- box type (2)
    data Printable = forall a . Show a => P a
    
    -- necessary Show instance for the box type (3)
    instance Show Printable where show (P x) = show x
    
    -- user defined:
    data Out1 = Out1 Int deriving (Show)
    data Out2 = Out2 Int Int deriving (Show)
    

答案 1 :(得分:8)

您确定确实想要在列表中添加不同的类型吗?

你可以使用 jetxee 这样的例子来存在量化,但想想实际上做了什么:你有一个未知类型的术语列表,你可以用它们做唯一的事情应用putOut来获取IO ()值。也就是说,如果“接口”仅提供一个具有已知结果类型的函数,则存在列表与结果列表之间没有区别。前者唯一可能的用途是将其转换为后者,那么为什么要添加额外的中间步骤呢?请改用这样的东西:

main :: IO ()
main = do
    sequence_ lst
    where lst :: [IO ()]
          lst = [out1 1, out2 1 2]

out1 x = putStrLn $ unwords ["Out1", show x]
out2 x y = putStrLn $ unwords ["Out2", show x, show y]

这一开始看似违反直觉,因为它依赖于Haskell的一些不寻常的功能。考虑:

  • 无需额外计算 - 延迟评估意味着showunwords和& c。除非执行IO操作,否则不会运行。
  • 简单地创建IO ()值不会产生副作用 - 它们可以存储在列表中,以纯代码传递,等等。它只是sequence_中运行它们的main函数。

相同的参数适用于“Show的实例”列表等等。 适用于像Eq这样的实例,你需要两个类型的值,但是存在的列表不会更好,因为你不知道如果任何两个值是相同的类型。您只能在 案例中检查每个元素是否与其自身相等,然后您也可以(如上所述)创建Bool的列表并完成它


在更一般的情况下,最好记住 Haskell类型类不是OOP接口。类型类是实现ad-hoc多态的强大方法,但不太适合隐藏实现细节。 OOP语言倾向于通过将所有内容绑定到同一个类层次结构来混淆ad-hoc多态,代码重用,数据封装,行为子类型等。在Haskell中你可以(通常必须)分别处理每个。

OOP语言中的对象粗略地说是一个(隐藏的,封装的)数据集合,这些数据与操作该数据的函数捆绑在一起,每个数据都将封装的数据作为隐式参数(this,{ {1}}等)。要在Haskell中复制它,您根本不需要类型类:

  • 将每个“类方法”写为常规函数,并使self参数明确。
  • 将每个函数部分应用于“封装”数据的值
  • 部分应用的功能合并为单一记录类型

记录类型取代界面;具有适当签名的任何函数集合表示接口的实现。在某些方面,这实际上是更好的面向对象的风格,因为私有数据是完全隐藏的,只有外部行为才会暴露。

在上面的简单案例中,这几乎完全等同于存在主义版本;函数记录是通过将每个类型类的方法应用于每个存在主义而获得的。

有一些类型类使用函数记录不能正常工作 - 例如self - 它们通常也是传统无法表达的相同类型类OOP接口,如现代版本的C#所示,广泛使用monadic样式但未提供任何类型的通用Monad接口。

另见this article涵盖我所说的相同内容。您可能还需要查看Graphics.DrawingCombinators以获取提供可扩展,可组合图形而不使用类型类的库的示例。

相关问题