合并同一类的不同实例

时间:2014-02-14 19:05:13

标签: haskell

我正在编写一个简单的CLI应用程序,它允许用户修改一些文档,真实的东西更复杂,但是假设我有一个跟踪当前文档并将CLI上指定的操作应用于所有文档的状态:

data MyState = MyState { doc :: [Document] }

run :: UserAction -> State MyState ()
run a = do s <- get ; put $ s { doc = map (edit a) (doc s) }

我有一些函数可以修改Document,它目前只是一个内存数据结构:

data Document = Document [Stuff] [OtherStuff]
edit :: Document -> UserAction -> Document

现在我想重构一下,把公共接口拉成一个类:

class Document d where edit :: d -> UserAction -> d
instance Document MemoryDocument where edit = ... -- as before
instance Document RemoteDocument where edit = ... -- use HDBC etc

但我怎样才能轻松整合这个?

明显的变化是我无法在一个州处理不同类型的文件的问题:

data (Document a) => MyState a = MyState { doc :: [a] }

由于a这里需要是一个动态类型,MemoryDocument或RemoteDocument。我可以使用包装器类型来模拟它,但这是很多不必要的样板代码(即每个实例的每个类函数一个模式)

data MyState = MyState { doc :: [DocumentWrapper] }
data DocumentWrapper = MD MemoryDocument | RD RemoteDocument

import Control.Applicative
instance Document DocumentWrapper where
  edit (MD d) = MD <$> edit d
  edit (RD d) = RD <$> edit d

有没有办法避免这种情况,也许有RankNTypes?

1 个答案:

答案 0 :(得分:5)

老实说,如果你有两种类型,那么

 [Either MemoryDocument RemoteDocument]

绝对是要走的路。事实上,如果你有一个静态数量的文件包装在一个sum类型可能是正确的举动。

您所描述的内容称为存在类型,您可以在其中删除类型信息,以便将异构数据存储在一个列表中。你可以这样做

 {-# LANGUAGE ExistentialQuantification #-}
 data DocBox = forall a. Document a => DocBox a

然后你有一个[DocBox],你可以像使用它一样使用它。定义

会很有帮助
 instance Document DocBox where
   edit (DocBox d) a = DocBox $ edit d a 

但这通常被认为是不好的做法,因为坦率地说它太过分了。你有两种类型,使用一个:)它就是它的用途。