【问题标题】:Haskell Types vs. Typeclasses in architectureHaskell 类型与架构中的类型类
【发布时间】:2015-12-15 01:55:40
【问题描述】:

假设我有一个应用程序,其核心数据是项目、文档集(“DocSets”)和文档(“Docs”)。文档不限于任何特定的内容类型;它们可以是电子表格、图像、HTML 等,并且可以具有其他文档没有的功能/行为。同样,根据我需要的行为类型,我可能有多种 DocSet。也许这个 DocSet 将文档显示在一个平面列表中,也许将一个状态与每个文档相关联,并且有一些基于状态的很酷的渲染技巧。

如果我不使用类型类,代码可能如下所示:

data Project = Project {
  projName :: Text,
  projDocSets :: [DocSet]
}

data DocSet = DocSet {
  dsName :: Text,
  dsDocs :: [Doc]
}

data Doc = Doc {
  docTitle :: Text,
  docType :: Text,
  docContents :: ByteString
}

我看到的问题是,虽然它允许存在多种文档(通过 docType 字段),但它本质上使用手动类型检查,这感觉不对(在我的脑海中)。我可以为 PagedTextDoc、ContinuousTextDoc、SpreadsheetDoc、ImageDoc 等提供数据构造函数。这对我来说似乎模块化很差。

编辑:我的“模块性差”评论依赖于没有很好地传达的知识。每个 Doc 都有一些共同的行为 ((反)序列化,被分组为一个 DocSet,一个标题,渲染, 其他随着应用程序的发展),以及一些独特的行为 (以易于编辑的格式进行内部表示,节省增量 编辑,部分呈现为 HTML 假设应用程序是一个 Web 应用程序)。

即使不是这样,在我看来,该应用程序也会是 如果我可以创建一个新模块,则更模块化,定义我的文档 以及您可以用它做什么,并且现在支持文档。那 方式,我在这里定义我的分页文档,我的电子表格在 在那里,两个人根本不关心对方。

也许我可以为 Docs 使用类型类?

data DocSet = DocSet {
  dsName :: Text,
  dsDocs :: [Doc]
}

class Doc d where
  docTitle :: d -> Text
  docType :: d -> Text
  docContents :: d -> ByteString

dsDocs 的声明现在不进行类型检查;列表仅适用于一种特定的具体类型,而不是类型类的任何成员。 DocSet 肯定需要能够存储/引用多种文档。

我尝试进行一些搜索,但坦率地说,我的 Google 技能在这里完全失败了。我是否正确考虑了数据结构?将所有 (Proj, DocSet, Doc) 都作为单个数据构造函数的方法真的是处理这个问题的最佳方法,还是我错过了 Haskell 处理多态性的方式?

【问题讨论】:

  • 第一种方法有什么问题?将docType :: Text 更改为docType :: DocumentType,这似乎非常明智。以哪种方式枚举您的文档类型的模块化较差?
  • 此外,如果您将类型类视为对“某物”建模,那么class C x where f0 :: x -> T0; f1 :: x -> T1; .. fn :: x -> Tn 形式的任何类型类都只是对具有字段{ f0 :: T0, .. , fn :: Tn } 的记录进行建模。所以你的类公式化并不比你的记录类型公式更强大(正如你所看到的,实际上更难使用)。
  • 编辑以试图澄清,但我可能做得不好。如果此应用程序将成为查看器,您的建议将非常有用。我可能需要稍后添加编辑,不知道这个时间表是什么。

标签: haskell


【解决方案1】:

好吧,基于类的提议甚至都行不通。类不是类型。如果Doc 是一个类,则不能有[Doc]

这里通常的方法是准确地确定您正在建模的是什么。

您想要一堆行为一致且可以轻松打包在一起的东西吗?然后将该行为放入一个类型中。这通常通过创建一个记录来完成,该记录包含您想要的每种行为的函数。

另一方面,您是否有很多事情需要非常不同的处理方式?在这种情况下,将数据放入一堆类型中,并让类型检查器(和详尽性检查器)一路引导您,以确保您始终在正确的位置处理正确的事情。

我根本不建议为此使用类。类(大部分)仅适用于您可以编写类型多态代码的情况,这些代码在存在具体类型时是有意义的,但您不需要知道类型是什么。 (这有点夸张,但它足够接近事实,可以将其用作第一近似启发式。)

【讨论】:

  • 伟大的想法。看看澄清是否对您有任何改变。
【解决方案2】:

假设我有一个应用程序,其核心数据是项目、文档 集合(“DocSets”)和文档(“Docs”)。文档不限于 任何特定的内容类型;它们可以是电子表格、图像、HTML、 等,并且可以具有其他文档没有的功能/行为。

听起来你不知道类型可以有多个构造函数(sum 类型)。而不是你的Doc 做:

data Doc = PagedTextDoc {docTitle :: Text, docContents :: ByteString}
         | SpreadsheetDoc {docTitle :: Text, docContents :: ByteString}
       ... etc

同样,我可能有多种 DocSet,具体取决于 我需要的行为类型。也许这个 DocSet 以 平面列表,也许将状态与每个文档相关联,并且 有一些基于状态的很酷的渲染技巧。

data DocSet = ListDocset Text [Doc] | KoolDocset [(Status, Doc)] | ...etc

【讨论】:

  • 我实际上知道该功能,但在二叉树的上下文中。改变 docContents 与每个数据构造函数一起呈现的方式,我认为这是一种有趣的思考方式。
  • 这打破了我一直在寻找的模块化,但它很干净,可以满足我的需要。有没有办法将不同类型的文档分成不同的模块。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-23
  • 1970-01-01
  • 2013-11-10
  • 1970-01-01
相关资源
最近更新 更多