我正在努力克服多年来在经典java风格继承模型中工作的经验,以便真正熟悉Haskell。事情不太顺利,我需要一点帮助。
假设我有一个类型类Foo
。我想表示Foo
的任意实现类的列表,但不是以一种限制列表中的每个项目都相同的方式;我需要一个异构的开放类型,这样我的库的消费者就可以实现他们自己的Foo
。
原因是因为我想写像下面这样的东西(洋泾浜Haskell):
class Foo -- something...
data Bar = Bar Int Char
data Baz = Baz String
instance Foo Bar
instance Foo Baz
saySomething :: Foo -> String
saySomething (Bar x _) = "Bar number " ++ (show x)
saySomething (Baz x) = "Your baz is " ++ x
saySomething _ = "Unknown Foo"
sayAll :: [Foo] -> [String]
sayAll = map (saySomething)
main = putStrLn $ sayAll [ (Bar 5 'k'), (Bar 7 'G'), (Baz "hello") ]
如何创建一个可扩展的类型类(或其他数据类型),可以自由地与相同类型类的其他类型混合,但不一定是完全相同的类型?
你正在寻找的是异构集合
我认为最好的方法是使用存在性,就像wiki上描述的那样:
{-# LANGUAGE ExistentialQuantification #-}
data Showable = forall a . Show a => MkShowable a
pack :: Show a => a -> Showable
pack = MkShowable
hlist :: [Showable]
hlist = [ pack 3
, pack 'x'
, pack pi
, pack "string"
, pack (Just ()) ]
您试图做的(异构)收集的问题是:一旦您有Foo
的列表,就很难回到原始类型。但是,如果您乐意忘记原始类型,那么解决问题的另一种方法是将数据转换为Foo
。这种方法可能看起来是错误的,但请记住,数据在Haskell中是不可变的,所以无论如何你永远无法修改你的对象,所以你唯一能做的就是从你的Foo
中获取信息。那么,像Foo
一样的真正的Bar
和像Bar
版本一样的新Foo
之间就没有区别了(而且,Haskell是懒惰的,所以转换只在需要的时候才会进行)。
当你意识到这一点时,你甚至可以更进一步,替换object
,但只是一堆函数(如@chi链接所述)。你的代码变成
data Foo = { saySomething :: String, saySomethingElse :: String }
-- Haskell is lazzy, so saySomething can be seen as function
-- without argument
class Fooable a where
toFoo :: a -> Foo
data Bar = Bar Int Char
data Baz = Bar String
instance Fooable Bar where
toFoo (Bar i c) = { "Bar number : " ++ show i, "Bar char : " ++ [c] }
instance Fooable Baz where
toFoo (Baz s) = {"Your baz is " ++ s, "Nothing to add." }
sayAll : [Foo] -> [String]
sayAll = map saySomething
bar = Bar 1 'a'
baz = Baz "Bazzar"
sayAll [toFoo bar, toFoo baz]
请注意,即使somethingElse
看起来不像一个函数,但它只是一个普通的函数,它永远不会被调用(在这个例子中)。toFoo
的结果可以看作是Bar
和Foo
之间的桥梁