在 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") ]
我如何创建一个可扩展类型类(或其他数据类型),可以与其他相同类型类自由混合,但不一定是完全相同的类型?
恕我直言,最好的方法是使用 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
的结果可以看作是Bar
和Foo
之间的桥梁。
我正在努力克服多年在经典 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") ]
我如何创建一个可扩展类型类(或其他数据类型),可以与其他相同类型类自由混合,但不一定是完全相同的类型?
恕我直言,最好的方法是使用 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
的结果可以看作是Bar
和Foo
之间的桥梁。