你已經在channel-chatter裡用過串流,也在boatswains-bilge裡將它們處置掉了。這個練習是你的第一個自訂串流,一個實作了協定的類別,讓那些原本能在<string-reader>上運作的with-input-stream/read1/stream-read1程式碼,也能在你的串流上運作。
你已經從dragons-descendants認識了TUPLE: child < parent …,那是一種單一父類別的階層,槽位會往下繼承。Factor 有第二種擴充類別圖的方式:開放式 mixin。MIXIN: m會宣告一個沒有槽位的類別,其他類別之後可以用INSTANCE: c m加入。成員關係是多對多的:一個類別可以同時是任意多個 mixin 的實例。
串流協定用兩個 mixin 來標示一個類別實作了介面的哪一半:
INSTANCE: my-stream input-stream ! readable
INSTANCE: my-stream output-stream ! writable
一個串流可以是其中一個、另一個,或兩者兼具。input-stream(或output-stream)上的泛型方法接著會分派到任何加入該 mixin 的類別,這就是環境中的read1、stream-contents、with-input-stream等等找到你的類別的方式。
Mixin 是開放式的:你可以在 mixin 宣告之後才加入新成員,甚至可以在不同的詞彙表中加入。這和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:自訂串流會繼承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 的自訂輸入串流。
定義一個tape元組,繼承disposable,並帶有一個wire槽位,用來存放底層的輸入串流。
TUPLE: tape < disposable wire ;
接著撰寫建構子<tape> ( wire -- tape )。請使用new-disposable(而不是new),讓解構子框架能註冊這條磁帶,並把底層串流存進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只會呼叫你的方法一次;你只要把底層的線路處置掉就好。
這 5 個部分就定位之後,磁帶就能像一般的輸入串流一樣運作:with-input-stream、stream-read,以及其他協定詞彙都會自動分派到你的方法。