* fp 는 수학적인 함수식을 원한다.
18.1 무엇이 객체를 변경 가능하게 하는가?
: 순수 함수형 객체와 변경 가능한 객체 간의 차이는 객체의 필드에 접근하거나 메소드를 호출하면 항상 동일한 결과가 나오냐에 있다.
val cs = List('a', 'b', 'c')
cs.head // cs.head는 cs를 정의한 시점부터 무조건 'a'
res0: Char = a
|
class BankAccount {
private var bal: Int = 0
def balance: Int = bal
def deposit(amount: Int) { require(amount > 0) bal += amount }
def withdraw(amount: Int): Boolean = if (amount > bal) false else { bal -= amount true } }
scala> val account = new BankAccount account: BankAccount = BankAccount@bf5bb7
scala> account deposit 100
scala> account withdraw 80 res1: Boolean = true
scala> account withdraw 80 res2: Boolean = false
|
: 변경 가능한 객체는 이전에 어떤 연산자를 실행했는가에 따라 결과가 달라진다.
: 필드에 var이 있다고 변경 가능한 객체는 아니다.
Ex. 어떤 클래스가 변경 가능한 상태를 가진 다른 객체에게 메소드 호출을 위임하기 때문에 var를 상속하거나 정의하지 않고도 변경 가능한 상태를 가질 수 있다.
: var을 포함하더라도 순수 함수일 수 도 있다.
Ex. 최적화를 위해 결과를 필드에 캐시하는 클래스
class Keyed { def computeKey: Int = ... // this will take some time ... }
class MemoKeyed extends Keyed { private var keyCache: Option[Int] = None override def computeKey: Int = { if (!keyCache.isDefined) keyCache = Some(super.computeKey) keyCache.get } }
|
18.2 재할당 가능한 변수와 프로퍼티
: 보통 재할당 가능한 변수에 대해 게터와 세터 메소드를 명시적으로 정의해 추가한다. 하지만, 스칼라는 비공개가 아닌 모든 var 멤버에 게터와 세터를 자동으로 정의해준다.
: var x의 게터는 x이고, 세터는 x_= 이다.
Ex. var hour = 12 가 있으면 'hour' 게터와 'hour_=' 세터가 생긴다. 필드에는 항상 private[this]가 붙는다.
" 두 클래스 정의는 동일하다 "
class Time { var hour = 12 var minute = 0 }
|
class Time { private[this] var h = 12 private[this] var m = 0
def hour: Int = h def hour_=(x: Int) { h = x }
def minute: Int = m def minute_=(x: Int) { m = x } } |
: 게터와 세터를 직접 정의해 변수 할당과 변수 접근 연산을 원하는 대로 변화시킬 수 있다.
=> require을 이용해 허용해서는 안 되는 값을 변수에 할당하지 못 하게
=> 어떤 변수에 대한 모든 접근을 로그로 남기기 위해
=> 변수에 이벤트를 접목해 어떤 변수를 변경할 때마다 구독을 요청한 다른 객체들에게 통지하게
class Time {
private[this] var h = 12 private[this] var m = 0
def hour: Int = h def hour_= (x: Int) { require(0 <= x && x < 24) h = x }
def minute = m def minute_= (x: Int) { require(0 <= x && x < 60) m = x } } |
: 연관된 필드 없이 게터와 세터 정의가 가능하다.
class Thermometer {
var celsius: Float = _
def fahrenheit = celsius * 9 / 5 + 32 def fahrenheit_= (f: Float) { celsius = (f - 32) * 5 / 9 } override def toString = fahrenheit +"F/"+ celsius +"C" }
scala> val t = new Thermometer t: Thermometer = 32.0F/0.0C
scala> t.celsius = 100
scala> t res3: Thermometer = 212.0F/100.0C
scala> t.fahrenheit = -40
scala> t res4: Thermometer = -40.0F/-40.0C
|
* 필드 초기화에 '=_'을 사용하면 필드에 제로를 할당(수 타입은 0, 불리언 타입은 false, 참조 타입은 null)
* 스칼라는 '=_' 초기화를 생략 불가 => "var celsius:Float" 는 초기화가 아닌 추상 변수
18.3 사례 연구 : 이산 이벤트 시뮬레이션
: 디지털 회로 시뮬레이터 예제 - 시뮬레이션 동안 실행한 동작 기록
< 디지털 회로 >
- Wire 클래스 : 디지털 회로를 구성하는 선
- def inverter(input: Wire, output: Wire) : 신호를 반전시킨다.
- def andGate(a1: Wire, a2: Wire, output: Wire) : 출력을 입력의 교집합으로 설정한다.
- def orGate(a1: Wire, a2: Wire, output: Wire) : 출력을 입력의 합집합으로 설정한다.
- 각 게이트는 시간 지연 존재 => 게이트 입력이 변한 뒤에 일정한 시간이 지나야 게이트의 출력이 변한다.
" 반가산기 구현 "
def halfAdder(a: Wire, b: Wire, s: Wire, c: Wire) { val d, e = new Wire orGate(a, b, d) andGate(a, b, c) inverter(c, e) andGate(d, e, s) }
|
" 전가산기 구현 "
def fullAdder(a: Wire, b: Wire, cin: Wire, sum: Wire, cout: Wire) { val s, c1, c2 = new Wire halfAdder(a, cin, s, c1) halfAdder(b, s, sum, c2) orGate(c1, c2, cout) }
|
< 시뮬레이션 API >
: 사용자가 정의한 액션을 구체적으로 수행한다.
abstract class Simulation {
type Action = () => Unit // 타입 멤버로써 수행할 액션 명시
case class WorkItem(time: Int, action: Action) // 지정된 시간에 실행할 필요가 있는 액션 클래스
private var curtime = 0 // 현재 시간 def currentTime: Int = curtime
private var agenda: List[WorkItem] = List() // 아직 실행되지 않은 모든 잔여 작업 항목, 실행 시간에 따라 정렬
private def insert(ag: List[WorkItem], // agenda에 작업 항목 삽입, 실행 시간에 따라 정렬해야 하는 조건 유지 item: WorkItem): List[WorkItem] = {
if (ag.isEmpty || item.time < ag.head.time) item :: ag else ag.head :: insert(ag.tail, item) }
def afterDelay(delay: Int) // delay 시간 뒤에 작업할 액션을 만들고 insert 수행
(block: => Unit) { // 이름에 의한 파라미터 val item = WorkItem(currentTime + delay, () => block) agenda = insert(agenda, item) }
private def next() { // agenda의 첫 번째 작업 액션 실행 (agenda: @unchecked) match { case item :: rest => agenda = rest curtime = item.time item.action() } }
def run() { // agenda의 모든 작업 수행(next 메소드를 통해 하나씩 순차적으로 작업 수행) afterDelay(0) { println("*** simulation started, time = "+ currentTime +" ***") } while (!agenda.isEmpty) next() } }
|
< 회로 시뮬레이션 >
abstract class BasicCircuitSimulation extends Simulation { def InverterDelay: Int // Inverter, andGate, orGate 지연 시간 추상 메소드 def AndGateDelay: Int def OrGateDelay: Int 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 (_ ()) // ' f => f() " 축약형 } def addAction(a: Action) = { // 선에 액션 추가, 액션 한 번 실행 actions = a :: actions a() } } def inverter(input: Wire, output: Wire) = { // 입력으로 받은 선에 InterverDelay 후 출력 신호를 반전시키는 액션 설치 def invertAction() { val inputSig = input.getSignal afterDelay(InverterDelay) { output setSignal !inputSig } } input addAction invertAction } def andGate // 두 입력 신호의 교집한한 결과를 AndGateDelay 후 output에 설정하는 액션 설치
(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 // 입력 신호 중 하나의 신호만 바껴도 and 재계산 a2 addAction andAction }
def orGate // 두 입력 신호의 합집합한 결과를 OrGateDelay 후 output에 설정하는 액션 설치
(o1: Wire, o2: Wire, output: Wire) { def orAction() { val o1Sig = o1.getSignal val o2Sig = o2.getSignal afterDelay(OrGateDelay) { output setSignal (o1Sig | o2Sig) } } o1 addAction orAction // 입력 신호 중 하나의 신호만 바껴도 or 재계산 o2 addAction orAction } def probe(name: String, wire: Wire) { // 선의 신호가 변할 때마다 실행해 신호 변화 감지 액션 설치 def probeAction() { println(name +" "+ currentTime + " new-value = "+ wire.getSignal) } wire addAction probeAction } }
|
< 최종 실행 >
abstract class Simulation {
type Action = () => Unit
case class WorkItem(time: Int, action: Action)
private var curtime = 0 def currentTime: Int = curtime
private var agenda: List[WorkItem] = List()
private def insert(ag: List[WorkItem], item: WorkItem): List[WorkItem] = {
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) }
private def next() { (agenda: @unchecked) match { case item :: rest => agenda = rest curtime = item.time item.action() } }
def run() { afterDelay(0) { println("*** simulation started, time = "+ currentTime +" ***") } while (!agenda.isEmpty) next() } }
abstract class BasicCircuitSimulation extends Simulation {
def InverterDelay: Int def AndGateDelay: Int def OrGateDelay: Int
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 (_ ()) }
def addAction(a: Action) = { actions = a :: actions a() } }
def inverter(input: Wire, output: Wire) = { def invertAction() { val inputSig = input.getSignal afterDelay(InverterDelay) { output setSignal !inputSig } } input addAction invertAction }
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 }
def orGate(o1: Wire, o2: Wire, output: Wire) { def orAction() { val o1Sig = o1.getSignal val o2Sig = o2.getSignal afterDelay(OrGateDelay) { output setSignal (o1Sig | o2Sig) } } o1 addAction orAction o2 addAction orAction }
def probe(name: String, wire: Wire) { def probeAction() { println(name +" "+ currentTime + " new-value = "+ wire.getSignal) } wire addAction probeAction } }
abstract class CircuitSimulation extends BasicCircuitSimulation {
def halfAdder(a: Wire, b: Wire, s: Wire, c: Wire) { val d, e = new Wire orGate(a, b, d) andGate(a, b, c) inverter(c, e) andGate(d, e, s) }
def fullAdder(a: Wire, b: Wire, cin: Wire, sum: Wire, cout: Wire) {
val s, c1, c2 = new Wire halfAdder(a, cin, s, c1) halfAdder(b, s, sum, c2) orGate(c1, c2, cout) } }
object MySimulation extends CircuitSimulation { def InverterDelay = 1 def AndGateDelay = 3 def OrGateDelay = 5 }
object main extends App{ val input1, input2, sum, carry = new MySimulation.Wire MySimulation.probe("sum", sum) MySimulation.probe("carry", carry) MySimulation.halfAdder(input1, input2, sum, carry) input1 setSignal true MySimulation.run() input2 setSignal true MySimulation.run() }
-------------------------------------------------
sum 0 new-value = false carry 0 new-value = false *** simulation started, time = 0 *** sum 8 new-value = true *** simulation started, time = 8 *** carry 11 new-value = true sum 15 new-value = false
|