여러 워드가 상태 하나를 공유해야 할 때가 있어요. 그때마다 스택 효과를 거쳐 값을 넘겨줄 필요는 없어요. Factor의 동적 변수는 SYMBOL:로 선언하는데, 각 워드가 네임스페이스에 저장된 값에 접근할 수 있게 해줘요.
SYMBOL:과 SYMBOLS:
SYMBOL: name은 변수 하나를 선언해요. SYMBOLS: a b c ;는 여러 개를 한 번에 선언해요:
USING: namespaces ;
SYMBOLS: counter total ;
set-global과 get-global은 (namespaces에 있어요) 전역 네임스페이스에서 동작해요:
set-global ( value variable -- )
get-global ( variable -- value )
change-global ( variable quot: ( old -- new ) -- )
0 counter set-global
counter get-global . ! => 0
counter [ 10 + ] change-global
counter get-global . ! => 10
모듈 수준의 상태를 다룰 때는 -global이 붙은 쪽을 쓰면 돼요.
with-variable로 스코프 바인딩하기동적 변수에는 스코프를 쓰는 두 번째 방식도 있어요. set / get / change는 (-global 접미사가 없어요) 현재 동적 스코프를 읽고 써요. 그리고 with-variable은 (namespaces에 있어요) 쿼테이션이 실행되는 동안 새로운 스코프를 만들어요:
with-variable ( value variable quot -- )
set ( value variable -- )
get ( variable -- value )
change ( variable quot: ( old -- new ) -- )
quot가 실행되는 동안 변수는 value에 바인딩돼요. quot가 끝나면 바인딩은 사라져요. 이렇게 하면 바깥에서 호출한 쪽이 값을 "주입"할 수 있고, 안쪽 코드는 모든 스택 시그니처를 거치지 않고도 그 값을 읽을 수 있어요.
SYMBOL: greeting
"Hello" greeting [
greeting get . ! => "Hello"
"Howdy" greeting set
greeting get . ! => "Howdy"
greeting [ "!" append ] change
greeting get . ! => "Howdy!"
] with-variable
greeting get-global . ! => f (the scoped binding is gone)
이 연습 문제에서는 전역 방식을 사용해요. 그래도 set/get/change가 나란히 짝을 이루는 걸 알아두면 다른 사람의 코드를 읽을 때 도움이 돼요.
동적 변수는 참조를 담고 있으니까, 그 안에 가변 컬렉션을 두고 제자리에서 갱신할 수 있어요:
SYMBOL: history
V{ } clone history set-global
"first event" history get-global push
"second event" history get-global push
history get-global .
! => V{ "first event" "second event" }
V{ … }는 리터럴 벡터예요. V{ } 리터럴은 저장하기 전에 항상 clone을 해야 해요. 리터럴은 호출 사이에 공유되거든요.
find: 첫 번째로 일치하는 요소find(sequences에 있어요)는 조건 함수가 참인 첫 번째 요소와 그 인덱스를 반환해요:
find ( seq quot -- index elt/f )
아무것도 일치하지 않으면 두 값 모두 f예요. nip으로 인덱스를 버리고 unless*를 쓰는 게 "없을 때 기본값"을 처리하는 표준 조합이에요.
reject: 일치하는 요소 버리기reject(역시 sequences에 있어요)는 filter의 반대예요. 조건 함수를 통과하지 못하는 요소를 반환해요.
지역 주민회에서 정원 텃밭의 등록 관리를 맡아 달라고 해요. 상태는 두 개의 동적 변수에 저장돼 있어요.
registrations — 현재 누군가에게 배정된 plot 튜플의 벡터예요.next-id — 다음 등록에 사용할 정수예요.plot 튜플에는 두 개의 슬롯이 있어요.
| 슬롯 | 타입 |
|---|---|
id |
정수 |
registered-to |
문자열 |
open-garden을 정의해 동적 변수를 초기화해요. registrations에는 빈 벡터를, next-id에는 1을 넣어요. 그다음 list-registrations를 정의해 현재 plot 벡터를 반환하게 해요.
open-garden
list-registrations .
! => V{ }
register를 정의해 스택에서 이름을 하나 꺼내고, 다음으로 사용할 id로 새 plot을 만들고, registrations 벡터에 추가하고, next-id를 하나 늘린 뒤, 새 plot을 반환해요.
open-garden
"Emma Balan" register .
! => T{ plot { id 1 } { registered-to "Emma Balan" } }
list-registrations .
! => V{ T{ plot { id 1 } { registered-to "Emma Balan" } } }
텃밭 id는 고유해야 하고, 등록을 해제한 뒤에도 계속 커져야 해요. next-id는 값을 재사용해서는 안 돼요.
release를 정의해 id를 받아 registrations에서 해당 항목을 제거해요. 등록되지 않은 id를 해제하면 아무 일도 일어나지 않아요.
open-garden
"Emma" register drop
1 release
list-registrations .
! => V{ }
get-registration을 정의해 id를 받아 해당하는 plot을 반환하고, 그 id를 가진 plot이 없으면 심볼 not-found를 반환해요.
open-garden
"Emma" register drop
1 get-registration .
! => T{ plot { id 1 } { registered-to "Emma" } }
7 get-registration .
! => not-found
find-by-name을 정의해 이름을 받아, 현재 그 사람에게 등록된 모든 plot의 벡터를 반환해요.
open-garden
"Emma" register drop
"Bob" register drop
"Emma" register drop
"Emma" find-by-name .
! => V{ T{ plot { id 1 } { registered-to "Emma" } }
T{ plot { id 3 } { registered-to "Emma" } } }
Exercism에 가입하고 Factor 트랙을 개념 47개연습 문제 163개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.