你已经在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 输入流。
定义一个 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 只会调用你这个方法一次;你只需要释放底层的线路。
这五部分就位后,磁带就能像普通的输入流一样工作:with-input-stream、stream-read 以及其他协议词都会自动分派到你的方法上。