Haskell:`Map(a,b)c`对`map a(Map b c)`?

时间:2013-05-17 15:39:36

标签: haskell map currying

将地图视为有限函数的表示,可以以咖喱或非曲面的形式给出两个或多个变量的映射;也就是说,Map (a,b) cMap a (Map b c)类型是同构的,或与它接近的东西。

有哪些实际考虑 - 效率等 - 用于在两种表示之间进行选择?

3 个答案:

答案 0 :(得分:17)

元组的Ord实例使用字典顺序,因此Map (a, b) c首先会按a排序,因此整体顺序将相同。关于实际考虑:

  • 因为Data.Map是一个二进制搜索树,所以在一个键上的分割与查找相当,因此在未处理的表单中获取给定a的子图将不会更加昂贵而不是以咖喱形式。

  • 咖喱形式可能会产生一个不太平衡的树,因为有多棵树而不是一棵树的明显原因。

  • 咖喱表格会有一些额外的开销来存储嵌套地图。

  • 如果某些a值产生相同的结果,则可以共享表示“部分应用程序”的咖喱表格的嵌套地图。

  • 同样,咖喱表格的“部分申请”会为您提供现有的内部地图,而未表格的表格必须构建新地图。

因此,未经证实的表格明显更好,但如果您希望经常进行“部分申请”并且可以从共享Map b c值中受益,那么咖喱表格可能会更好。

请注意,必须小心谨慎,以确保您从潜在的共享中实际受益;您需要显式定义任何共享内部映射,并在构造完整映射时重用单个值。

编辑: Tikhon Jelvis在评论中指出,元组构造函数的内存开销 - 我认为没有考虑到 - 根本不是微不足道的。咖喱形式肯定会有一些开销,但这个开销与有多少不同的a值成正比。另一方面,未处理形式的元组构造函数开销与密钥总数成比例。

因此,如果平均而言,对于a的任何给定值,有三个或更多使用它的不同键,您可能会使用咖喱版本来节省内存。当然,对不平衡树木的担忧仍然适用。我想的越多,我就越怀疑咖喱的形式是明确的更好,除非你的钥匙非常稀疏且分布不均匀。


请注意,由于定义的确定对GHC很重要,因此如果要共享子表达式,则在定义函数时需要同样小心;这是您有时看到以这样的样式定义函数的一个原因:

foo x = go
  where z = expensiveComputation x
        go y = doStuff y z

答案 1 :(得分:4)

元组在两个元素中都是懒惰的,所以元组版本引入了一些额外的懒惰。这是好还是坏很大程度上取决于您的使用情况。 (特别是,比较可能会强制元组元素,但前提是有许多重复的a值。)

除此之外,我认为这将取决于你有多少重复。如果a几乎总是不同b,那么你将会有很多小树,所以元组版本可能会更好。另一方面,如果情况相反,非元组版本可能会为您节省一点时间(一旦找到合适的子树并且您正在寻找a,就不会经常重新比较b )。

我想起了尝试,以及它们如何存储公共前缀一次。非元组版本似乎有点像。如果有很多共同的前缀,trie可能比BST更有效,如果没有,则效率更低。

但最重要的是:基准测试!! ; - )

答案 2 :(得分:3)

除了效率方面,这个问题还有一个务实的方面:你想用这个结构做什么?

例如,您是否希望能够为类型a的给定值存储空地图?如果是这样,那么未经证实的版本可能更实用!

这是一个简单的例子:假设我们想要存储String - 值的人物属性 - 比如该人的stackoverflow个人资料页面上某些字段的值。

type Person = String
type Property = String

uncurriedMap :: Map Person (Map Property String)
uncurriedMap = fromList [
                   ("yatima2975", fromList [("location","Utrecht"),("age","37")]),
                   ("PLL", fromList []) ]
curriedMap :: Map (Person,Property) String
curriedMap = fromList [
                 (("yatima2975","location"), "Utrecht"),
                 (("yatima2975","age"), "37") ]

使用curried版本,没有很好的方法来记录系统已知用户"PLL"但未填写任何信息的事实。人/属性对("PLL",undefined)将导致运行时崩溃,因为密钥中的Map是严格的。

您可以将curriedMap的类型更改为Map (Person,Property) (Maybe String)并将Nothing存储在那里,这可能是案例中的最佳解决方案;但是,如果存在未知/不同数量的属性(例如,取决于人的类型),那么也会遇到困难。

所以,我想这也取决于你是否需要这样的查询功能:

data QueryResult = PersonUnknown | PropertyUnknownForPerson | Value String
query :: Person -> Property -> Map (Person, Property) String -> QueryResult

在咖喱版中很难写(如果不是不可能的话),但在未经证实的版本中很容易。