TLA +:如何删除结构键/值配对?

时间:2017-11-04 20:13:41

标签: lru tla+

我有一个规范,我试图定义一个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网站,但似乎没有从列表中删除键值对。到目前为止,我唯一能想到的就是过滤掉与键匹配的值并创建一个新的字典对象,但我更喜欢一种更直接访问的方法。

2 个答案:

答案 0 :(得分:3)

在我回答之前,我不得不问另一个问题:'删除一个值'是什么意思?请记住,TLA +不是一种编程语言:它是一种规范语言。这意味着它建立在对您正在做的事情的非常清楚的理解的基础上。我们来谈谈删除。

TLA +中唯一两个复杂的集合是集合和函数。函数将一些元素集()映射到值。结构和序列只是函数的语法糖:结构的域是它的固定键,而序列的域是1..nn \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 fError: 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