面向對象軟件構造(第2版)-第6章 Abstract data types抽象數據類型 (中)

 

6.4 FORMALIZING THE SPECIFICATION

6.4 形式化規格

 

The glimpse of data abstraction presented so far is too informal to be of durable use. Consider again our staple example: a stack, as we now understand it, is defined in terms of the applicable operations; but then we need to define these operations!

到目前爲止,數據抽象的介紹過於簡略而無法有效地使用。再一次考慮我們常用的例子: 棧,正如我們現在所瞭解的那樣,它是根據應用的運算來定義的;那麼我們需要定義這些運算!

 

Informal descriptions as above (put pushes an element “on top of ” the stack, remove pops the element “last pushed” and so on) do not suffice. We need to know precisely how these operations can be used by clients, and what they will do for them.

如上的簡略描述 (put是把一個元素壓入棧頂,remove是取出最後放入的元素,等等)並不足夠。我們需要準確地知道客戶端如何使用這些運算,這些運算需要做些什麼樣的動作。

 

An abstract data type specification will provide this information. It consists of four paragraphs, explained in the next sections:

一個抽象數據類型的規格將提供這些信息。它由四段組成,下面將作出解釋:

 

• TYPES. 類型

• FUNCTIONS. 函數

• AXIOMS. 定理

• PRECONDITIONS. 前置條件

 

These paragraphs will rely on a simple mathematical notation for specifying the properties of an abstract data type (ADT for short).

這些段落將會使用一個簡單的數學符號來詳細說明抽象數據類型(縮寫爲ADT)的屬性。

 

The notation — a mathematical formalism, not to be confused with the software notation of the rest of this book even though for consistency it uses a similar syntactic style — has no name and is not a programming language; it could serve as the starting point for a formal specification language, but we shall not pursue this avenue here, being content enough to use self-explanatory conventions for the unambiguous specification of abstract data types.

符號-一個數學的形式,不要被下面的軟件符號所迷惑,儘管爲了一致性它使用了一種類似語法的風格-沒有名字,也不是程序設計語言;它用於一個正式規格語言的起草,但是在這裏,我們不將研究這些,對於明確的抽象數據類型的規格,使用清楚的約定已經能夠滿足了。

 

Specifying types

指定類型

 

The TYPES paragraph indicates the types being specified. In general, it may be convenient to specify several ADTs together, although our example has only one, STACK.

類型段敘述了被指定的類型。通常,把幾種ADT放在一起敘述會方便些,儘管我們的例子只有一個,棧STACK

 

By the way, what is a type? The answer to this question will combine all the ideas developed in the rest of this chapter; a type is a collection of objects characterized by functions, axioms and preconditions. If for the moment you just view a type as a set of objects, in the mathematical sense of the word “set” — type STACK as the set of all possible stacks, type INTEGER as the set of all possible integer values and so on — you are not guilty of any terrible misunderstanding. As you read this discussion you will be able to refine this view. In the meantime the discussion will not be too fussy about using “set” for “type” and conversely.

順便說一下,什麼是類型? 這個問題的答案將結合本章餘下部分中被介紹的所有概念;一個類型是一個對象的集合,這個對象被函數,定理和前提條件所描述。如果此刻您僅僅把一個類型視爲一組對象,在單詞“集合(set)”的數學意義上-STACK類型當作所有可能的棧的集合,INTEGER類型作爲所有的整數值的集合,諸如此類-對於任何極度地誤解,您都沒有錯。當您讀到這裏的時候,您要能夠提升這個觀點。同時,這個討論並沒有過多關注於對類型使用集合或相反的情況。

 

On one point, however, you should make sure to avoid any confusion: an abstract data type such as STACK is not an object (one particular stack) but a collection of objects (the set of all stacks). Remember what our real goal is: finding a good basis for the modules of our software systems. As was noted in the previous chapter, basing a module on one particular object — one stack, one airplane, one bank account — would not make sense. O-O design will enable us to build modules covering the properties of all stacks, all airplanes, all bank accounts — or at least of some stacks, airplanes or accounts.

然而,在一點上,您應該確保避免任何的混亂: 像是STACK這樣的抽象數據類型,不是一個對象(一個特別的棧),而是一個對象(所有棧的集合)的集合。記住我們的真正目標是: 爲我們的軟件系統模塊找到一種良好的基礎。如前一章所述, 基於一個特別的對象-一個棧,一架飛機,一個銀行賬戶-而構建模塊不再有意義。OO的設計將會使我們能夠構建模塊來包含所有的棧,所有的飛機,所有銀行賬戶的屬性-或至少一些棧,飛機或賬戶。

 

An object belonging to the set of objects described by an ADT specification is called an instance of the ADT. For example, a specific stack which satisfies the properties of the STACK abstract data type will be an instance of STACK. The notion of instance will carry over to object-oriented design and programming, where it will play an important role in explaining the run-time behavior of programs.

屬於一個ADT規格所描述的對象集合中的一個對象叫做ADT的一個實例(instance)。舉例來說,一個特定的棧,其滿足STACK抽象數據類型的屬性,它就是STACK的實例。實例的概念將會放到面向對象的設計和編程中去介紹,在那裏,它在解釋程序的運行時方面起着重要的作用。

 

The TYPES paragraph simply lists the types introduced in the specification. Here:


類型段簡單地列舉了在規格中所介紹的類型,這裏:

 

Our specification is about a single abstract data type STACK, describing stacks of objects of an arbitrary type G.

我們的規格是關於一個單一的抽象數據類型STACK,它描述了一個任意G類型對象的棧。

 

Genericity

泛型

 

