我试图理解haskell-skiplist 的内部代码。
作者定义了以下代数数据类型和一些相关函数——其细节并不重要。
data Node k v
= Node { nKey :: k
, nValue :: v
, nSibling :: Node k v
, nChild :: Node k v }
| Head { nSibling :: Node k v
, nChild :: Node k v }
| Nil
nodeLTKey :: forall k v. Ord k => Node k v -> k -> Bool
nodeEQKey :: forall k v. Eq k => Node k v -> k -> Bool
sibling :: forall k v. Node k v -> Node k v
稍后在insert
第 113 行附近的函数中,他们定义了一个类型为 的辅助函数Node k v -> (Node k v, Int)
。我把我困惑的部分摘录在下面。
go Nil = (Nil, wins)
go n
| nodeLTKey sN k = (n {nSibling = fst $ go sN}, -1)
| nodeEQKey sN k = (n {nSibling = sN {nValue = v}}, -1)
-- how can they update the inner record?
where
sN = sibling n
-- How can the compiler be sure that sN is a Node and not Head / Nil?
该sibling
函数返回 aNode k v
并且只有 a 的可能变体之一Node k v
实际上具有名为 的字段nValue
。我不明白第二个后卫如何确定sN
将有一个nValue
字段 - 即,sN
将是这种Node
情况而不是Head
/ Nil
。
仅从发布的类型和代码来看,确实不能保证构造函数
sN
的类型和代码Node
,并且代码可能在运行时失败。但是,如果我们查看您链接但未复制的上下文,则nodeEQKey
定义为仅对于Node
字段与参数匹配的对象返回 TruenodeEQKey
。所以第二个方程可以安全地更新它的nValue
,因为它的主体只有在它的守卫匹配时才会被执行,而只有当sN
是Node
with时才会发生nKey == k
。编译器不保证该节点具有正确的形状;检查发生在运行时。但如果您打开
-Wall
,您将收到不完整模式匹配的警告,这可能会在运行时失败。