将地图视为有限函数的表示形式,两个或多个变量的地图可以以柯里化(Curried)或未柯里化(Uncurried)形式给出;即类型
在选择两种表示形式之间有哪些实际考虑因素 - 效率等等?
Map (a,b) c
和Map a (Map b c)
是同构的,或者非常接近。在选择两种表示形式之间有哪些实际考虑因素 - 效率等等?
Map (a,b) c
和Map a (Map b c)
是同构的,或者非常接近。Ord
的元组实例使用词典序,所以无论如何 Map (a, b) c
都会首先按照 a
进行排序,因此总体顺序将保持不变。关于实际考虑:
由于 Data.Map
是一个二叉搜索树,所以在键分割方面,非柯里化形式的子映射查找与柯里化形式相比不会显著更加昂贵。
柯里化形式可能会产生更不平衡的树,原因很明显,因为有多棵树而不是只有一棵。
柯里化形式将具有额外的开销来存储嵌套映射。
表示“部分应用程序”的柯里化形式的嵌套映射可以共享,如果某些 a
值产生相同的结果。
类似地,柯里化形式的“部分应用”会给你现有的内部映射,而非柯里化形式必须构造一个新映射。
因此,在一般情况下,非柯里化形式是明显更好的,但如果您希望经常进行“部分应用程序”并从共享 Map b c
值中受益,则柯里化形式可能更好。
请注意,必须小心确保您实际上从潜在的共享中获得了好处;您需要明确定义任何共享的内部映射,并在构造完整映射时重复使用单个值。
编辑:Tikhon Jelvis 在评论中指出,我没有考虑到元组构造函数的内存开销。当然,柯里化形式也有一些开销,但这种开销与有多少个不同的 a
值成比例。另一方面,非柯里化形式中的元组构造开销与总键数成比例。
因此,如果平均而言,对于任何给定的 a
值,有三个或更多不同的键使用它,则使用柯里化版本可能会节省内存。当然,不平衡树的问题仍然存在。我越想越觉得柯里化形式是无可争议的更好,除非您的键非常稀疏且分布不均匀。
请注意,因为 GHC 对定义的元数很敏感,因此在定义函数时需要注意同样的问题,如果您希望子表达式被共享。这就是为什么有时会看到像这样定义函数的方式之一:
foo x = go
where z = expensiveComputation x
go y = doStuff y z
a
键会多使用 6 个单词,而非柯里化版本每个 a, b
对将要使用 3 个单词来存储元组。如果你没有太多的 a
,我认为柯里化的版本可能更加*高效地利用内存。 - Tikhon JelvisMap
键部分有所欠缺。当前形式使得嵌套map比包含map中作为键本来应该的要更懒,这既是好事也是坏事。如果您对包含的maps进行了大量编辑而没有强制执行它们,则在柯里化的情况下可能会泄漏更多的内存,但在未柯里化的形式中,则必须支付不必要的元组成本,并且无法像效率那么高地查询柯里化的子树。我倾向于柯里化map,特别是当我想利用外部map存在和嵌套空map时。 - Edward Kmett(,)
会在插入空 Map
时被强制执行。 - Edward Kmetttype 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") ]
data QueryResult = PersonUnknown | PropertyUnknownForPerson | Value String
query :: Person -> Property -> Map (Person, Property) String -> QueryResult
在柯里化版本中写起来很困难(甚至是不可能的),但在非柯里化版本中很容易。
Map (a, b) c
可能更加节省内存(也可能更快)。如果有一种方法(我不确定,因为我很少使用 maps)可以在前缀键范围上进行折叠,那么你仍然可以有效地使用这种表示来执行柯里化应用。 - user1020786