In STACK [G], G denotes an arbitrary, unspecified type. G is called a formal generic parameter of the abstract data type STACK, and STACK itself is said to be a generic ADT. The mechanism permitting such parameterized specifications is known as genericity; we already encountered a similar concept in our review of package constructs.

STACK [G]裏,G表示一個任意的,未指定的類型。G被稱之爲抽象數據類型STACK泛化形式參數(formal generic parameter)STACK本身稱之爲泛化ADT。允許如此參數化規格的機制被認爲是泛型;在包結構的回顧中我們已經碰到過相似的概念。

 

It is possible to write ADT specifications without genericity, but at the price of unjustified repetition. Why have separate specifications for the types “stack of bank accounts”, “stack of integers” and so on? These specifications would be identical except where they explicitly refer to the type of the stack elements — bank accounts or integers. Writing them, and then performing the type substitutions manually, would be tedious. Reusability is desirable for specifications too — not just programs! Thanks to genericity, we can make the type parameterization explicit by choosing some arbitrary name, here G, to represent the variable type of stack elements.

編寫沒有泛型的ADT規格是有可能的,但這是以可能不正確的重複爲代價的。對於“銀行賬戶棧”,“整數棧”等等,爲什麼要不同的規格呢?除了它們所明確使用的棧元素的類型-銀行賬戶或整數-之外,這些規格都是一樣的。編寫它們,然後手動地執行類型替換,這很單調乏味。複用性對規格而言也同樣需要-不僅僅只針對程序!由於泛型,通過選擇一些任意的名字,這裏是G,我們能夠把類型明確地定義成參數,用來表示各種不同的棧元素類型。

 

As a result, an ADT such as STACK is not quite a type, but rather a type pattern; to obtain a directly usable stack type, you must obtain some element type, for example ACCOUNT, and provide it as actual generic parameter corresponding to the formal parameter G. So although STACK is by itself just a type pattern, the notation STACK [ACCOUNT] is a fully defined type. Such a type, obtained by providing actual generic parameters to a generic type, is said to be generically derived.

結果一個ADT不是一個真正的類型,就像STACK這樣而更確切地說是一個類型模式要獲得一個直接可用的棧類型, 您必須獲得一些象ACCOUNT一樣的元素類型,以符合形參G泛化實際參數(actual generic parameter)的方式提供。因此,儘管STACK本身只是一個類型模式,但符號STACK [ACCOUNT]是一個完整定義的類型。對一個泛化類型提供泛化實參,從而獲得的類型稱之爲被泛化派生(generically derived)的類型。

 

The notions just seen are applicable recursively: every type should, at least in principle, have an ADT specification, so you may view ACCOUNT as being itself an abstract data type; also, a type that you use as actual generic parameter to STACK (to produce a generically derived type) may itself be generically derived, so it is perfectly all right to use STACK [STACK [ACCOUNT]] specifying a certain abstract data type: the instances of that type are stacks, whose elements are themselves stacks; the elements of these latter stacks are bank accounts.

概念看上去有些遞歸:至少在理論上,每個類型應該有一個ADT規格,因此,您可以把ACCOUNT看成是它本身的一個抽象數據類型;同樣,一個您用於STACK (產生一個泛化派生類型)的泛化實參可以當成本身的泛化派生, 因此,用

STACK [STACK [ACCOUNT]] 來描述一個特定的抽象數據類型是非常正常的:此類型的實例是棧,棧的元素是棧自己;後面棧中的元素是銀行賬戶。

 

As this example shows, the preceding definition of “instance” needs some qualification. Strictly speaking, a particular stack is an instance not of STACK (which, as noted, is a type pattern rather than a type) but of some type generically derived from STACK, for example STACK [ACCOUNT]. It is convenient, however, to continue talking about instances of STACK and similar type patterns, with the understanding that this actually means instances of their generic derivations.

如例子所示,上述“實例”的定義需要一些限制條件。嚴格來說,一個精確的棧不但是一個STACK(如上所述,這是一個類型模式而非一個類型)的實例而且是從STACK派生出的一些泛化類型的實例,如STACK [ACCOUNT]。不管怎樣,理解這些泛化派生的實例的實際意義,對於繼續討論STACK的實例和類似的類型模式是比較有益的。

 

Similarly, it is not quite accurate to talk about STACK being an ADT: the correct term is “ADT pattern”. For simplicity, this discussion will continue omitting the word “pattern” when there is no risk of confusion.

同樣地,把STACK作爲ADT加以討論並不十分正確:正確的術語應該是“ADT模式”。爲了簡單化,在不產生混亂的風險下,我們的討論將會繼續省略“模式”。

 

The distinction will carry over to object-oriented design and programming, but there we will need to keep two separate terms:

•The basic notion will be the class; a class may have generic parameters.

•Describing actual data requires types. A non-generic class is also a type, but a generic class is only a type pattern. To obtain an actual type from a generic class, we will need to provide actual generic parameters, exactly as we derive the ADT STACK [ACCOUNT] from the ADT pattern STACK.

這個區別將會延續到面向對象的設計和編程中,在那裏我們需要保持二個獨立的術語:

·  基本的概念將會是(class);一個類可以有泛化參數。

·  描述實際數據需要類型(types)。一個非泛化的類也是一個類型,但是一個泛化的類就只是一個類型模式。要從一個泛化類中獲得一個實際的類型,我們需要提供泛化實參,正如我們從ADT模式STACK中派生STACK [ACCOUNT]一樣。

 

Later chapters will explore the notion of genericity as applied to classes, and how to combine it with the inheritance mechanism.

後面的章節將會研究應用於類的泛型概念,並研究該如何把它和繼承機制結合起來。

 

Listing the functions

列出函數

 

After the TYPES paragraph comes the FUNCTIONS paragraph, which lists the operations applicable to instances of the ADT. As announced, these operations will be the prime component of the type definition — describing its instances not by what they are but by what they have to offer.

