[英]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,因为你的
Out1
和Out2
是两种不同的类型,即使它们都属于同一类型类。
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中的异构列表示例。
Put {-# LANGUAGE ExistentialQuantification #-}
at the top of the module 将
{-# LANGUAGE ExistentialQuantification #-}
放在模块的顶部
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]
Define a necessary class instance for the box type itself: 为框类型本身定义必要的类实例:
instance Show ShowBox where show (SB s) = show s
Use a list of boxes. 使用框列表。
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:
考虑:
show
, unwords
, &c. show
, unwords
和c。 won't be run unless the IO
action is executed. IO
操作,否则不会运行。 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语言中的对象是与操作该数据的函数捆绑在一起的(隐藏的,封装的)数据的集合,每个数据都将封装的数据作为隐式参数(
this
, self
等)。 To replicate this in Haskell, you don't need type classes at all: 要在Haskell中复制它,您根本不需要类型类:
self
parameter made explicit. self
参数显式化。 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.