轨道
/
Factor
Factor
/
练习
/
电报员的纸带
电报员的纸带

电报员的纸带

学习练习

简介

你已经在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 输入流。

1. 定义 tape

定义一个 tape 元组,让它继承 disposable,并包含一个槽位 wire,用来存放底层的输入流。

TUPLE: tape < disposable wire ;

然后编写构造函数 <tape> ( wire -- tape )。使用 new-disposable(而不是 new),这样析构框架就会把磁带登记进去;再把底层流存进 wire 槽位。

2. 混入 input-stream

把 tape 声明为一个 input-stream:

INSTANCE: tape input-stream

这样,协议默认的组合子就会分派到你的类上。

3. 读取一个摩尔斯符号

实现 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

4. 委托元素类型

实现 M: tape stream-element-type ( tape -- type ),把工作委托给线路。磁带不会改变所读元素的类型。

5. 清理

实现 M: tape dispose* ( tape -- )。框架每次 dispose 只会调用你这个方法一次;你只需要释放底层的线路。

这五部分就位后,磁带就能像普通的输入流一样工作:with-input-stream、stream-read 以及其他协议词都会自动分派到你的方法上。

通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Factor Exercism

准备好开始 电报员的纸带 了吗?

注册 Exercism,借助 47 个概念163 个练习 和真人导师指导,学习并掌握 Factor,全部免费。