在類型(TYPES)段之後是函數(FUNCTIONS)段,其列出了應用到ADT實例上的運算。就象所宣稱的那樣,這些運算將會是類型定義中的主要組件-不僅僅通過定義,還通過所能提供的函數來描述實例。

 

Below is the FUNCTIONS paragraph for the STACK abstract data type. If you are a software developer, you will find the style familiar: the lines of such a paragraph evoke the declarations found in typed programming languages such as Pascal or Ada. The line for new resembles a variable declaration; the others resemble routine headers.

下面是STACK抽象數據類型的函數段。如果您是一位軟件開發者,您會發現熟悉的風格:段中的每一行都調用了聲明(declarations),這些聲明就像在PascalAda這樣典型的程序設計語言中的一樣。new的那一行類似一個變量的聲明;其餘的類似於例程的頭定義。


Each line introduces a mathematical function modeling one of the operations on stacks. For example function put represents the operation that pushes an element onto the top of a stack.

每一行都介紹了一個數學函數,對應於棧上的一個運算模型。例如,函數put描繪了壓入一個元素到棧頂的運算。

 

Why functions? Most software people will not naturally think of an operation such as put as a function. When the execution of a software system applies a put operation to a stack, it will usually modify that stack by adding an element to it. As a result, in the above informal classification of commands, put was a “command” — an operation which may modify objects. (The other two categories of operations were creators and queries).

爲什麼是函數?大多數的軟件工程師都不會想當然地把put這樣的一個運算當作一個函數。當軟件系統的執行對一個棧申請一個put運算的時候,它通常會加入一個元素來修改那個棧。結果,在上述非正式的命令分類中,put是一個“命令command”-一個可以修改對象的運算。(運算的另外二個種類是創建符和查詢)。

 

An ADT specification, however, is a mathematical model, and must rely on wellunderstood mathematical techniques. In mathematics the notion of command, or more generally of changing something, does not exist as such; computing the square root of the number 2 does not modify the value of that number. A mathematical expression simply defines certain mathematical objects in terms of certain other mathematical objects: unlike the execution of software on a computer, it never changes any mathematical object.

然而,一個ADT規格就是一個數學模型,並且必須依賴於簡單易懂的數學技術。數學上的命令的概念或更通常的是計算式的方法並不是如此。計算2的平方根並不修改數值2的本身。一個數學表達式只是根據其它的數學對象來定義某個數學對象: 它從不改變任何的數學對象,這不同於在計算機上軟件的執行。

 

Yet we need a mathematical concept to model computer operations, and here the notion of function yields the closest approximation. A function is a mechanism for obtaining a certain result, belonging to a certain target set, from any possible input belonging to a certain source set. For example, if R denotes the set of real numbers, the function definition

square_plus_one: R ® R

square_plus_one (x) = x2 + 1 (for any x in R)

introduces a function square_plus_one having R as both source and target sets, and yielding as result, for any input, the square of the input plus one.

你仍然需要一個數學的概念來模擬計算機的運算,同時,這裏函數的觀念最爲接近。一個函數是一種機制,它從任何可能的輸入中獲得一個特定的結果,其中,輸入屬於一個特定的來源集合,結果屬於一個特定的目標集合。例如,如果R表示爲一個實數集合,函數定義

square_plus_one: R ® R

square_plus_one (x) = x2 + 1 (for any x in R)

則表示爲函數square_plus_one即把R作爲來源集合,也作爲目標集合,同時對於任何的輸入,產生平方並加一作爲其結果。

 

The specification of abstract data types uses exactly the same notion. Operation put, for example, is specified as put: STACK [G] ´ G ® STACK [G] which means that put will take two arguments, a STACK of instances of G and an instance of G, and yield as a result a new STACK [G]. (More formally, the source set of function put is the set STACK [G] ´ G, known as the cartesian product of STACK [G] and G; this is the set of pairs <s, x> whose first element s is in STACK [G] and whose second element x is in G.) Here is an informal illustration:

抽象數據類型的規格正是使用同樣的觀念。例如,put運算的表達式爲 put: STACK [G] ´ G ® STACK [G] 式子表明使用兩個參數,一個是G實例所用的STACK,一個是G的實例,併產生一個新的STACK [G]作爲結果。(更規範地說,函數put的來源集合是STACK [G] ´ G集合,這就是STACK [G]G笛卡兒乘積(cartesian product);這是一個<s, x>對集,它的第一個元素sSTACK [G],第二個元素xG。)下面是一個非正式的圖例:

 

 

With abstract data types, we only have functions in the mathematical sense of the term; they will produce neither side effects nor in fact changes of any kind. This is the condition that we must observe to enjoy the benefits of mathematical reasoning.

對於抽象數據類型,我們只有函數能對應與術語的數學意義;事實上,它們既沒有副作用又不生產任何性質上的改變。要想利用數學推理上的便利,這是我們一定要遵守的條件。

 

When we leave the ethereal realm of specification for the rough-and-tumble of software design and implementation, we will need to reintroduce the notion of change; because of the performance overhead, few people would accept a software execution environment where every “push” operation on a stack begins by duplicating the stack. Later we will examine the details of the transition from the change-free world of ADTs to the change-full world of software development. For the moment, since we are studying how best to specify types, the mathematical view is the appropriate one.

當我們離開虛無縹緲的規格仙境,前往軟件設計和實現的混亂地界的時候,我們需要再次提出變化的觀念;因爲性能高於一切,很少有人會接受這樣的一個軟件執行環境,在棧上的每次“入棧”運算都是從複製棧開始的。稍後我們將會分析從ADTs的無變化世界到軟件開發的充滿變化世界的轉變細節。目前,由於我們正在學習如何最佳地描述類型,數學觀點是一個適合的方法。

 

