Ruby 周末练习:如何用 Ruby 写出可读性高的状态机代码

HalF_taN · 2014年04月19日 · 最后由 wujian_hit 回复于 2014年05月10日 · 3631 次阅读

这几天在研究编译器,为了熟悉一下 FSA 在 C 语言中应该怎么实现,做了一些练习。我发现被很多代码规范所不齿的 goto 语句貌似在写状态机的时候非常有用,能够写出可读性很高的代码。如果不使用 goto,必然会引入一个状态标志,还有一大堆 switch/case,可读性反而下降了。

不妨想想看在 Ruby 中应该怎么实现?应该用些什么技巧才能避免一大堆判断语句呢?我对元编程理解不是很深入,没有丝毫头绪啊 😔

/*
 * init  = initial state
 * op_in = operator loaded in
 * eql   = equal sign loaded in
 *
 * |    States   |   input    |   transition to
 * |             |            |
 * |    init     |   digit    |   init
 * |    init     |   * + -    |   op_in ( with target transfer )
 * |    init     |   key_ac   |   init ( with resetting )
 * |    op_in    |   digit    |   op_in
 * |    op_in    |   * + -    |   op_in ( with calculation being done )
 * |    op_in    |   key_ac   |   init ( with resetting )
 * |    op_in    |   key_eql  |   init ( with calculation being done )
 */

/*
 * where calculation actually being done.
 * @param: [int op] loaded operator
 * @param: [int lhs] left hand side operand
 * @param: [int rhs] right hand side operand
 * @return: result of the calculation
 */
static int do_calc(int op, int lhs, int rhs);
extern int read_key();

void calculator(void)
{
    some_init_proc();
    int i;
    int lhs, rhs, *target;
    int state, op;

    lhs = rhs = 0;
    target = &lhs;
    display_num(0);

    // State INIT
    while (1) {
init:
        i = read_key();
        if (i < 10) {
            *target *= 10;
            *target += i;
            display_num(*target);
        } else if (i == KEY_AC) {
            lhs = rhs = 0;
            target = &lhs;
            display_num(0);
            goto init;
        } else if (i == '+' || i == '-' || i == '*') {
            target = &rhs;
            op = i;
            goto op_in;
        }
    }

    // State OP_IN
    while (1) {
op_in:
        i = read_key();
        if (i < 10) {
            *target *= 10;
            *target += i;
            display_num(*target);
        } else if (i == KEY_AC) {
            lhs = rhs = 0;
            target = &lhs;
            display_num(0);
            goto init;
        } else if (i == '+' || i == '-' || i == '*') {
            target = &rhs;
            lhs = do_calc(op, lhs, rhs);
            display_num(lhs);
            op = i;
            goto op_in;
        } else if (i == KEY_EQL) {
            lhs = do_calc(op, lhs, rhs);
            display_num(lhs);
            goto equ;
        }
    }

    // State EQL
    while (1) {
eql:
        i = read_key();
        if (i == KEY_AC) {
            target = &lhs;
            lhs = rhs = 0;
            op = '+';
            goto init;
        }
    }
}

int do_calc(int op, int lhs, int rhs)
{
    switch (op) {
    case '+':
        return lhs + rhs;
    case '-':
        return lhs - rhs;
    case '*':
        return lhs * rhs;
    default:
        return lhs + rhs;
    }
}

对这个不了解,不过 state_machine 的代码可能对楼主有帮助

#1 楼 @lyfi2003 我用的另外一个,当时觉得比 state_machine 简单点

https://github.com/aasm/aasm

#1 楼 @lyfi2003 #2 楼 @Victor 用 Gem 当然是可以解决这个问题,但是锻炼自己思维能力的目的就达不到了啊。 我想能不能利用sendmethod_missing,还有singleton method来实现,比如:

while has_input?
  current_state = current_state.send next_input
end

@HalF_taN 我的意思是参考下它的源代码~

#4 楼 @lyfi2003 嗯,这倒是可以。不过总有种做作业偷看答案的感觉 😁

#5 楼 @HalF_taN 先模仿,再学习,是个正常的过程:)

好像 fitter 和 goto 有点关系。。。

匿名 #8 2014年04月20日

goto 是一种continuation,而:

a continuation is an abstract representation of the control state of a computer program

continuation wiki

比较抽象化的方法是用图来代表状态和状态转移。

用 case switch 这种方法写状态机很原始。整理一张状态跳转表,查表就 ok 了。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号