[英]mapping list of different types implementing same function?
我想将一个函数应用于列表中的每个元素(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不允许异构列表。 所以你不能列出Outputables,因为你的Out1
和Out2
是两种不同的类型,即使它们都属于同一类型类。
但是有一种解决方法可以使用ExistentialQuantification
模拟异构列表。 请参阅Haskell wikibook中的异构列表示例。
将{-# LANGUAGE ExistentialQuantification #-}
放在模块的顶部
定义一个盒子类型,它隐藏了异构元素:
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
列表并完成它。
在更一般的情况下,最好记住Haskell类型类不是OOP接口 。 类型类是实现ad-hoc多态的强大方法,但不太适合隐藏实现细节。 OOP语言倾向于通过将所有内容绑定到同一个类层次结构来混淆ad-hoc多态,代码重用,数据封装,行为子类型等。 在Haskell你可以(并且经常必须 )分别处理每个。
粗略地说,OOP语言中的对象是与操作该数据的函数捆绑在一起的(隐藏的,封装的)数据的集合,每个数据都将封装的数据作为隐式参数( this
, self
等)。 要在Haskell中复制它,您根本不需要类型类:
self
参数显式化。 记录类型取代了接口 ; 具有适当签名的任何函数集合表示接口的实现。 在某些方面,这实际上是更好的面向对象的风格 ,因为私有数据是完全隐藏的,只有外部行为才会暴露。
与上面更简单的情况一样,这几乎完全等同于存在主义版本; 通过将类型类的每个方法应用于每个存在主义,您可以获得函数记录。
有一些类型的类使用函数记录不能正常工作 - 例如Monad
- 它们通常也是传统OOP接口无法表达的相同类型类 ,如现代版本的C#所示广泛使用IMonad
风格但不提供任何类型的通用IMonad
接口。
另见本文涉及我所说的相同内容。 您可能还需要查看Graphics.DrawingCombinators ,以获取提供可扩展,可组合图形而不使用类型类的库的示例。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.