The role of the operations modeled by each of the functions in the specification of STACK is clear from the previous discussion:

STACK規格中的每一個函數所模擬運算的功能在先前的討論中已經很清楚了:

 

• Function put yields a new stack with one extra element pushed on top. The figure on the preceding page illustrates put (s, x) for a stack s and an element x.

·由於一個額外的元素壓入棧頂,函數put產生一個新的棧。上頁的圖例中描繪了棧s和元素xput (s, x)運算。

 

• Function remove yields a new stack with the top element, if any, popped; like put, this function should yield a command (an object-changing operation, typically implemented as a procedure) at design and implementation time. We will see below how to take into account the case of an empty stack, which has no top to be popped.

·如果有棧頂元素的話,彈出棧頂元素,函數remove產生一個新的棧;就象put一樣,此函數在設計和實現階段應該產生一個指令(一個改變對象的運算,就象一段程序的典型實現)。我們將在下面看到如何考慮一個空棧的情況,棧中並沒有棧頂元素可以被彈出。

 

• Function item yields the top element, if any.

·如果有棧頂元素的話,函數item得出棧頂元素。

 

• Function empty indicates whether a stack is empty; its result is a boolean value (true or false); the ADT BOOLEAN is assumed to have been defined separately.

·函數empty指出是否棧爲空棧;其結果是一個布爾值(truefalse);假定ADT BOOLEAN另外已經單獨定義過了。

 

• Function new yields an empty stack.

·函數new產生一個空棧。

 

The FUNCTIONS paragraph does not fully define these functions; it only introduces their signatures — the list of their argument and result types. The signature of put is

STACK [G] ´ G ® STACK [G]

indicating that put accepts as arguments pairs of the form <s, x> where s is an instance of STACK [G] and x is an instance of G, and yields as a result an instance of STACK [G]. In principle the target set of a function (the type that appears to the right of the arrow in signature, here STACK [G]) may itself be a cartesian product; this can be used to describe operations that return two or more results. For simplicity, however, this book will only use single-result functions.

在函數段中並沒有完整地定義這些函數;只是介紹了它們的標記式 (signatures) 它們的參數和結果類型的列表。put的標記式是

STACK [G] ´ G ® STACK [G]

表示了put接受<s, x>對的形式作爲參數,其中sSTACK [G]的實例,xG的實例,併產生STACK [G]的實例作爲輸出結果。理論上,一個函數的目標集合(在標記式中箭頭右邊顯示的類型,這裏是STACK [G])本身可以是一個笛卡爾乘積;這能夠被用來描述返回兩至三個結果的運算。然而,爲了簡單化,本書只採用單個結果的函數。

 

The signature of functions remove and item includes a crossed arrow instead of the standard arrow used by put and empty. This notation expresses that the functions are not applicable to all members of the source set; it will be explained in detail below.

函數removeitem的標記式上包含了一個十字的箭頭,取代了putempty所用的普通箭頭。此符號表示了函數不適用於所有的來源集合;詳述見後。

 

The declaration for function new appears as just

new: STACK

with no arrow in the signature. This is in fact an abbreviation for

new: ® STACK

introducing a function with no arguments. There is no need for arguments since new must always return the same result, an empty stack. So we just remove the arrow for simplicity. The result of applying the function (that is to say, the empty stack) will also be written new, an abbreviation for new ( ), meaning the result of applying new to an empty argument list.

函數new的聲明

new: STACK

在標記式上並沒有箭頭。這實際上是一個

new: ® STACK

的縮寫,表示一個無任何參數的函數。由於總是一定返回同樣的結果,一個空棧,參數就沒有必要了。因此爲了簡單化,我們去掉了箭頭。申請函數(確切地說,申請一個空棧)的式子可以寫成new,這是new ( )的縮寫,含義爲申請一個無參數列表的new

 

Function categories

函數種類

 

The operations on a type were classified informally at the beginning of this chapter into creators, queries and commands. With an ADT specification for a new type T, such as STACK [G] in the example, we can define the corresponding classification in a more rigorous way. The classification simply examines where T appears, relative to the arrow, in the signature of each function:

在本章開頭,一個類型上的運算被非正式地劃分爲創建符,查詢和命令。對於一個新類型TADT規格來說,就像例子中的STACK [G]一樣,我們可以用一種更嚴格的方式來定義相關的分類。在每個函數的標記式中,分類簡單地檢查T出現在相對箭頭的位置:

 

• A function such as new for which T appears only to the right of the arrow is a creator function. It models an operation which produces instances of T from instances of other types — or, as in the case of a constant creator function such as new, from no argument at all. (Remember that the signature of new is considered to contain an implicit arrow.)

·一個函數,如newT只出現在箭頭的右邊,這是一個創建符函數(creator function)。它模擬一個運算,從另外一個類型中產生T的實例-或者,就像new這樣的常量創建符函數,在根本沒有參數的情況下產生實例。(記住new的標記式中包含着一個隱含的箭頭。)

 

• A function such as item and empty for which T appears only on the left of the arrow is a query function. It models an operation which yields properties of instances of T, expressed in terms of instances of other types (BOOLEAN and the generic parameter G in the examples).

·一個函數,如itememptyT只出現在箭頭的左邊,這是一個查詢函數(query function)。它模擬一個運算,產生T的實例的屬性,用其它類型的實例來表達結果(在例子中爲BOOLEAN和泛化參數G)

 

• A function such as put or remove for which T appears on both sides of the arrow is a command function. It models an operation which yields new instances of T from existing instances of T (and possibly instances of other types).

