简体   繁体   English

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

[英]mapping list of different types implementing same function?

I want to apply a function to every element in a list (map) but the elements may have different types but all implement the same function (here "putOut") like an interface. 我想将一个函数应用于列表中的每个元素(map),但元素可能有不同的类型,但都实现了相同的函数(这里是“putOut”),就像一个接口。 However I cannot create a list of this "interface" type (here "Outputable"). 但是,我无法创建此“接口”类型的列表(此处为“可输出”)。

How do I map a list of different types implementing the same function? 如何映射实现相同功能的不同类型的列表?

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 doesn't allow for heterogenous lists. Haskell不允许异构列表。 So you cannot make a list of Outputables, because your Out1 and Out2 are two distinct types, even if they both belong to the same type class. 所以你不能列出Outputables,因为你的Out1Out2是两种不同的类型,即使它们都属于同一类型类。

But there is a workaround which allows to simulate heterogeneous lists with ExistentialQuantification . 但是有一种解决方法可以使用ExistentialQuantification模拟异构列表。 See an example of heterogeneous lists in Haskell wikibook. 请参阅Haskell wikibook中的异构列表示例。

How to use 如何使用

  1. Put {-# LANGUAGE ExistentialQuantification #-} at the top of the module {-# LANGUAGE ExistentialQuantification #-}放在模块的顶部

  2. Define a box type, which hides heterogeneous elements inside: 定义一个盒子类型,它隐藏了异构元素:

      data ShowBox = forall s. Show s => SB s heteroList :: [ShowBox] heteroList = [SB (), SB 5, SB True] 
  3. Define a necessary class instance for the box type itself: 为框类型本身定义必要的类实例:

      instance Show ShowBox where show (SB s) = show s 
  4. Use a list of boxes. 使用框列表。

An example 一个例子

Your example may be rewritten as: 您的示例可能会被重写为:

{-# 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)

Are you sure you really want to put different types in a list? 你确定你真的想在列表中放入不同的类型吗?

You could use something like jetxee 's example with existential quantification, but think about what that actually does: You have a list of terms of unknown type, and the only thing you can do with them is apply putOut to get an IO () value back. 你可以使用jetxee的例子和存在量化,但想想实际上做了什么:你有一个未知类型的术语列表,你可以用它们做的唯一事情就是应用putOut来获得IO ()值背部。 That is to say, if the "interface" only provides one function with a known result type, there's no difference between a list of existentials and a list of results . 也就是说, 如果“接口”仅提供具有已知结果类型的一个函数,则存在列表和结果列表之间没有区别 The only possible use of the former involves converting it to the latter, so why add the extra intermediate step? 前者唯一可能的用途是将其转换为后者,那么为什么要添加额外的中间步骤呢? Use something like this instead: 使用这样的东西代替:

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]

This may seem counterintuitive at first, because it relies on some unusual features of Haskell. 这一开始看似违反直觉,因为它依赖于Haskell的一些不寻常的功能。 Consider: 考虑:

  • No extra computation is done--lazy evaluation means that show , unwords , &c. 没有进行额外的计算 - 懒惰的评估意味着showunwords和c。 won't be run unless the IO action is executed. 除非执行IO操作,否则不会运行。
  • No side effects are involved in simply creating IO () values--they can be stored in lists, passed around in pure code, and so on. 简单地创建IO ()值不会产生任何副作用 - 它们可以存储在列表中,在纯代码中传递,等等。 It's only the sequence_ function in main that runs them. 只有main中的sequence_函数才能运行它们。

The same argument applies to lists of "instances of Show " and whatnot. 相同的参数适用于“ Show实例”和诸如此类的列表。 It doesn't work well for instances of something like Eq , where you need two values of the type, but a list of existentials wouldn't work any better because you don't know if any two values are the same type. 不适用于像Eq这样的实例,你需要两个类型的值,但是存在列表不会更好,因为你不知道任何两个值是否是同一类型。 All you could do in that case would be check each element to be equal to itself, and then you might as well (as above) just create a list of Bool s and be done with it. 这种情况下你所能做的就是检查每个元素是否等于它自己,然后你也可以(如上所述)创建一个Bool列表并完成它。


In more general cases, it's best to keep in mind that Haskell type classes are not OOP interfaces . 在更一般的情况下,最好记住Haskell类型类不是OOP接口 Type classes are a powerful means of implementing ad-hoc polymorphism, but are not as well-suited to hiding implementation details. 类型类是实现ad-hoc多态的强大方法,但不太适合隐藏实现细节。 OOP languages tend to conflate ad-hoc polymorphism, code reuse, data encapsulation, behavioral subtyping, and such by tying everything to the same class hierarchy; OOP语言倾向于通过将所有内容绑定到同一个类层次结构来混淆ad-hoc多态,代码重用,数据封装,行为子类型等。 in Haskell you can (and often must ) deal with each separately. 在Haskell你可以(并且经常必须 )分别处理每个。

An object in an OOP language is, roughly speaking, a collection of (hidden, encapsulated) data bundled with functions to manipulate that data, each of which takes the encapsulated data as an implicit argument ( this , self , etc.). 粗略地说,OOP语言中的对象是与操作该数据的函数捆绑在一起的(隐藏的,封装的)数据的集合,每个数据都将封装的数据作为隐式参数( thisself等)。 To replicate this in Haskell, you don't need type classes at all: 要在Haskell中复制它,您根本不需要类型类:

  • Write each "class method" as a regular function, with the self parameter made explicit. 将每个“类方法”写为常规函数,并使self参数显式化。
  • Partially apply each function to a value of the "encapsulated" data 将每个函数部分应用于“封装”数据的值
  • Combine the partially applied functions into a single record type 部分应用的函数组合到单个记录类型中

The record type replaces the interface ; 记录类型取代了接口 ; any collection of functions with the proper signatures represents an implementation of the interface. 具有适当签名的任何函数集合表示接口的实现。 In some ways this is actually better object-oriented style , because the private data is completely hidden and only the exterior behavior is exposed. 在某些方面,这实际上是更好的面向对象的风格 ,因为私有数据是完全隐藏的,只有外部行为才会暴露。

As in the simpler case above, this is almost exactly equivalent to the existential version; 与上面更简单的情况一样,这几乎完全等同于存在主义版本; the record of functions is what you'd get by applying each method of the type class to each existential. 通过将类型类的每个方法应用于每个存在主义,您可以获得函数记录。

There are some type classes where using a record of functions wouldn't work well-- Monad , for instance--which are generally also the same type classes that can't be expressed by conventional OOP interfaces , as demonstrated by modern versions of C# making extensive use of monadic style yet not providing any sort of generic IMonad interface. 有一些类型的类使用函数记录不能正常工作 - 例如Monad - 它们通常也是传统OOP接口无法表达的相同类型类 ,如现代版本的C#所示广泛使用IMonad风格但不提供任何类型的通用IMonad接口。

See also this article covering the same things I'm saying. 另见本文涉及我所说的相同内容。 You may also want to look at Graphics.DrawingCombinators for an example of a library offering extensible, composable graphics without using type classes . 您可能还需要查看Graphics.DrawingCombinators ,以获取提供可扩展,可组合图形而不使用类型类的库的示例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM