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
: mutable, immutable 중 어떤 것을 선택해야 할지 모르겠다면, immutable로 시작하고 나중에 필요에 따라 바꾸는 게 좋다. ( immutable이 mutable 보다 프로그램 추론이 쉽다. )
: mutable을 먼저 사용한다면 코드가 복잡하고 추론하기 어려울 때 immutable 고려( 변경 가능 컬렉션의 복사본을 언제 만들어야 할지 고민되거나, 누가 갖고 있는지나 누구에게 포함되어 있는지 많이 생각해야 한다면 )
: 컬렉션에 저장할 원소의 수가 적을 경우 immutable이 mutable보다 더 작게 메모리를 사용
Ex. 변경 가능한 빈 HashMap은 80바이트이고 원소를 하나 추가할 때마다 16바이트가 더 든다. 변경 불가능한 빈 맵은 싱글톤 객체 하나를 모든 참조가 공유하기 때문에 포인터 필드 하나만큼의 메모리만 필요하다.
: 변경 불가능한 맵/집합은 크기가 4일 때까지 단일 객체(Set1~Set4, Map1~Map4)를 사용한다. 단일 객체 크기는 16~40 바이트 정도로 변경 가능한 맵/집합보다 공간이 훨씬 작다. => 많은 컬렉션이 작은 크기라면 컬렉션을 변경 불가능하게 만드는 것이 공간을 절약하고 성능을 향상하는 중요한 선택
: 스칼라에서는 맵/집합에 데이터를 조금 저장하는 것을 추구, immutable은 값을 매번 복사 => 메모리를 많이 잡아먹음 => 별도 객체
scala> val treeSet = TreeSet(colors) :15: error: could not find implicit value for parameter ord: Ordering[List[java.lang.String]] val treeSet = TreeSet(colors) ^