·一個函數,如putremoveT出現在箭頭的兩邊,這是一個命令函數(command function)。它模擬一個運算,從已經存在的T的實例(也可能是其它類型的實例)中產生一個新的T的實例。

 

An alternative terminology calls the three categories “constructor”, “accessor” and “modifier”. The terms retained here are more directly related to the interpretation of ADT functions as models of operations on software objects, and will carry over to class features, the software counterparts of our mathematical functions.

另一組對應的術語稱這三種分類爲構造函數(constructor),存取函數(accessor)和修改函數(modifier)。但在這裏所採用的術語更直接地涉及到了,把ADT函數解釋成在軟件對象上的運算模型的過程,並且概念將延續到類的特性上,那是和這些數學函數相匹配的軟件元素。

 

The AXIOMS paragraph

定理段落

 

We have seen how to describe a data type such as STACK through the list of functions applicable to its instances. The functions are known only through their signatures.

通過實例上的函數列表,我們已經瞭解瞭如何描述一個象STACK一樣的數據類型。函數只有通過它們的標記式纔可以得知。

 

To indicate that we have a stack, and not some other data structure, the ADT specification as given so far is not enough. Any “dispenser” structure, such as a first-in first-out queue, will also satisfy it. The choice of names for the operations makes this particularly clear: we do not even have stack-specific names such as push, pop or top to fool ourselves into believing that we have defined stacks and only stacks.

要表明我們有一個棧而不是其它的什麼數據結構,到目前爲止給出的ADT規格並不足夠。任何“自動售貨機”式的結構,如先進先出隊列,也可以滿足它。運算上的命名選擇解釋得很清楚:我們甚至沒有用象pushpoptop這些棧的相關命名來讓我們自己誤認爲我們已經定義了棧,純粹的棧。

 

This is not surprising, of course, since the FUNCTIONS paragraph declared the functions (in the same way that a program unit may declare a variable) but did not fully define them. In a mathematical definition such as the earlier example

square_plus_one: R ® R

square_plus_one (x) = x2 + 1 (for any x in R)

the first line plays the role of the signature declaration, but there is also a second line which defines the function’s value. How do we achieve the same for the functions of an ADT?

由於函數(FUNCTION)段落聲明瞭函數(用了和程序單元聲明一個變量的同樣方法)但是沒有完整地定義它們,所以這當然沒什麼奇怪的。在一個如先前例子那樣的數學定義

square_plus_one: R ® R

square_plus_one (x) = x2 + 1 ( R的定義域內的任何 x)

中,第一行起了標記式聲明的作用,但是這裏有着定義了函數值的第二行。我們如何能對一個ADT的函數作同樣的事情呢?

 

Here we should not use an explicit definition in the style of the second line of square_ plus_one’s definition, because it would force us to choose a representation — and this whole discussion is intended to protect us from representation choices.

這裏,在第二行square_ plus_one的定義中,我們不應該使用一個顯定義形式,因爲這將強迫我們去選擇一個表示法-整個的討論都在讓我們脫離表示法選擇。

 

Just to make sure we understand what an explicit definition would look like, let us write one for the stack representation ARRAY_UP as sketched above. In mathematical terms, choosing ARRAY_UP means that we consider any instance of STACK as a pair <count, representation>, where representation is the array and count is the number of pushed elements. Then an explicit definition of put is (for any instance x of G):

put (<count, representation>, x) = <count + 1, representation [count+1: x]>

where the notation a [n: v] denotes the array obtained from a by changing the value of the element at index n so that it is now v, and keeping all other elements, if any, as they are.

爲確保我們瞭解一個定義的表現特徵,讓我們對於上面簡單描述過的棧表示法ARRAY_UP寫一個定義。在數學的術語中,選擇ARRAY_UP意味着我們把STACK的任何實例看作一個對<count, representation>,其中的representation是一個數組,count是棧中元素的數目。接着是一個put的精確定義(對於G的任何實例x)

put (<count, representation>, x) = <count + 1, representation [count+1: x]>

其中,符號a [n: v]表示通過改變a中索引爲n的元素的值爲v從而獲得的數組,如果數組中有其它的元素,則保持不變。

 

This definition of function put is just a mathematical version of the implementation of the put operation sketched in Pascal notation, next to representation ARRAY_UP, in the picture of possible stack representations at the beginning of this chapter.

函數put的這個定義,僅僅是在Pascal符號中簡述put運算實現的一個數學版本,在本章開頭的那幅棧表示法的圖例上,緊鄰着ARRAY_UP表示法。

 

But this is not what we want; “Free us from the yoke of representations!”, the motto of the Object Liberation Front and its military branch (the ADT brigade), is also ours.

但這並不是我們想要的;把我們從表示法的束縛中解放出來!”,這是對象解放陣線和其軍事組織(ADT) 的口號,也是我們的。

 

Because any explicit definition would force us to select a representation, we must turn to implicit definitions. We will refrain from giving the values of the functions of an ADT specification; instead we will state properties of these values — all the properties that matter, but those properties only.

因爲任何顯定義都將迫使我們選擇一個表示法,我們必須轉向隱定義(implicit definition)。我們要對給一個ADT規格的函數賦值的行爲進行剋制;取而代之的是我們要聲明這些值的屬性-所有重要的屬性,也僅限於它們。

 

The AXIOMS paragraph states these properties. For STACK it will be:

