Programming in Scala 18장
여러가지로 바빠서 이제야 번역을 마치게 되었네요. 상태 기반 객체와 그에 대한 예제를 디지털 회로 시뮬레이션을 소개하는데, 잘 와닿지는 않습니다. 실무에서 디지털 회로를 구현할 일이 없으니깐요-_–;; 전체적으로 매끄럽진 않지만 대충 번역하고 19장으로 넘어갑니다.
18 Stateful Objects
이 장에서는 상태 기반 객체가 무엇인지 그리고 Scala에서 이를 표현하기 위한 어떤 문법을 제공하는지를 설명한다. 이 장의 두 번째 부분에서는 상태 기반 객체뿐만 아니라 디지털 회로를 정의하며 내부 DSL(domain specific language)을 만들게 되는 이산 사건 시뮬레이션 소개한다.
18.1 What makes an object stateful?
객체의 구현을 살펴보지 않아도 순수 함수형 객체와 상태 기반 객체의 근본적인 차이점을 볼 수 있다. 순수 함수형 객체의 메소드를 호출하거나 필드를 역참조할 때는 항상 같은 결과를 얻게 된다. 예를 들면, 캐릭터형 리스트에 대해
1
|
|
cs.head
를 적용하면 항상 ‘a’를 반환한다.
반면에 상태 기반 객체의 경우 메소드 호출이나 필드의 억세스 결과는 그 객체에 대해 무슨 동작이 이전에 실행되었는지에 의존한다.
상태 기반 객체의 좋은 예는 은행 계좌이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
BankAccount
클래스는 private 변수 bal
과 세 개의 public 메소드를 정의하고 있다. balance
는 현재 잔고를 반환하고, deposit은 주어진 amount
를 bal
에 더하고, withdraw
는 남은 잔고가 음수가 아닌지 확인하면서 주어진 ‘amount’를 ‘bal’에서 뺀다. withdraw
의 반환값은 요청 자금이 성공적으로 인출되었는지 아닌지를 나타내는 Boolean
이다.
BankAccount
의 내부 동작에 대해 아무 것도 몰라도 BankAccount
가 상태 기반 객체인 것은 알 수 있다.
1 2 3 4 |
|
위 두 개의 인출 인터랙션이 다른 결과를 반환하고 있다는 것을 주목하자. 첫 번째 인출 동작은 true
를 반환하지만, 두 번째 인출 동작은 false
를 반환한다. 따라서 분명하게 은행 계정은 같은 동작이 다른 시간에 다른 결과를 반환할 수 있기 때문에 가변적인 상태를 지니고 있다.
BankAccount
의 상태성(statefulness)은 var
정의를 포함하고 있기 때문에 보나마나 분명하다고 생각할 수 있다. 보통 상태와 var
는 관련되어 있지만, 일이란게 항상 그리 명확한 것은 아니다. 예를 들면, 클래스는 var
를 정의하거나 상속하지 않더라도 상태가 있을 수 있는데, 가변 상태를 지닌 다른 객체의 메소드 호출을 진행하기 때문이다. var
를 포함하는 클래스이면서 여전히 순수 함수형(purely funtional)인 역의 경우도 마찬가지로 가능하다. 한 예로는 최적화 목적을 위해 값비싼 연산의 결과를 캐시하는 클래스가 될 것이다.
값비싼 computeKey
를 지닌 다음의 최적화되지 않은 Keyed
클래스를 가정해보자.
1 2 3 4 |
|
computeKey
가 어떤 var
변수도 읽거나 쓰지 않는다고 주어지면, 캐시를 추가함으로써 좀 더 효율적인 Keyed
를 만들 수 있다.
1 2 3 4 5 6 7 |
|
MemoKeyed
를 사용하면 (캐시로 인해) 속도를 얻는 것 외에도 Keyed
클래스와 MemoKeyed
클래스 동작이 정확하게 동일하다. 결과적으로 Keyed
클래스가 순수 함수형이면, 변수를 재할당하는 MemoKeyed
도 같은 순수 함수형이다.
18.2 Reassignable variables and properties
재할당 가능한 변수에 대해 두 가지 기본적인 연산을 수행할 수 있다. 그 값을 획득하거나 혹은 새로운 값을 설정하는 것이다. Scala에서 모든 var
는 자신의 getter와 setter 메소드를 암묵적으로 정의하는 어떤 객체의 비private 멤버이다. (비 private 멤버, public이거나 protected 라는 뜻인 듯..)
그러나 이러한 getter와 setter는 Java 컨벤션과 다른 이름을 지닌다. var x
의 getter는 그냥 “x”의 이름을 가지는데, setter는 “x_=”의 이름을 가진다.
예를 들면, 클래스에 나타나는 var
정의는
1
|
|
“hour” getter를 생성하고, 이외에도 재할당 가능한 변수인 “hour_=” setter를 생성한다. 이 필드는 항상 private[this]
로 표기되는데, 이 변수를 소유하는 객체에서만 접근할 수 있다는 것을 의미한다. 반면에 getter와 setter는 원본 var
와 같은 가시성을 지닌다. var
정의가 public
이면 getter와 setter도 그러하고, protected
이면 이들도 protected
이며 나머지 경우도 이와 같다.
예를 들어, 다음의 Time
클래스를 고려해보자, 이 클래스는 hour
와 minute
를 지닌 두 개의 public var
가 정의되어 있다.
1 2 3 4 |
|
이 클래스의 구현은 아래에 기술된 클래스 정의와 정확하게 동일하다.
1 2 3 4 5 6 7 8 9 10 |
|
지역 필드 h
와 m
의 이름은 이미 사용 중인 어떤 이름과도 충돌하지 않기 위해 임의로 선정된다.
var
의 이러한 gette와 setter 확장에 대한 흥미로운 점은 var
를 정의할 것인지, getter와 setter를 직접 정의할 것인지를 선택할 수 있다는 것이다. 예를 들면, 다음의 Time
클래스는 잘못된 값으로 할당하는 hour
와 minute
을 잡아내는 요구사항을 지니고 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
변수를 setter와 getter의 쌍으로 해석하는 Scala 컨벤션은 C#의 프로퍼티와 같은 능력을 제공하는 효과가 있다. 프로퍼티는 변수의 getter와 setter에 대한 모든 억세스를 로깅하는데 사용할 수 있다. 또는 변수를 이벤트와 연동할 수 있다. 예를 들면, 변수가 수정될 때마다 어떤 구독 메소드에 통지할 수 있다. (이 예제는 35장에서 볼 수 있다.)
관련된 필드가 없는 getter와 setter를 정의하는 것도 가능하다. 다음의 Thermometer
클래스는 읽고 갱신될 수 있는 온도 변수를 은닉화한다.
1 2 3 4 5 6 7 8 9 |
|
celsius
변수는 변수의 “값 초기화”인 _
로 기술되어 기본값으로 설정된다. 좀 더 정확히는 필드 ”=_“ 초기자(initializer)는 그 필드에 제로값을 할당한다. 제로값은 필드의 타입에 좌우된다. number 타입은 0이고, boolean 타입은 false이고 참조 타입은 null이다.
Scala에서는 “=_” 초기자를 간단히 제거할 수 없다는 것을 주의해야 한다. 만약,
1
|
|
라고 작성하면, 이는 초기화되지 않은 추상 변수(abstract value)를 선언하게 된다.
celsius
변수 정의 다음에 “fahrenheit” getter와 “fahrenheit_=” setter가 정의되어 있는데, 같은 온도를 억세스하지만 화씨로 계산한다. 화씨의 현재 온도값을 저장하기 위한 별도의 필드가 없다. 대신에 화씨값의 setter와 getter 메소드는 자동으로 섭씨 온도로 바꾼다.
1 2 3 4 5 6 7 8 |
|
18.3 Case study: Discrete event simulation
이 장의 나머지는 상태기반 객체가 일급 함수와 어떻게 결합될 수 있는지 확장된 예제로 살펴본다. 우리는 디지털 회로의 시뮬레이터의 설계와 구현을 살펴볼 것이다. 첫 번째 디지털 회로를 위한 약간의 언어를 살펴보고 두 번째 간단하지만 이산(discrete) 사건 시뮬레이션을 위한 일반적인 프레임워크를 설명한다. 마지막으로 이산 시뮬레이션 프로그램이 어떻게 구성되고 빌드될 수 있는지를 살펴본다.
이 예제는 고전인 “Structure and Interpretation of Computer Programs (by Abelson and Sussman)”에서 따왔다. 다른 점은 구현 언어가 Scheme이 아니라 Scala이며 예제의 다양한 측면들이 4개의 소프트웨어 레이어로 구조화된다는 것인데, 하나는 시뮬레이션 프레임워크이고, 다른 하나는 기본 회로 시뮬레이션 패키지이고, 세번째는 사용자정의 회로 라이브러리이고, 마지막은 각각의 시뮬레이션되는 회로 자체이다. 각 레이어는 클래스로 표현되며, 좀 더 구체적이 레이어는 보다 일반적인 레이어를 상속한다.
18.4 A language for digital circuits
디지철 회로를 기술하는 작은 언어로 시작하자. 디지털 회로는 배선(wire)과 기능 박스(function box)로 만들어진다. 배선은 기능 박스에 의해 변환되는 신호를 전달한다. 신호는 boolean으로 표현되는데, true는 signal-on 상태이고 false는 signal-off 상태를 나타낸다.
다음은 3가지 기본 기능 박스 (혹은 게이트)이다.
- inverter: 시그널의 부정을 취한다.
- and-gate: 입력에 대한 논리곱을 출력으로 설정한다.
- or-gate: 입력에 대한 논리합을 출력으로 설정한다.
이런 게이트는 모든 다른 기능 박스(function box)를 만드는 데에 충분하다. 게이트에는 “delay”가 있는데, 이 때문에 게이트의 출력은 입력이 바뀌고 지연 시간이 지난 후에야 바뀌게 된다.
다음의 Scala 클래스와 함수 집합에 의해 디지털 회로의 요소들을 기술할 것이다. 먼저, 배선을 위한 Wire 클래스가 있다.
1 2 3 |
|
좀 더 간단한 같은 표현은 다음과 같다.
1
|
|
두 번째로, 필요한 기본 게이트를 ‘만드는’ 3가지 프로시져들이다.
1 2 3 |
|
함수에 주안점을 둔 Scala에 있어 흔치 않은 것은 이런 프로시져는 결과로써 구성된 게이트를 반환하는 것이 아니라 부작용(side-effect)으로써 게이트를 만든다는 것이다. 예를 들면, inverter(a, b)
의 호출은 배선 a
와 b
사이의 인버터를 둔다.
더 복잡한 기능은 기본 게이트로 만들 수 있다. 예를 들면, 다음은 반가산기를 구성한다. halfAdder
메소드는 두 개의 입력 a
와 b
를 취해 “s = (a + b) % 2”로 정의된 합계 s
를 연산하고, “c = (a + b) /2”라고 정의된 자리올림수 c
를 출력한다.
1 2 3 4 5 6 7 |
|
더 복잡한 회로를 구성하기 위해서는 halfAdder
메소드를 사용할 수 있다. 예를 들면, 다음은 전가산기를 나타낸다. 두 개의 입력 a
와 b
, 그리고 자리올림수 cin
을 취해 sum = (a + b + cin) % 2
로 정의되는 합계를 연산하고 s
그리고 cout = (a + b + cin) / 2
로 정의된 자리올림수를 출력한다.
1 2 3 4 5 6 |
|
클래스 Wire
와 함수 inverter
, andGate
그리고 orGate
는 사용자가 디지털 회로를 정의할 수 있게 되는 작은 언어를 대표한다. 이는 개별적으로 구현되어지기 보다는 호스트 언어 내에 라이브러리로 정의되는 내부 DSL(domain specific lanauge)의 좋은 예이다.
회로 DSL 구현은 아직 해결되어야할 과제가 있다. DSL로 회로를 정의하는 목적이 회로를 시뮬레이션하는 것이기 때문에 이산 사건 시뮬레이션을 위한 일반적인 API에 기반을 두고 DSL을 구현하는 것이 타당하다. 다음 두 섹션은 먼저 시뮬레이션 API와 다음으로 그 위에 회로 DSL의 구현을 제시할 것이다.
18.5 The Simulation API
시뮬레이션 API는 다음에 나타나 있다. org.stairwarybook.simulation
패키지의 Simulation
클래스로 구성된다. 구체적인 시뮬레이션 라이브러리는 이 클래스를 상속해서 도메인 특정한 기능들을 증대시킬 것이다.
이산 사건 시뮬레이션은 기술된 시간들에 사용자 정의 액션들을 수행한다. 구체적인 시뮬레이션의 하위 클래스에 의해 정의되는 액션들은 모두 공통적인 타입을 공유한다.
1
|
|
이 문장은 빈 매개변수 리스트를 취해 Unit
을 반환하는 프로시져 타입의 별칭을 Action
으로 정의한다. Action
은 Simulation
클래스의 타입 멤버이다. 이를 () => Unit
타입의 좀더 읽기 쉬운 이름으로써 생각할 수 있다. 타입 멤버는 20.6에서 자세하게 설명될 것이다.
액션이 수행될 때의 시간은 시뮬레이션되는 시간으로 실제 “wall clock” 시간과는 아무런 관련이 없다. 시뮬레이션되는 시간은 단순히 정수형으로 표시한다. 현재 시뮬레이션되는 시간은 private 변수로 유지한다.
1
|
|
이 변수는 현재 시간을 반환하는 public 접근자 메소드를 갖고 있다.
1
|
|
private 변수와 public 접근자의 조합은 Simluation 클래스 밖에서 현재 시간이 수정되지 않는 것을 보장하는데 사용된다. 결국에는 시뮬레이션 시간 여행을 모델링하는 경우를 제외하면, 보통 현재 시간을 관리하는 시뮬레이션 객체를 원하지 않는다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
특정 시간에 실행되는 액션을 작업 항목(Work Item)이라고 부른다. 작업 항목은 다음의 클래스로 구현된다.
1
|
|
WorkItem
클래스를 case
클래스로 만들었다. 이는 클래스의 인스턴스를 생성하는 WorkItem
이란 factory 메소드를 사용할 수 있고, 생성자 매개변수 time
과 action
억세스 하는 접근자를 제공하는 편리한 문법을 가져다준다.
Simulation
클래스는 아직 실행되지 않은 모든 남은 작업 항목의 목록을 지닌다. 작업 항목은 이들이 실행되는 시뮬레이션 시간에 의해 정렬된다.
1
|
|
agenda
는 이를 갱신하는 insert
메소드에 의해 적당히 정렬된 순서로 유지된다. insert
메소드가 afterDelay
에서 호출되는 것을 볼 수 있는데, 이는 agenda
에 작업 목록을 추가하는 유일한 방법이다.
1 2 3 4 |
|
이름이 함축하는 것처럼, 이 메소드는 (block에 의해 주어진) 액션을 agenda
에 추가함으로써 현재 시뮬레이션 시간 이후 delay
시간 단위에 실행하는 스케쥴을 잡게 된다. 예를 들면, 다음 호출은 currentTime + delay
시뮬레이션 시간에 실행되는 새 작업 목록을 생성한다.
1
|
|
실행되는 코드는 메소드의 두번째 인자에 포함되어 있다. 이 인자의 정식 매개변수는 “ => Unit” 타입이다. by-name 매개변수는 함수에 전달될 때 평가되지 않는다. 그래서 위의 호출에서는 count
는 시뮬레이션 프레임워크가 작업 목록에 저장된 액션을 호출할 때만 증가된다. afterDelay
는 커리 함수(curried function)임을 주목하자. 이는 9.5 섹션에서 소개된 커링(curring)이 메소드 호출을 좀 더 내장된 문법처럼 보이게 하는데 사용될 수 있다는 원리의 좋은 예이다.
생성된 작업 목록은 여전히 agenda
에 입력될 필요가 있으며, insert
메소드에 의해 이뤄지는데, 이는 agenda
가 시간순으로 정렬되는 불변성(invariant)을 지켜준다.
1 2 3 4 |
|
Simulation
클래스의 핵심은 run
메소드에 의해 정의된다.
1 2 3 4 5 6 7 |
|
이 메소드는 반복적으로 agenda
의 첫번째 항목을 가져와, 이를 agenda
에서 제거하여 실행되며 agenda
에 더 이상의 항목이 없을 때까지 이뤄진다. 각 스텝은 next
메소드 호출에 의해 수행되고, 이는 다음과 같이 정의된다.
1 2 3 4 5 6 7 8 |
|
next
메소드는 현재 목록을 패턴 매칭으로 앞 항목 item
과 작업 항목의 남은 리스트 rest
로 분할한다. 현재 목록에서 앞 항목을 제거하고 시뮬레이션 시간 curtime
을 작업 항목의 시간으로 설정하여 작업 항목의 액션을 실행한다.
agenda
가 비어 있지 않을 때만 next
가 호출될 수 있다는 것을 주목하자. 빈 리스트에 해당하는 case가 없으니 빈 agenda
에 next
를 실행하려고 하면 MatchError
예외를 얻게 된다.
Scala 컴파일러는 리스트의 가능한 패턴 중 하나를 놓친 것에 대해 경고한다.
Simulator.scala:19: warning: match is not exhaustive!
missing combination
agenda match {
^
one warning found
이럴 경우 놓친 case는 문제가 되지 않는데, 왜냐하면 next
가 오직 비어 있지 않은 agenda
에 대해서만 호출될 것을 알고 있기 때문이다. 그러므로, 이 경고를 비활성화시키고 싶을 것이다. 이는 Simulation
코드가 agenda match
가 아닌 (agenda: @unchecked) match
를 사용하는 이유이다.
18.6 Circuit Simulation
다음 스텝은 섹션 18.4에 그려진 회로를 위한 DSL을 구현하기 위해 시뮬레이션 프레임워크를 사용하는 것이다. 회로 DSL은 배선 클래스와 AND 게이트, OR 게이트, 그리고 반가산기를 생성하는 메소드로 구성된다는 것을 상기하라. 이들은 시뮬레이션 프레임워크를 확장하는 BasicCircuitSimulation
클래스에 모두 포함된다. 다음이 이 클래스이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
|
실제 지연은 이 클래스 레벨에서는 알 수 없다. 왜냐하면 이들은 시뮬레이터되는 회로에 의존하기 때문이다. 그래서 구체적인 정의는 하위 클래스에 위임하도록 지연값들이 추상 BasicCircuitSimulation
클래스 내에 선언되었다.
The Wire class
배선 클래스는 다음의 3가지 기본 액션을 지원한다.
- getSignal: Boolean 배선의 현재 신호를 반환한다.
- setSignal(sig: Boolean): 배선의 신호를 sig에 설정한다.
- addAction(p: Action): 주어진 프로시져 p를 배션의 액션에 첨부한다. 아이디어는 어떤 배선에 첨부된 모든 액션의 프로시저가 배선의 신호가 바뀌는 시간마다 실행되도록 하는 것이다. 즉, 첨부된 액션은 배선에 추가된 시간에 한번, 그 이후에는 배선의 신호가 변경될 때마다 실행된다.
두 개의 private
변수는 배선의 상태를 형성한다. sigVal
변수는 현재 신호와 actions
변수는 현재 배선에 첨부되는 액션의 프로시저를 나타낸다.
흥미로운 메소드의 구현은 setSignal
이다. 배선의 신호가 변경되었을 때 새 값을 변수 sigVal
에 저장하고 나아가 배선에 첨부된 모든 액션들이 실행된다. 이에 대한 단축 문법은 action foreach (_ ())
으로 함수 _ ()
를 action
리스트의 각 요소에 적용한다. 섹션 8.5에서 설명된 것처럼 함수 _ ()
은 f => f()
의 단축 표현으로 함수를 취해서 이를 빈 매개변수 리스트에 적용한다.
The inverter method
인버터를 생성한 결과는 액션이 자신의 입력 배선에 설치되는 것뿐이다. 이 액션은 설치되는 시점에 한번 그후에는 입력 신호가 변경될 때마다 매번 호출된다. 액션의 영향으로 인버터 입력값의 반전시킨 값이 출력값으로 설정된다. 인버터 게이트가 지연값을 가지기 때문에 이 변화값은 입력값이 바뀌어 액션이 실행돤 후 시뮬레이션 시간의 InverterDelay
만큼 영향을 받아야 한다.
1 2 3 4 5 6 7 8 9 |
|
inverter
메소드의 영향으로 input
배선에 invertAction
이 추가된다. 이 액션은 호출될 때, 입력 신호를 얻어와 시뮬레잉션 아젠다에 output
신호를 반전하는 다른 액션을 설치한다. 이 다른 액션은 시뮬레이션 시간의 InverterDay
만큼 후에 실행되는 것이다.
The andGate and orGate methods
AND 게이트 구현은 인버터 구현과 유사하다. AND 게이트 목적은 입력 신호의 합(conjunction) 을 출력하는 것이다. 이는 두 입력 중 하나의 변화 이후 시뮬레이트된 AndGateDelay
시간 단위에서 발생해야 한다. 따라서 다음과 같이 구현된다.
1 2 3 4 5 6 7 8 9 10 11 |
|
andGate
메소드의 영향으로 addAction
을 입력 배선 a1
과 a2
모두에 추가된다. 이 액션은 호출될 때, 입력 시그널 모두를 얻어와 입력 시그널의 합에 대한 output
신호를 설정하는 다른 액션을 설치한다. 이 다른 액션은 시뮬레이트 시간 AndGateDelay
단위 이후에 실행된다. 입력 배선이 달라지면 출력이 재계산되어야 한다는 것을 주의하라. 그래서 두 입력 배선 a1
과 a2
각각에 대해 andAction
이 설치된다. orGate
메소드는 논리합 대신 논리곱을 수행하는 것을 제외하면 비슷하게 구현된다.
Simulation output
시뮬레이터를 실행하려면 배선의 신호 변화를 검사하는 방법이 필요하다. 이를 해내기 위해서 배선에 조사자(probe)를 두는 액션을 시뮬에이션할 수 있다.
1 2 3 4 5 6 7 |
|
probe
프로시저의 영향으로 주어진 배선에 probeAction
이 설치된다. 늘 하던대로, 설치된 액션은 배선 신호가 변화될 때마다 실행된다. 이 경우는 단순히 배선의 이름과 마찬가지로 현재 시뮬레이션 시간 그리고 배선의 새로운 값을 출력한다.
Running the simulator
시뮬레이터를 구동해볼 시간이다. 구체적인 시뮬레이션을 정의하기 위해 시뮬레이션 프레임워크를 상속할 필요가 있다. 관심있는 것을 보기 위해, BasicCircuitSimulation
을 상속하는 추상 시뮬레이션 클래스를 생성하고 이번 장과 18.6장, 18.7장에서 살펴본 것처럼 반가산기와 전가산기를 위한 메소드 정의를 포함할 것인데, 이를 CircuitSimulation
이라 부르며 이상 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
구체적인 회로 시뮬레이션은 CircuitSimulation
클래스로부터 상속된 객체일 것이다. 그 객체는 시뮬레이트되는 회로 구현 기술에 따라 게이트 지연을 수정할 필요가 있다. 마지막으로, 시뮬레이션할 구체적인 회로를 정의할 필요가 있다. 이런 단계는 Scala 인터프리터로 대화식으로 행할 수 있다.
1
|
|
먼저, 게이트 지연이다. MySimulation
이라 부르는 객체를 정의하고 어떤 숫자를 입력하자.
1 2 3 4 5 |
|
MySimulation
객체 멤버를 반복적으로 억세스할 것이기 때문에, 객체의 import로 이후의 코드를 줄여쓴다.
scala> import MySimulation._
다음은 회로이다. 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
조사자(probe)가 즉시 출력을 찍었다는 것을 주목하라. 이는 배선에 설치된 모든 액션은 처음 액션을 설치했을 때 실행된다는 사실의 결과이다.
이제 배선을 연결하는 반가산기를 정의하자.
scala> halfAdder(input1, input2, sum, carry)
마지막으로, 하나씩, 두 입력 배선에 true
설정하고, 시뮬레이션을 실행하자.
scala> input1 setSignal true
scala> run()
*** simulation started, time = 0 ***
sum 8 new-value = true
scala> input2 setSignal true
scala> run()
*** simulation started, time = 8 ***
carry 11 new-value = true
sum 15 new-value = false
18.7 Conclusion
이 장에서는 가변 상태(mutable state)와 고차함수(higher-order function) 이질적으로 보이는 두 개의 기술을 함께 다뤘다. 가변 상태는 물리적 엔티티의 상태가 시간이 지남에 따라 변경되는 것을 시뮬레이션하는데 사용되었고 고차함수는 시뮬레이션 시간에 특정한 점에서 액션을 실행하는 시뮬레이션 프레임워크에서 사용되었다. (이하 생략)