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

use*_*228 3 haskell types interface map

我想将一个函数应用于列表中的每个元素(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
Run Code Online (Sandbox Code Playgroud)

sas*_*nin 10

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

但是有一种解决方法可以模拟异构列表ExistentialQuantification.请参阅Haskell wikibook中的异构列表示例.

如何使用

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

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

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

      instance Show ShowBox where
        show (SB s) = show s
    
    Run Code Online (Sandbox Code Playgroud)
  4. 使用框列表.

一个例子

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

{-# 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)
Run Code Online (Sandbox Code Playgroud)


C. *_*ann 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]
Run Code Online (Sandbox Code Playgroud)

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

  • 没有额外的计算做-懒惰的评价意味着show,unwords,&C.除非IO执行操作,否则不会运行.
  • 简单地创建IO ()值不会产生任何副作用- 它们可以存储在列表中,以纯代码传递,等等.它只是运行它们的sequence_功能main.

同样的论点适用于"实例Show"和诸如此类的列表.它不适用于Eq需要两个类型值的实例,但是存在列表不会更好,因为你不知道任何两个值是否是同一类型.在这种情况下你所能做的就是检查每个元素是否等于它自己,然后你也可以(如上所述)创建一个Bools 列表并完成它.


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

在一个面向对象的语言中的对象,粗略地说,与功能捆绑操作这些数据(隐藏,封装的)数据,其中每一个取封装的数据作为隐式参数(的集合this,self等等).要在Haskell中复制它,您根本不需要类型类:

  • 将每个"类方法"写为常规函数,并使self参数显式化.
  • 将每个函数部分应用于"封装"数据的值
  • 部分应用的函数组合到单个记录类型中

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

与上面更简单的情况一样,这几乎完全等同于存在主义版本; 通过将类型类的每个方法应用于每个存在主义,您可以获得函数记录.

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

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