定理(AXIOMS段描述了這些屬性。對應於STACK的是:


 

The first two axioms express the basic LIFO (last-in, first-out) property of stacks. To understand them, assume we have a stack s and an instance x, and define s' to be put (s, x), that is to say the result of pushing x onto s. Adapting an earlier figure:

前兩個定理表達了棧的基本的LIFO(last-in, first-out後進先出)屬性。爲了便於理解,假設我們有一個棧s和一個實例x,同時定義s'代表put (s, x),就是把x壓入s所得的結果。和上一張圖形類似:

 


Here axiom A1 tells us that the top of s' is x, the last element that we pushed; and axiom A2 tells us that if we remove the top element from s', we get back the stack s that we had before pushing x. These two axioms provide a concise description of the fundamental property of stacks in pure mathematical terms, without any recourse to imperative reasoning or representation properties.

其中,定理A1告訴我們s'的棧頂是x,這是我們壓入的最後一個元素;定理A2告訴我們如果我們從s'移除棧頂元素,我們會得到壓入x之前的棧s。這兩個定理提供了在純數學術語中棧基本屬性的一個簡要描述,並沒有藉助於必要的推理或是表示法屬性。

 

Axioms A3 and A4 tell us when a stack is empty and when it is not: a stack resulting from the creator function new is empty; any stack resulting from pushing an element on an existing stack (empty or not) is non-empty.

定理A3A4告訴我們空棧和非空棧:一個由創建符函數new產生的棧是空的;在一個已知的棧中(空或非空)壓入一個元素,這個棧是非空的。

 

These axioms, like the others, are predicates (in the sense of logic), expressing that a certain property is always true for every possible value of s and x. Some people prefer to read A3 and A4 in the equivalent form

這些定理和其它的一樣,都是謂項(邏輯上的意義),表達了對於sx任何可能的值,一個特定的屬性總是爲真。一些人更願意通過同樣的形式來了解A3A4

under which you may also view them, informally at least, as defining function empty by induction on the size of stacks.

您也可以把它們看成,至少非正式地看成,是通過歸納棧的大小來定義函數empty

 

Two or three things we know about stacks

有關棧的幾點

 

ADT specifications are implicit. We have encountered two forms of implicitness:

ADT規格是隱含的(implicit)。我們曾遇到過隱含性的兩種形式:

 

• The ADT method defines a set of objects implicitly, through the applicable functions. This was described above as defining objects by what they have, not what they are. More precisely, the definition never implies that the operations listed are the only ones; when it comes to a representation, you will often add other operations.

·ADT方法以應用函數的方式隱含地定義了一組對象。通過它們所具有的而不是它們是什麼來定義對象,這已經在上面被描述過了。更精確的是,定義從未暗示所列出來的這些運算是唯一的;當應用到表示法的時候,你將會加入其它的運算。

 

• The functions themselves are also defined implicitly: instead of explicit definitions (such as was used for square_plus_one, and for the early attempt to define put by reference to a mathematical representation), we use axioms describing the functions’ properties. Here too there is no claim of exhaustiveness: when you eventually implement the functions, they will certainly acquire more properties.

·函數本身也被隱含地定義:我們使用定理來描述函數的特性,而不是顯定義(正如在square_plus_one上所採用的,或者是,先前通過引用一個數學表示法來定義put的例子)。這裏也沒有詳盡列出所有屬性的要求: 當你最終實現函數的時候,它們肯定會獲得更多的屬性。

 

This implicitness is a key aspect of abstract data types and, by implication, of their future counterparts in object-oriented software construction — classes. When we define an abstract data type or a class, we always talk about the type or class: we simply list the properties we know, and take these as the definition. Never do we imply that these are the only applicable properties.

隱含性是抽象數據類型的一個主要方面,同時暗示着也是在面向對象軟件構造中其將來的類似版本-類-的關鍵方面。當我們定義一個抽象數據類型或一個類的時候,我們總是就事論事:我們只是列出我們所知道的屬性,而且把它們當作定義。我們從來沒有表示這些是唯一的可適用的屬性。

 

Implicitness implies openness: it should always be possible to add new properties to an ADT or a class. The basic mechanism for performing such extensions without damaging existing uses of the original form is inheritance.

隱含性意味着開放:它應該總是能夠把新的屬性加入到ADT或是一個類中去。對於完成這樣的擴展而不損壞正在使用中的原來形式,其基本機制就是繼承。

 

The consequences of this implicit approach are far-reaching. The “supplementary topics” section at the end of this chapter will include more comments about implicitness.

隱含方式的結果有着深遠的影響。在本章末尾的“增補主題”小節中將會包括隱含性的更多解釋。

 

Partial functions

部分函數

 

The specification of any realistic example, even one as basic as stacks, is bound to encounter the problems of undefined operations: some operations are not applicable to every possible element of their source sets. Here this is the case with remove and item: you cannot pop an element from an empty stack; and an empty stack has no top.

任何現實例子的規格,即使和棧一樣的基本,也必定會遇到未定義運算的問題: 一些運算並不適用於它們源集合中的每一個可能的元素。這裏有removeitem的情況:你不能夠從一個空棧中取出一個元素;而且一個空棧也沒有棧頂。

 

The solution used in the preceding specification is to describe these functions as partial. A function from a source set X to a target set Y is partial if it is not defined for all members of X. A function which is not partial is total. A simple example of partial function in standard mathematics is inv, the inverse function on real numbers, whose value for any appropriate real number x is

在上述規格中所使用的解決方案是部分描述這些函數。一個函數,如果它的定義不是對X的所有成員有效,那麼這個函數從源集合X到目標集合Y是部分的。一個不是部分的函數就是完全函數(Total Function)。在標準數學中,一個簡單的部分函數例子是inv,即實數的倒數,對於任何可能的實數x其值是:


Because inv is not defined for x = 0, we may specify it as a partial function on R, the set of all real numbers:


因爲 inv對於x = 0沒有定義,我們可以認爲它是R的一個部分函數,R是所有實數的集合:

 

To indicate that a function may be partial, the notation uses the crossed arrow  

; the normal arrow ® will be reserved for functions which are guaranteed to be total.

爲了表示一個函數是部分的,符號使用了一個十字箭頭 ;普通的箭頭®使用於完全函數。

 

The domain of a partial function in  is the subset of X containing those elements for which the function yields a value. Here the domain of inv is R {0}, the set of real numbers other than zero.

中,部分函數的定義域(domain)X的子集,包含了能讓函數得到有效值的元素。這裏的定義域invR {0},一個除零之外的實數集合。

 

The specification of the STACK ADT applied these ideas to stacks by declaring put and item as partial functions in the FUNCTIONS paragraph, as indicated by the crossed arrow in their signatures. This raises a new problem, discussed in the next section: how to specify the domains of these functions.

通過在FUNCTIONS段聲明putitem爲部分函數,並在其標記式上顯示十字箭頭,STACK ADT的規格在棧上應用了這些思想。這引發了一個新的問題,下個小節來討論: 如何指定這些函數的定義域。

 

In some cases it may be desirable to describe put as a partial function too; this is necessary to model implementations such as ARRAY_UP and ARRAY_DOWN, which only support a finite number of consecutive put operations on any given stack. It is indeed a good exercise to adapt the specification of STACK so that it will describe bounded stacks with a finite capacity, whereas the above form does not include any such capacity restriction. This is a new use for partial functions: to reflect implementation constraints. In contrast, the need to declare item and remove as partial functions reflected an abstract property of the underlying operations, applicable to all representations.

在某些情況中, put描述成一個部分函數也是合乎需要的;要是模擬諸如ARRAY_UPARRAY_DOWN之類的實現,這是必須的,這些實現在任何指定的棧上僅僅支持有限數量的連續put運算。儘管上述的形式並不包括任何的容量限制,但是改編STACK的規格使它描述具有一個有限容量的棧,這是會一個不錯的練習。 這是一個部分函數的新用法:反映實現上的限制。與之相反的是,把itemremove聲明爲部分函數的要求反映了基本運算的一個抽象屬性,這個屬性適用於所有的表示法。

 

Preconditions

前置條件

 

Partial functions are an inescapable fact of software development life, merely reflecting the observation that not every operation is applicable to every object. But they are also a potential source of errors: if f is a partial function from X to Y, we are not sure any more that the expression f (e) makes sense even if the value of e is in X: we must be able to guarantee that the value belongs to the domain of f.

部分函數是一個軟件開發活動中的必然事實,它只不過反映了這樣的一個結論,即不是每個運算都適用於每個對象。但是它們也是一個潛在的錯誤源:如果f是一個部分函數,其定義域從XY,那麼我們不再能夠確定表達式f (e)有意義,即使e的值在X範圍內:我們必須能夠保證其值屬於f的定義域。

 

For this to be possible, any ADT specification which includes partial functions must specify the domain of each of them. This is the role of the PRECONDITIONS paragraph.

要使之可能的話,任何包含部分函數的ADT規格必須指定每一個部分函數的定義域。這就是PRECONDITIONS段的作用。

 

For STACK, the paragraph will appear as:

對於STACK,這段顯示如下:

 


where, for each function, the require clause indicates what conditions the function’s arguments must satisfy to belong to the function’s domain.

這裏,對於每一個函數,require子句指出了函數的參數必須要滿足函數定義域的條件。

 

The boolean expression which defines the domain is called the precondition of the corresponding partial function. Here the precondition of both remove and item expresses that the stack argument must be non-empty. Before the require clause comes the name of the function with dummy names for arguments (s for the stack argument in the example), so that the precondition can refer to them.

定義了定義域的布爾表達式被稱之爲相關部分函數的前置條件(precondition。這裏的removeitem表達式的前置條件是參數棧不能爲空。在require子句之前是帶有虛參數名稱的函數名(例子中的s棧參數),因此,前置條件能夠指代它們。

 

Mathematically, the precondition of a function f is the characteristic function of the domain of f. The characteristic function of a subset A of a set X is the total function ch: X ® BOOLEAN such that ch (x) is true if x belongs to A, false otherwise.

精確地說,一個函數f前置條件是f定義域的特徵函數(characteristic function)。集合X的一個子集A的特徵函數是完全函數ch: X ® BOOLEAN,因此,如果x屬於A,那麼ch (x)爲真,反之爲假。

 

The complete specification

完整的規格

 

The PRECONDITIONS paragraph concludes this simple specification of the STACK abstract data type. For ease of reference it is useful to piece together the various components of the specification, seen separately above. Here is the full specification:

PRECONDITIONS段是STACK抽象數據類型的簡單規格的最後一段。爲了便於引用,把上面我們所得到的各個單獨的規格部分合並起來,便得到了這個完整的規格:

 


 


Nothing but the truth

現實性

 

The power of abstract data type specifications comes from their ability to capture the essential properties of data structures without overspecifying. The stack specification collected on the preceding page expresses all there is to know about the notion of stack in general, excluding anything that only applies to some particular representations of stacks. All the truth about stacks; yet nothing but the truth.

抽象數據類型的力量來自於這樣的一種能力,它們能捕捉數據結構的基本屬性,卻並不冗餘。上面所收集的棧規格使我們瞭解了棧的普遍概念,而排除了應用在一些特別的棧表示法上的特殊性。所有這些都是棧的現實性。

 

This provides a general model of computation with data structures. We may describe complex sequences of operations by mathematical expressions enjoying the usual properties of algebra; and we may view the process of carrying out the computation (executing the program) as a case of algebraic simplification.

這裏提供了一個數據結構計算的通用模型。通過數學表達式並使用代數的一般性質,我們可以描述複雜的運算次序;並且,作爲一個代數簡化的例子,我們可以看到完成計算(執行程序)的過程。

 

In elementary mathematics we have been taught to take an expression such as

cos2 (a – b) + sin2 (a + b – 2 ´ b)

and apply the rules of algebra and trigonometry to simplify it. A rule of algebra tells us that we may simplify a + b – 2 ´ b into a – b for any a and b; and a rule of trigonometry tells us that we can simplify cos2 (x) + sin2 (x) into 1 for any x. Such rules may be combined; for example the combination of the two preceding rules allow us to simplify the above expression into just 1.

在初等數學中,我們曾學過使用下列的表達式cos2 (a – b) + sin2 (a + b – 2 ´ b)

,並運用代數和三角函數的定理來簡化。代數定理告訴我們,對於任意的ab我們可以把a + b – 2 ´ b簡化成a – b;從三角函數定理得之,對於任意的xcos2 (x) + sin2 (x)等於1。這樣的定理可以組合使用;例如,結合這兩個定理來簡化上述表達式,我們得到1

 

In a similar way, the functions defined in an abstract data type specification allow us to construct possibly complex expressions; and the axioms of the ADT allow us to simplify such expressions to yield a simpler result. A complex stack expression is the mathematical equivalent of a program; the simplification process is the mathematical equivalent of a computation, that is to say, of executing such a program.

同樣的,在一個抽象數據類型規格中定義的函數允許我們建立或許很複雜的表達式;ADT的定理讓我們能夠簡化這類表達式以產生一個更爲簡單的結果。一個複雜的棧表達式相當於一個程序上的數學題;簡化過程就是一個計算過程的數學解題步驟,更確切地說是一個程序執行過程的數學解題步驟。

 

Here is an example. With the specification of the STACK abstract data type as given above, we can write the expression

這裏有一個例子。對於上面給出的STACK抽象數據類型規格,我們可以寫出下列表達式

item (remove (put (remove (put (put (

remove (put (put (put (new, x1), x2), x3)),

item (remove (put (put (new, x4), x5)))), x6)), x7)))

 

Let us call this expression stackexp for future reference. It is perhaps easier to understand stackexp if we define it in terms of a sequence of auxiliary expressions:

爲了便於下面引用,讓我們稱此表達式爲stackexp。如果我們利用輔助表達的方式,這也許能更容易地理解stackexp

 

s1 = new

s2 = put (put (put (s1, x1), x2), x3)

s3 = remove (s2)

s4 = new

s5 = put (put (s4, x4), x5)

s6 = remove (s5)

y1 = item (s6)

s7 = put (s3, y1)

s8 = put (s7, x6)

s9 = remove (s8)

s10 = put (s9, x7)

s11 = remove (s10)

stackexp = item (s11)

 

Whichever variant of the definition you choose, it is not hard to follow the computation of which stackexp is a mathematical model: create a new stack; push elements x1, x2, x3, in this order, on top of it; remove the last pushed element (x3), calling s3 the resulting stack; create another empty stack; and so on. Or you can think of it graphically:

不論您選擇什麼樣的定義變體,理解stackexp這樣的一個數學模型的運算並不困難:  創建一個新的棧;把x1, x2, x3順序地壓入棧頂;移去最後一個壓入棧的元素(x3),這樣得到的棧稱之s3;創建另外一個空棧;等等。或者,您可以把它想象成圖表:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


You can easily find the value of such an ADT expression by drawing figures such as the above. (Here you would find x4.) But the theory enables you to obtain this result formally, without any need for pictures: just apply the axioms repeatedly to simplify the expression until you cannot simplify any further. For example:

通過繪製類似上述的圖表,您可以輕鬆地得到這樣一個ADT表達式的值。(這裏您可以得到x4。)但是,無須藉助圖形,理論就能幫助我們正式地獲得這個結果:僅僅反覆地運用定理來簡化表達式直到您不再能進一步地簡化。比如:

 

• Applying A2 to simplify s3, that is to say remove (put (put (put (s1, x1), x2), x3)), yields put (put (s1, x1), x2)). (With A2, any consecutive remove-put pair cancels out.)

·運用A2簡化s3remove (put (put (put (s1, x1), x2), x3)),產生put (put (s1, x1), x2))。(用A2消去任何連續的remove-put對。)

 

