* 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

+ Recent posts