channel-chatter에서 스트림을 사용해 봤고, boatswains-bilge에서 스트림을 정리했어요. 이번 연습 문제는 직접 만드는 첫 스트림이에요. 프로토콜을 구현하는 클래스로, <string-reader>에서 작동하던 with-input-stream / read1 / stream-read1 코드가 여러분의 스트림에서도 똑같이 작동하게 해줘요.
dragons-descendants에서 TUPLE: child < parent …를 이미 알고 있어요. 슬롯이 아래로 흐르는 단일 부모 계층이죠. Factor에는 클래스 그래프를 확장하는 또 다른 방법이 있어요. 바로 열린 믹스인이에요. MIXIN: m은 슬롯이 없는 클래스를 선언하고, 다른 클래스들은 나중에 INSTANCE: c m으로 그 클래스에 참여할 수 있어요. 소속 관계는 다대다라서, 하나의 클래스는 원하는 만큼 많은 믹스인의 인스턴스가 될 수 있어요.
스트림 프로토콜은 두 개의 믹스인으로 클래스가 인터페이스의 어느 쪽을 구현하는지 표시해요.
INSTANCE: my-stream input-stream ! readable
INSTANCE: my-stream output-stream ! writable
스트림은 둘 중 하나일 수도, 둘 다일 수도 있어요. 그러면 input-stream(또는 output-stream)의 제네릭 메서드가 믹스인에 참여한 모든 클래스로 디스패치돼요. 주변의 read1, stream-contents, with-input-stream 같은 것들이 여러분의 클래스로 찾아가는 방식이 바로 이거예요.
믹스인은 열려 있어요. 믹스인을 선언한 뒤에도, 심지어 다른 보캐뷸러리에서도 새 멤버를 추가할 수 있어요. 자식 클래스를 선언할 때 관계가 고정되는 TUPLE: < parent와는 대조적이죠.
프로토콜 제네릭은 io에 있어요.
stream-read1 ( stream -- elt/f )
stream-read ( n stream -- seq/f )
stream-read-unsafe ( n buf stream -- count )
stream-write1 ( elt stream -- )
stream-write ( seq stream -- )
stream-flush ( stream -- )
stream-element-type ( stream -- type )
stream-read1은 다음 바이트를 반환해요(스트림 끝에서는 f). stream-write1은 바이트 하나를 써요. stream-read-unsafe는 대부분의 입력 스트림이 제공하는 더 저수준의 일괄 읽기이고, stream-read는 그 위에 만들어져 메모리 할당을 처리해요. stream-element-type은 스트림이 바이트(+byte+)를 다루는지 문자(+character+)를 다루는지 알려줘요. 다른 스트림을 감쌀 때는, 가장 간단한 구현은 그냥 기반 스트림에 위임하는 거예요.
destructors가 담당해요스트림은 정리 가능한 자원이에요. 직접 만든 스트림은 disposable을 상속하고 dispose*를 구현해요. boatswains-bilge에서 배운 프레임워크를 스트림 클래스에 적용한 것이죠.
TUPLE: my-stream < disposable underlying ;
: <my-stream> ( underlying -- s )
my-stream new-disposable swap >>underlying ;
M: my-stream dispose* underlying>> dispose ;
이걸 갖추고 나면, channel-chatter에서 본 모든 with-… 컴비네이터가 여러분의 스트림에서 자동으로 작동해요.
화물선에서 일하는 무선 전신 기사예요. 신호는 잡음이 많은 회선을 타고 들어와요. 배의 기계 소리, 날씨 때문에 생기는 잡음, 이따금 섞여 들어오는 외국어 교신이, 실제로 듣고 있는 모스 신호와 뒤섞여 있죠. 메시지를 잡아내려면 회선을 테이프 장치에 통과시켜요. 이 장치는 유효한 모스 기호(., -, 그리고 글자 사이를 구분하는 공백)를 빼고 모든 것을 걸러내요.
할 일은 테이프를 사용자 정의 Factor 입력 스트림으로 만드는 거예요.
disposable을 확장하고, 기반 입력 스트림을 담을 wire 슬롯 하나를 가지는 tape 튜플을 정의해요.
TUPLE: tape < disposable wire ;
그다음 생성자 <tape> ( wire -- tape )를 작성해요. 소멸자 프레임워크가 테이프를 등록하도록 new가 아니라 new-disposable을 사용하고, 기반 스트림을 wire 슬롯에 저장해요.
tape가 input-stream임을 선언해요:
INSTANCE: tape input-stream
그러면 프로토콜의 기본 컴비네이터가 해당 클래스로 디스패치돼요.
M: tape stream-read1 ( tape -- elt/f )를 구현해요. 기반 스트림에서 CHAR: ., CHAR: -, CHAR: \s(공백) 중 하나를 만날 때까지 바이트를 읽어요. 그 바이트를 반환해요. 스트림이 f(스트림 끝)를 반환하면 f도 반환해요. 테이프는 거기서 끝이에요.
"x.y -ab cz" <string-reader> <tape>
[ stream-read1 ] [ stream-read1 ] [ stream-read1 ] tri
! => CHAR: . CHAR: - CHAR: \s
M: tape stream-element-type ( tape -- type )를 구현하면서 기반 스트림에 위임해요. 테이프는 읽는 원소의 종류를 바꾸지 않아요.
M: tape dispose* ( tape -- )를 구현해요. 프레임워크는 dispose마다 이 메서드를 정확히 한 번 호출해요. 기반 스트림에 dispose를 호출하기만 하면 돼요.
이 다섯 조각이 자리를 잡으면 테이프는 일반 입력 스트림처럼 동작해요. with-input-stream, stream-read, 그리고 다른 프로토콜 워드가 모두 해당 메서드로 자동으로 디스패치돼요.
Exercism에 가입하고 Factor 트랙을 개념 47개연습 문제 163개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.