我有一个规范,我试图定义一个LRU Cache系统,我遇到的一个问题是如何从结构键/值配对中删除值(基本上是字典或哈希-map用其他语言)。
到目前为止,这是规范本身(不完整):
EXTENDS Integers, Sequences
VARIABLES capacity, currentSize, queue, dictionary
Init == /\ (capacity = 3 ) /\ (currentSize = 0)
/\ (queue = <<>>) /\ (dictionary = [])
AddItem(Item, Value) == IF currentSize < capacity
THEN /\ currentSize' = currentSize + 1
/\ queue' = Append(queue, Item)
/\ dictionary[item] = value
ELSE /\ queue' = Append(Tail(queue), Item)
/\ dictionary' = [x \in dictionary: x /= queue[3]]
GetItem(Item) == dictionary[item]
Next == \/ AddItem
\/ GetItem
我正在引用this documentation on the Learn TLA Plus网站,但似乎没有从列表中删除键值对。到目前为止,我唯一能想到的就是过滤掉与键匹配的值并创建一个新的字典对象,但我更喜欢一种更直接访问的方法。
答案 0 :(得分:3)
在我回答之前,我不得不问另一个问题:'删除一个值'是什么意思?请记住,TLA +不是一种编程语言:它是一种规范语言。这意味着它建立在对您正在做的事情的非常清楚的理解的基础上。我们来谈谈删除。
TLA +中唯一两个复杂的集合是集合和函数。函数将一些元素集(域)映射到值。结构和序列只是函数的语法糖:结构的域是它的固定键,而序列的域是1..n
,n \in Nat
。功能需要有一个域。如果要从结构中“删除”某个键,则需要从结构域中“删除”它。
相应的动作是序列的尾部。 Lamport在Specifying Systems的第341页定义了它,它包含在TLA +工具箱中。这是定义(来自标准模块Sequences.tla
- 在链接版本中稍作修改):
Tail(seq) == [i \in 1 .. (Len(seq) - 1) |-> seq[i + 1]]
换句话说,序列的尾部是通过将序列偏移量构造为1来定义的,最后一个元素被移除。从函数域中删除某些东西是通过构造一个相同的函数来完成的,该函数在其域中没有一个元素。当你想到它时,这是有道理的:我们不能改变数学函数,而不是改变数字7。
然后,我们需要在现有的基础上构建新的函数,以便TLA +添加一些方便的语法。要更改函数中的单个映射,我们有EXCEPT
。为了添加新映射,TLC模块添加了@@
运算符。删除映射通常不是人们所做的事情,因此我们必须自己定义。你必须做类似
dictionary' = [x \in DOMAIN dictionary \ {item} |-> dictionary[x]]
请注意,添加到字典的方式是错误的:dictionary[item] = value
是一个等式检查,而不是一个赋值。 dictionary'[item] = value
也不起作用,因为它没有完全指定dictionary
。你必须做类似
dictionary' = [x \in {item} |-> value] @@ dictionary
(或使用:>
,也在TLC模块中)
在这一点上,它可能会像我们走错路一样,可能有一种更简单的方法来指定一个不断变化的字典。我猜你的规范不依赖于你的密钥的一些实现细节:如果你使用字符串而不是整数作为键,你不希望你的缓存改变行为。在这种情况下,我会指定一组任意键和值,这样我们就可以定义这样的突变:
CONSTANTS Keys, Values, NULL
VARIABLE dict \* [key \in Keys |-> NULL]
Add(dict, key, val) == [dict EXCEPT ![key] = val]
Del(dict, key, val) == [dict EXCEPT ![key] = NULL]
dict[key] = NULL
表示该密钥不在字典中。
这通常是我为初学者推荐PlusCal的原因之一,因为在学习规范基础知识的同时,您不必担心如何改变函数。如果你正在编写一个pluscal算法,你可以用dict[key] := val
变异dict。
答案 1 :(得分:1)
添加到@ Hovercouch的答案:&#34;删除&#34;与编程语言不同,它与TLA +规范的含义不符。
将dictionary
声明为VARIABLE
表示dictionary
是不带参数(nullary)的运算符的标识符,并且可以更改行为的值(& #34;随着时间的推移&#34;)。它没有说什么。
查看步骤(行为中的连续状态对),dictionary
(在第一个状态中)和dictionary'
(在第二个状态中为dictionary
)的值是无关的[1,p.313]。只有通过规范中表达的约束才能限制它们的值。
如果处于州dictionary = [x \in {"foo", "bar"} |-> x]
,则dictionary'
可以任何(ZF中的任何值,即任何集合)。见[1,Sec。第72页第6.5节&#34;不要......&#34;在第80页,第139--140页。
状态谓词的值
dictionary["foo"] = "foo"
在这种状态下是TRUE
,因为确实dictionary
将值"foo"
映射到值"foo"
。相反,状态谓词:
dictionary["foo"] = "bar"
是FALSE
,因为"foo" # "bar"
。如果在第一个状态的步骤中,我们写(参见[1,p.82]):
(dictionary["foo"] = "foo")'
我们只是说 next 状态中的表达式dictionary["foo"]
等于"foo"
。我们还没有说dictionary
是该状态下的函数 - 只是它是dictionary["foo"]
碰巧等于"foo"
的值。也许dictionary
不是那里的一个功能。
我们何时拨打给定值f
a&#34;功能&#34;?答案是当f
碰巧满足公式IsAFunction(f)
时,其中运算符IsAFunction
被定义为[1,p.303]:
IsAFunction(g) == g = [x \in DOMAIN g |-> g[x]]
元组(&#34;数组&#34;)是针对某些1..n
的域n \in Nat
的函数。在TLA +中,函数只是具有上述属性的值。由于ZF中的任何值都是集合,因此函数是集合,因为当y \in f
是函数[1,高于Sec时,我们仍然可以写f
。第43页的4.5和第30页的第3.3节]。
例如,使用定理证明器TLAPS,我们可以证明以下定理,并通过运行tlapm -v -C test.tla
使用Isabelle检查证明:
---- MODULE test ----
EXTENDS TLAPS
f == [x \in {} |-> x]
g == [x \in {1, 2} |-> 3]
THEOREM
LET
S == f \cup {g}
IN
(1 \in f) => (1 \in S)
PROOF BY Zenon
(* you can replace this with OBVIOUS *)
=====================
(模型检查器TLC枚举状态,因为我们不知道恰好是函数的集合中包含哪些元素,TLC无法评估表达式y \in f
, Error: Attempted to check if the value: ... is an element of the function ...
。语法分析器SANY确认上述模块格式正确。)
当f[x]
不是函数或f
时,我们也可以写x \notin DOMAIN f
。问题是在这些情况下f[x]
的值未指定,因此规范不应该取决于这些值是什么。
表达式dictionary = []
不是TLA +的一部分。要编写具有空域的函数(只有一个这样的函数,请参阅关于函数扩展性的公理[1,p.303]):
dictionary = [x \in {} |-> TRUE]
我会将capacity
声明为CONSTANT
,因为它旨在保持对某个行为不变(除非它会发生变化)。
此外,currentSize
并未因规格而减少,但我认为尚未添加此内容。
另外值得注意的是,dictionary
会将每个项目映射到唯一值,而queue
最终可能会包含同一项目的多个副本。不确定OP对此案的意图。
EXTENDS Integers, Sequences
CONSTANT capacity
VARIABLES currentSize, queue, dictionary
Init == /\ (capacity = 3 ) /\ (currentSize = 0)
/\ (queue = <<>>) /\ (dictionary = [x \in {} |-> TRUE])
AddItem(Item, Value) ==
IF currentSize < capacity
(* Overwrite to what value the dictionary maps
the value. *)
THEN /\ currentSize' = currentSize + 1
/\ queue' = Append(queue, Item)
/\ dictionary' =
[x \in DOMAIN dictionary |->
IF x = Item
THEN Value
ELSE dictionary[x]]
(* Remove the leading item from the dictionary's domain,
then add the given item (perhaps the same), and
map it to the given value. *)
ELSE /\ queue' = Append(Tail(queue), Item)
/\ LET
(* It can happen that OldDom = NewDom. *)
OldDom == DOMAIN queue
first == queue[1]
TailDom == OldDom \ { first }
NewDom == TailDom \cup {Item}
IN
dictionary' =
[x \in NewDom |->
IF x = Item
THEN Value
ELSE dictionary[x]]
GetItem(Item) == dictionary[item]
Next == \/ AddItem
\/ GetItem
表达式
dictionary' = [x \in DOMAIN dictionary |->
IF x = Item THEN Value ELSE dictionary[x]]
可以用[1,p.49]
代替dictionary' = [dictionary EXCEPT ![Item] = Value]
表达式
dictionary' = [x \in NewDom |->
IF x = Item THEN Value ELSE dictionary[x]]
无法替换为EXCEPT
。
[1] Leslie Lamport,"Specifying systems",Addison-Wesley,2002