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

Haskell 实现相同功能的不同类型的映射列表?,haskell,interface,map,types,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

我想对列表(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

Haskell不允许异构列表。因此,您不能列出输出文件,因为您的
Out1
Out2
是两种不同的类型,即使它们都属于同一类型

但是有一种变通方法,允许使用
存在量化
模拟异构列表。 请参阅Haskell wikibook中的示例

如何使用
  • {-#语言存在量化{-}
    放在模块顶部

  • 定义一个框类型,该框类型将隐藏内部的异构图元:

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

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

  • 一个例子 您的示例可以改写为:

    {-# 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)
    
    您确定要在列表中放置不同的类型吗

    您可以使用类似于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的一些不同寻常的特性。考虑:

    • 不进行额外计算——延迟计算意味着
      show
      unwords
      ,&c。除非执行
      IO
      操作,否则不会运行
    • 简单地创建
      IO()
      值不涉及任何副作用——它们可以存储在列表中,也可以在纯代码中传递,等等。只有
      main
      中的
      sequence\
      函数运行它们
    同样的参数也适用于“Show的实例”和诸如此类的列表。对于像
    Eq
    这样需要两个类型的值的实例,它不起作用,但是一个存在主义列表也不会更好,因为您不知道任何两个值是否是相同的类型。在这种情况下,您所能做的就是检查每个元素是否与自身相等,然后您也可以(如上所述)创建一个
    Bool
    s的列表并使用它


    在更一般的情况下,最好记住,Haskell类型的类不是OOP接口。类型类是实现特殊多态性的强大手段,但不太适合隐藏实现细节。OOP语言倾向于通过将所有内容绑定到同一类层次结构,将即席多态性、代码重用、数据封装、行为子类型等融合在一起;在Haskell中,您可以(而且通常必须)单独处理每一个问题

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

    • 将每个“类方法”作为常规函数编写,并显式设置
      self
      参数
    • 将每个函数部分应用于“封装”数据的值
    • 将部分应用的函数合并为一个记录类型
    记录类型替换接口;任何具有适当签名的函数集合都表示接口的实现。在某些方面,这实际上是更好的面向对象风格,因为私有数据是完全隐藏的,只有外部行为是公开的

    就像上面简单的例子一样,这几乎完全等同于存在主义版本;函数的记录是通过将类型类的每个方法应用于每个存在类得到的

    有些类型类在使用函数记录时无法很好地工作,例如,
    Monad
    ,它们通常也是相同的类型类,不能用传统的OOP接口来表示,现代版本的C#大量使用了一元风格,但没有提供任何类型的通用
    IMonad
    接口


    请参见我所说的内容。您可能还想查看一个库的示例,该库提供可扩展、可组合的图形,而不使用类型类

    当用户无法使用此列表中的用户定义类型时,如何设计程序模块化?它们是:-)这就是存在量化的目的。我认为您应该了解数据类型和类型类之间的区别。例如,请参见此问题的答案>“是否确实要将不同类型放入列表?”<实际上,我有一个由模型(如圆形、矩形、网格等)和资源(纹理等)组成的渲染系统。在处理步骤I转换模型时,返回(不同)模型的列表,然后将其传递到输出IO,输出IO绘制与模型对应的资源。为了让用户使用自定义模型扩展库,我需要提供一些接口概念(至少这是我在OOP中要做的)。@sisif:有关如何在Haskell中表示OOP风格接口的更好描述,请参阅我的扩展答案。