表示Haskell中类型类的任意实现



我正在努力克服多年来在经典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的结果可以看作是BarFoo之间的桥梁

最新更新