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

Representing arbitrary implementations of a typeclass in Haskell

我正在努力克服多年在经典 Java 风格继承模型中工作的经历,以真正适应 Haskell。一直不太顺利,我需要一点帮助。

假设我有一个类型类Foo。我想表示 Foo 的任意实现 类 的列表,但不是以限制列表中每个项目相同的方式;我需要一个异构的、开放的类型,这样我的图书馆的消费者就可以实现他们自己的 Foo.

之所以这样是因为我想写这样的东西(pidgin 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") ]

我如何创建一个可扩展类型类(或其他数据类型),可以与其他相同类型类自由混合,但不一定是完全相同的类型?

你要找的是Heterogenous collections

恕我直言,最好的方法是使用 existentials,如 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 的列表,就很难返回到原始类型。但是,如果您愿意 忘记 原始类型,另一种解决问题的方法是 convert 您的数据为 Foo .这种方法可能看起来是错误的,但请记住,数据在 Haskell 中是不可变的,因此您永远无法修改您的对象,因此您可以对 Foo 做的唯一事情就是从中获取信息他们。这样一来,真正的 Bar 表现得像 Foo 和新的 Foo 表现得像 Bar 版本就没有区别了(另外,Haskell 是懒惰的,所以转换 只会在需要时进行。

当你意识到这一点时,你甚至可以更进一步替换 object 但只是一堆函数(如@chi link 中所述)。您的代码变为

 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之间的桥梁