• The same axiom indicates that s6 is put (s4, x4); then we can use axiom A1 to deduce that y1, that is to say item (put (s4, x4)), is in fact x4, showing that (as illustrated by the arrow on the above figure) s7 is obtained by pushing x4 on top of s3.

·同樣的定理表示s6put (s4, x4);接着,我們能夠使用定理A1推導y1item (put (s4, x4)),實際上就是x4,並壓入x4s3棧頂得出s7(就是圖中箭頭所示)。

 

And so on. A sequence of such simplifications, carried out as simply and mechanically as the simplifications of elementary arithmetic, yields the value of the expression stackexp, which (as you are invited to check for yourself by performing the simplification process rigorously) is indeed x4.

諸如此類。這樣的簡化次序,就和小學算術一樣簡單和按部就班,得到了表達式stackexp的值x4,(您可以用這樣的簡化步驟自己試一下)。

 

This example gives a glimpse of one of the main theoretical roles of abstract data types: providing a formal model for the notion of program and program execution. This model is purely mathematical: it has none of the imperative notions of program state, variables whose values may change in time, or execution sequencing. It relies on the standard expression evaluation techniques of ordinary mathematics.

這個例子簡單地描繪了抽象數據類型的一個主要理論作用:對程序概念和程序執行提供了一個正式的模型。這個模型是純數學的:它沒有程序中的聲明,可以隨時改變值的變量,或者執行次序的必要概念。它依賴於常規數學的標準表達式的推導方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章