Example11.3

「Example11.3」の編集履歴(バックアップ)一覧はこちら

Example11.3 - (2010/10/24 (日) 09:32:36) の最新版との変更点

追加された行は緑色になります。

削除された行は赤色になります。

#co(){ 11.3 Extended Example: Discrete Event Simulation We now discuss an example that demonstrates how assignments and higher-order functions can be combined in interesting ways. We will build a simulator for digital circuits. } ** 11.3 高度な例 : 離散イベントシミュレーション (Extended Example: Discrete Event Simulation) 代入と高階関数がどのように興味深い形で結びつくか、例をとおして考えます。ここでは、デジタル回路シミュレータを構築します。 #co(){ The example is taken from Abelson and Sussman's book [ASS96]. We augment their basic (Scheme-) code by an object-oriented structure which allows code-reuse through inheritance. The example also shows how discrete event simulation programs in general are structured and built. We start with a little language to describe digital circuits. A digital circuit is built from wires and function boxes. Wires carry signals which are transformed by function boxes. We will represent signals by the booleans true and false. } この例は Abelson と Sussman の本[ASS96]から借りました。彼らの基本的な (Scheme の) コードを、継承でコードを再利用できるオブジェクト指向によって拡張しています。この例は、一般的な離散イベントのシミュレーションプログラムが、どのように構造化され構築されるかも示します。 デジタル回路を記述する小さな言語から始めます。デジタル回路は&bold(){結線}と &bold(){function box} から構築されます。結線は信号を運び、function box は信号を変換します。信号は ture と false の真偽値で表現します。 #co(){ Basic function boxes (or: gates) are: - An inverter, which negates its signal - An and-gate, which sets its output to the conjunction of its input. - An or-gate, which sets its output to the disjunction of its input. } 次は基本的な function box です。 - &bold(){インバーター} : 信号を反転します。 - &bold(){AND ゲート} : 入力の論理積を出力に設定します。 - &bold(){OR ゲート} : 入力の論理和を出力に設定します。 #co(){ Other function boxes can be built by combining basic ones. Gates have delays, so an output of a gate will change only some time after its inputs change. } ほかの function box は、これらの組み合わせで作れます。 ゲートには&bold(){遅延}があり、ゲートの出力変化は、入力からいくらか時間が経ってから起きます。 #co(){ A Language for Digital Circuits. We describe the elements of a digital circuit by the following set of Scala classes and functions. First, there is a class Wire for wires. We can construct wires as follows. } &b(){デジタル回路用言語 } デジタル回路の要素を、次の Scala クラスと関数によって記述します。まず、結線を表す Wire クラスです。結線は次のようにして生成できます。 val a = new Wire val b = new Wire val c = new Wire #co(){ Second, there are procedures } 次に、手続きです。 def inverter(input: Wire, output: Wire) def andGate(a1: Wire, a2: Wire, output: Wire) def orGate(o1: Wire, o2: Wire, output: Wire) #co(){ which "make" the basic gates we need (as side-effects). More complicated function boxes can now be built from these. For instance, to construct a half-adder, we can define: } これらの手続きは、われわれが必要とする基本ゲートを (副作用として)「作成」します。これらを使ってもっと複雑な function box を作れます。たとえば半加算器は、次のように定義できます。 def halfAdder(a: Wire, b: Wire, s: Wire, c: Wire) { val d = new Wire val e = new Wire orGate(a, b, d) andGate(a, b, c) inverter(c, e) andGate(d, e, s) } #co(){ This abstraction can itself be used, for instance in defining a full adder: } この抽象化は、それ自体で使用できます。たとえば、全加算器の定義で def fullAdder(a: Wire, b: Wire, cin: Wire, sum: Wire, cout: Wire) { val s = new Wire val c1 = new Wire val c2 = new Wire halfAdder(a, cin, s, c1) halfAdder(b, s, sum, c2) orGate(c1, c2, cout) } #co(){ Class Wire and functions inverter, andGate, and orGate represent thus a little language in which users can define digital circuits. We now give implementations of this class and these functions, which allow one to simulate circuits. These implementations are based on a simple and general API for discrete event simulation. } このようにして、クラス Wire および関数 inverter、andGate、orGate は、ユーザーがデジタル回路を定義できる小さな言語を表現しています。では、回路をシミュレートできるように、このクラスと関数群に実装を与えましょう。実装は、離散イベントシミュレーション用のシンプルで一般的な API に基づきます。 #co(){ The Simulation API. Discrete event simulation performs user-defined actions at specified times. An action is represented as a function which takes no parameters and returns a Unit result: } &b(){シミュレーション API } 離散イベントシミュレーションは、ユーザーが定義した&bold(){アクション}を指定された&bold(){時刻}に実行します。&bold(){アクション}は、引数をとらず Unit を返す関数として表されます。 type Action = () => Unit #co(){ The time is simulated; it is not the actual "wall-clock" time. A concrete simulation will be done inside an object which inherits from the abstract Simulation class. This class has the following signature: } &bold(){時刻}はシミュレーション上のものであり、「壁時計」が指す現実の時刻ではありません。 具体的なシミュレーションは、抽象クラス Simulation を継承したオブジェクトの内部で行われます。このクラスは次のシグネチャを持ちます。 abstract class Simulation { def currentTime: Int def afterDelay(delay: Int, action: => Action) def run() } #co(){ Here, currentTime returns the current simulated time as an integer number, afterDelay schedules an action to be performed at a specified delay after currentTime, and run runs the simulation until there are no further actions to be performed. } currentTime は、現在のシミュレーション上の時刻を整数値として返します。afterDelay は、currentTime から指定された時間が経過したときにアクションが実行されるよう、スケジュールします。run は、実行するアクションがなくなるまでシミュレーションを実行します。 #co(){ The Wire Class. A wire needs to support three basic actions. - getSignal: Boolean returns the current signal on the wire. - setSignal(sig: Boolean) sets the wire's signal to sig. - addAction(p: Action) attaches the specified procedure p to the actions of the wire. All attached action procedures will be executed every time the signal of a wire changes. } &b(){結線クラス } 結線は3つの基本的なアクションをサポートする必要があります。 ・getSignal : 結線の現在の信号を返します。 ・setSignal(sig:Boolean) : 結線の信号を sig に更新します。 ・addAction(p:Action) : 指定された手続き p を結線の&bold(){アクション}に付加します。結線の信号が変更されるたびに、付加されたすべてのアクション手続きを実行します。 #co(){ Here is an implementation of the Wire class: } Wire クラスの実装例を示します。 class Wire { private var sigVal = false private var actions: List[Action] = List() def getSignal = sigVal def setSignal(s: Boolean) = if (s != sigVal) { sigVal = s actions.foreach(action => action()) } def addAction(a: Action) { actions = a :: actions; a() } } #co(){ Two private variables make up the state of a wire. The variable sigVal represents the current signal, and the variable actions represents the action procedures currently attached to the wire. } 2つのプライベート変数が結線の状態を作り上げます。変数 sigVal は結線の現在の信号を表し、変数 actions は現在結線に付加されているアクション手続きを表します。 #co(){ The Inverter Class. We implement an inverter by installing an action on its input wire, namely the action which puts the negated input signal onto the output signal. The action needs to take effect at InverterDelay simulated time units after the input changes. This suggests the following implementation:} &b(){インバータクラス } インバータ(Not 回路)を、入力の結線にアクションをインストールすることで実装します。アクションは入力信号を反転して出力信号とします。アクションが効果を現すのは、入力が変化してからシミュレーション時間 InverterDelay 経過後でなくてはなりません。以上より、次の実装が導かれます。 def inverter(input: Wire, output: Wire) { def invertAction() { val inputSig = input.getSignal afterDelay(InverterDelay) { output setSignal !inputSig } } input addAction invertAction } #co(){ The And-Gate Class. And-gates are implemented analogously to inverters. The action of an andGate is to output the conjunction of its input signals. This should happen at AndGateDelay simulated time units after any one of its two inputs changes. Hence, the following implementation: } &b(){AND ゲートクラス } AND ゲートは、インバーターの類推で実装できます。andGate のアクションは、入力信号の論理積を出力することです。それは、2つの入力のいずれかが変化してから、シミュレーション時間 AndGateDelay 経過後におきる必要があります。実装は次のようになります。 def andGate(a1: Wire, a2: Wire, output: Wire) { def andAction() { val a1Sig = a1.getSignal val a2Sig = a2.getSignal afterDelay(AndGateDelay) { output setSignal (a1Sig & a2Sig) } } a1 addAction andAction a2 addAction andAction } #co(){ Exercise 11.3.1 Write the implementation of orGate. } &b(){演習 11.3.1 } OR ゲートの実装を書きなさい #co(){ Exercise 11.3.2 Another way is to define an or-gate by a combination of inverters and and gates. Define a function orGate in terms of andGate and inverter. What is the delay time of this function? } &b(){演習 11.3.2 } OR ゲートの別の定義方法として、インバーターと AND ゲートを組み合わせる方法があります。関数 orGate を、andGate とinverter を使って定義しなさい。また、この関数の遅延(delay)時間は? #co(){ The Simulation Class. Now, we just need to implement class Simulation, and we are done. The idea is that we maintain inside a Simulation object an agenda of actions to perform. The agenda is represented as a list of pairs of actions and the times they need to be run. The agenda list is sorted, so that earlier actions come before later ones. } &b(){シミュレーションクラス } さあ、あとは Simulation クラスを実装すれば出来あがりです。考え方としては、我々が Simulation オブジェクト内部の面倒を見て、&bold(){agenda}(予定表)のアクションを実行するようにしてやります。agenda はアクションと、実行すべき時刻のペアのリストとして表現されます。agenda リストは実行すべき時刻でソートしておいて、早いアクションが先にくるようにします。 abstract class Simulation { case class WorkItem(time: Int, action: Action) private type Agenda = List[WorkItem] private var agenda: Agenda = List() #co(){ There is also a private variable curtime to keep track of the current simulated time. } また、プライベート変数 curtime によって、シミュレーション上の時刻を追跡します。 private var curtime = 0 #co(){ An application of the method afterDelay(delay, block) inserts the element WorkItem(currentTime + delay, () => block) into the agenda list at the appropriate place. } メソッド afterDelay(delay, block) を適用すると、要素 WorkItem(currentTime + delay, () => block) は agenda リストの適切な場所に挿入されます。 private def insert(ag: Agenda, item: WorkItem): Agenda = if (ag.isEmpty || item.time < ag.head.time) item :: ag else ag.head :: insert(ag.tail, item) def afterDelay(delay: Int)(block: => Unit) { val item = WorkItem(currentTime + delay, () => block) agenda = insert(agenda, item) } #co(){ An application of the run method removes successive elements from the agenda and performs their actions. It continues until the agenda is empty: } メソッド run を適用すると、agenda から要素を取り除いてそのアクションを実行する、ということが繰り返されます。それは agenda が空になるまで続きます。 private def next() { agenda match { case WorkItem(time, action) :: rest => agenda = rest; curtime = time; action() case List() => } } def run() { afterDelay(0) { println("*** simulation started ***") } while (!agenda.isEmpty) next() } #co(){ Running the Simulator. To run the simulator, we still need a way to inspect changes of signals on wires. To this purpose, we write a function probe. } &b(){シミュレータの実行 } シミュレータを走らせる前に、結線上の信号変化を追跡する方法を用意します。そのための関数 probe を書きます。 def probe(name: String, wire: Wire) { wire addAction { println(name + " " + currentTime + " new_value = " + wire.getSignal) } } #co(){ Now, to see the simulator in action, let's define four wires, and place probes on two of them: } さあ、シミュレータを動かします。まず、4つの結線を定義し、そのうちの二つに probe をセットしましょう。 scala> val input1, input2, sum, carry = new Wire scala> probe("sum", sum) sum 0 new_value = false scala> probe("carry", carry) carry 0 new_value = false #co(){ Now let's define a half-adder connecting the wires: } 半加算器の回路を定義してみましょう。 scala> halfAdder(input1, input2, sum, carry) #co(){ Finally, set one after another the signals on the two input wires to true and run the simulation. } 最後に、2つの入力結線の信号を順番に true に設定して、シミュレータを走らせましょう。 scala> input1 setSignal true; run *** simulation started *** sum 8 new_value = true scala> input2 setSignal true; run carry 11 new_value = true sum 15 new_value = false #center(){[[前ページ>Example11.2]] [[ 11 章>Chapter 11 Mutable State]] [[目次>ScalaByExample和訳]] [[次ページ>Example11.4]]} ---- #comment ***************************************** #co(){ 11.4 Summary We have seen in this chapter the constructs that let us model state in Scala - these are variables, assignments, and imperative control structures. State and Assignment complicate our mental model of computation. In particular, referential transparency is lost. On the other hand, assignment gives us new ways to formulate programs elegantly. As always, it depends on the situation whether purely functional programming or programming with assignments works best. }
#co(){ 11.3 Extended Example: Discrete Event Simulation We now discuss an example that demonstrates how assignments and higher-order functions can be combined in interesting ways. We will build a simulator for digital circuits. } #setmenu2(ex-r-menu) ** 11.3 高度な例 : 離散イベントシミュレーション(Extended Example: Discrete Event Simulation) 代入と高階関数がどのように興味深い形で結びつくか、例をとおして考えます。ここでは、デジタル回路シミュレータを構築します。 #co(){ The example is taken from Abelson and Sussman's book [ASS96]. We augment their basic (Scheme-) code by an object-oriented structure which allows code-reuse through inheritance. The example also shows how discrete event simulation programs in general are structured and built. We start with a little language to describe digital circuits. A digital circuit is built from wires and function boxes. Wires carry signals which are transformed by function boxes. We will represent signals by the booleans true and false. } この例は Abelson と Sussman の本[ASS96]から借りました。彼らの基本的な (Scheme の) コードを、継承でコードを再利用できるオブジェクト指向によって拡張しています。この例は、一般的な離散イベントのシミュレーションプログラムが、どのように構造化され構築されるかも示します。 デジタル回路を記述する小さな言語から始めます。デジタル回路は&bold(){結線}と &bold(){function box} から構築されます。結線は信号を運び、function box は信号を変換します。信号は ture と false の真偽値で表現します。 #co(){ Basic function boxes (or: gates) are: - An inverter, which negates its signal - An and-gate, which sets its output to the conjunction of its input. - An or-gate, which sets its output to the disjunction of its input. } 次は基本的な function box です。 - &bold(){インバーター} : 信号を反転します。 - &bold(){AND ゲート} : 入力の論理積を出力に設定します。 - &bold(){OR ゲート} : 入力の論理和を出力に設定します。 #co(){ Other function boxes can be built by combining basic ones. Gates have delays, so an output of a gate will change only some time after its inputs change. } ほかの function box は、これらの組み合わせで作れます。 ゲートには&bold(){遅延}があり、ゲートの出力変化は、入力からいくらか時間が経ってから起きます。 #co(){ A Language for Digital Circuits. We describe the elements of a digital circuit by the following set of Scala classes and functions. First, there is a class Wire for wires. We can construct wires as follows. } &b(){デジタル回路用言語 } デジタル回路の要素を、次の Scala クラスと関数によって記述します。まず、結線を表す Wire クラスです。結線は次のようにして生成できます。 val a = new Wire val b = new Wire val c = new Wire #co(){ Second, there are procedures } 次に、手続きです。 def inverter(input: Wire, output: Wire) def andGate(a1: Wire, a2: Wire, output: Wire) def orGate(o1: Wire, o2: Wire, output: Wire) #co(){ which "make" the basic gates we need (as side-effects). More complicated function boxes can now be built from these. For instance, to construct a half-adder, we can define: } これらの手続きは、われわれが必要とする基本ゲートを (副作用として)「作成」します。これらを使ってもっと複雑な function box を作れます。たとえば半加算器は、次のように定義できます。 def halfAdder(a: Wire, b: Wire, s: Wire, c: Wire) { val d = new Wire val e = new Wire orGate(a, b, d) andGate(a, b, c) inverter(c, e) andGate(d, e, s) } #co(){ This abstraction can itself be used, for instance in defining a full adder: } この抽象化は、それ自体で使用できます。たとえば、全加算器の定義で def fullAdder(a: Wire, b: Wire, cin: Wire, sum: Wire, cout: Wire) { val s = new Wire val c1 = new Wire val c2 = new Wire halfAdder(a, cin, s, c1) halfAdder(b, s, sum, c2) orGate(c1, c2, cout) } #co(){ Class Wire and functions inverter, andGate, and orGate represent thus a little language in which users can define digital circuits. We now give implementations of this class and these functions, which allow one to simulate circuits. These implementations are based on a simple and general API for discrete event simulation. } このようにして、クラス Wire および関数 inverter、andGate、orGate は、ユーザーがデジタル回路を定義できる小さな言語を表現しています。では、回路をシミュレートできるように、このクラスと関数群に実装を与えましょう。実装は、離散イベントシミュレーション用のシンプルで一般的な API に基づきます。 #co(){ The Simulation API. Discrete event simulation performs user-defined actions at specified times. An action is represented as a function which takes no parameters and returns a Unit result: } &b(){シミュレーション API } 離散イベントシミュレーションは、ユーザーが定義した&bold(){アクション}を指定された&bold(){時刻}に実行します。&bold(){アクション}は、パラメータをとらず Unit を返す関数として表されます。 type Action = () => Unit #co(){ The time is simulated; it is not the actual "wall-clock" time. A concrete simulation will be done inside an object which inherits from the abstract Simulation class. This class has the following signature: } &bold(){時刻}はシミュレーション上のものであり、「壁時計」が指す現実の時刻ではありません。 具体的なシミュレーションは、抽象クラス Simulation を継承したオブジェクトの内部で行われます。このクラスは次のシグネチャを持ちます。 abstract class Simulation { def currentTime: Int def afterDelay(delay: Int, action: => Action) def run() } #co(){ Here, currentTime returns the current simulated time as an integer number, afterDelay schedules an action to be performed at a specified delay after currentTime, and run runs the simulation until there are no further actions to be performed. } currentTime は、現在のシミュレーション上の時刻を整数値として返します。afterDelay は、currentTime から指定された時間が経過したときにアクションが実行されるよう、スケジュールします。run は、実行するアクションがなくなるまでシミュレーションを実行します。 #co(){ The Wire Class. A wire needs to support three basic actions. - getSignal: Boolean returns the current signal on the wire. - setSignal(sig: Boolean) sets the wire's signal to sig. - addAction(p: Action) attaches the specified procedure p to the actions of the wire. All attached action procedures will be executed every time the signal of a wire changes. } &b(){結線クラス } 結線は3つの基本的なアクションをサポートする必要があります。 ・getSignal : 結線の現在の信号を返します。 ・setSignal(sig:Boolean) : 結線の信号を sig に更新します。 ・addAction(p:Action) : 指定された手続き p を結線の&bold(){アクション}に付加します。結線の信号が変更されるたびに、付加されたすべてのアクション手続きを実行します。 #co(){ Here is an implementation of the Wire class: } Wire クラスの実装例を示します。 class Wire { private var sigVal = false private var actions: List[Action] = List() def getSignal = sigVal def setSignal(s: Boolean) = if (s != sigVal) { sigVal = s actions.foreach(action => action()) } def addAction(a: Action) { actions = a :: actions; a() } } #co(){ Two private variables make up the state of a wire. The variable sigVal represents the current signal, and the variable actions represents the action procedures currently attached to the wire. } 2つのプライベート変数が結線の状態を作り上げます。変数 sigVal は結線の現在の信号を表し、変数 actions は現在結線に付加されているアクション手続きを表します。 #co(){ The Inverter Class. We implement an inverter by installing an action on its input wire, namely the action which puts the negated input signal onto the output signal. The action needs to take effect at InverterDelay simulated time units after the input changes. This suggests the following implementation:} &b(){インバータクラス } インバータ(Not 回路)を、入力の結線にアクションをインストールすることで実装します。アクションは入力信号を反転して出力信号とします。アクションが効果を現すのは、入力が変化してからシミュレーション時間 InverterDelay 経過後でなくてはなりません。以上より、次の実装が導かれます。 def inverter(input: Wire, output: Wire) { def invertAction() { val inputSig = input.getSignal afterDelay(InverterDelay) { output setSignal !inputSig } } input addAction invertAction } #co(){ The And-Gate Class. And-gates are implemented analogously to inverters. The action of an andGate is to output the conjunction of its input signals. This should happen at AndGateDelay simulated time units after any one of its two inputs changes. Hence, the following implementation: } &b(){AND ゲートクラス } AND ゲートは、インバーターの類推で実装できます。andGate のアクションは、入力信号の論理積を出力することです。それは、2つの入力のいずれかが変化してから、シミュレーション時間 AndGateDelay 経過後におきる必要があります。実装は次のようになります。 def andGate(a1: Wire, a2: Wire, output: Wire) { def andAction() { val a1Sig = a1.getSignal val a2Sig = a2.getSignal afterDelay(AndGateDelay) { output setSignal (a1Sig & a2Sig) } } a1 addAction andAction a2 addAction andAction } #co(){ Exercise 11.3.1 Write the implementation of orGate. } &b(){演習 11.3.1 } OR ゲートの実装を書きなさい #co(){ Exercise 11.3.2 Another way is to define an or-gate by a combination of inverters and and gates. Define a function orGate in terms of andGate and inverter. What is the delay time of this function? } &b(){演習 11.3.2 } OR ゲートの別の定義方法として、インバーターと AND ゲートを組み合わせる方法があります。関数 orGate を、andGate とinverter を使って定義しなさい。また、この関数の遅延(delay)時間は? #co(){ The Simulation Class. Now, we just need to implement class Simulation, and we are done. The idea is that we maintain inside a Simulation object an agenda of actions to perform. The agenda is represented as a list of pairs of actions and the times they need to be run. The agenda list is sorted, so that earlier actions come before later ones. } &b(){シミュレーションクラス } さあ、あとは Simulation クラスを実装すれば出来あがりです。考え方としては、我々が Simulation オブジェクト内部の面倒を見て、&bold(){agenda}(予定表)のアクションを実行するようにしてやります。agenda はアクションと、実行すべき時刻のペアのリストとして表現されます。agenda リストは実行すべき時刻でソートしておいて、早いアクションが先にくるようにします。 abstract class Simulation { case class WorkItem(time: Int, action: Action) private type Agenda = List[WorkItem] private var agenda: Agenda = List() #co(){ There is also a private variable curtime to keep track of the current simulated time. } また、プライベート変数 curtime によって、シミュレーション上の時刻を追跡します。 private var curtime = 0 #co(){ An application of the method afterDelay(delay, block) inserts the element WorkItem(currentTime + delay, () => block) into the agenda list at the appropriate place. } メソッド afterDelay(delay, block) を適用すると、要素 WorkItem(currentTime + delay, () => block) は agenda リストの適切な場所に挿入されます。 private def insert(ag: Agenda, item: WorkItem): Agenda = if (ag.isEmpty || item.time < ag.head.time) item :: ag else ag.head :: insert(ag.tail, item) def afterDelay(delay: Int)(block: => Unit) { val item = WorkItem(currentTime + delay, () => block) agenda = insert(agenda, item) } #co(){ An application of the run method removes successive elements from the agenda and performs their actions. It continues until the agenda is empty: } メソッド run を適用すると、agenda から要素を取り除いてそのアクションを実行する、ということが繰り返されます。それは agenda が空になるまで続きます。 private def next() { agenda match { case WorkItem(time, action) :: rest => agenda = rest; curtime = time; action() case List() => } } def run() { afterDelay(0) { println("*** simulation started ***") } while (!agenda.isEmpty) next() } #co(){ Running the Simulator. To run the simulator, we still need a way to inspect changes of signals on wires. To this purpose, we write a function probe. } &b(){シミュレータの実行 } シミュレータを走らせる前に、結線上の信号変化を追跡する方法を用意します。そのための関数 probe を書きます。 def probe(name: String, wire: Wire) { wire addAction { println(name + " " + currentTime + " new_value = " + wire.getSignal) } } #co(){ Now, to see the simulator in action, let's define four wires, and place probes on two of them: } さあ、シミュレータを動かします。まず、4つの結線を定義し、そのうちの二つに probe をセットしましょう。 scala> val input1, input2, sum, carry = new Wire scala> probe("sum", sum) sum 0 new_value = false scala> probe("carry", carry) carry 0 new_value = false #co(){ Now let's define a half-adder connecting the wires: } 半加算器の回路を定義してみましょう。 scala> halfAdder(input1, input2, sum, carry) #co(){ Finally, set one after another the signals on the two input wires to true and run the simulation. } 最後に、2つの入力結線の信号を順番に true に設定して、シミュレータを走らせましょう。 scala> input1 setSignal true; run *** simulation started *** sum 8 new_value = true scala> input2 setSignal true; run carry 11 new_value = true sum 15 new_value = false #center(){[[前ページ>Example11.2]] [[ 11 章>Chapter 11 Mutable State]] [[目次>ScalaByExample和訳]] [[次ページ>Example11.4]]} ---- #comment

表示オプション

横に並べて表示:
変化行の前後のみ表示:
ツールボックス

下から選んでください:

新しいページを作成する
ヘルプ / FAQ もご覧ください。