2018-03-18

Sather's iterators

Coroutines By Tevfik AKTUĞLU - Own work, CC BY-SA 4.0

Sather's way for loops is interesting: you have iterators which yield giving the control back to the caller, very much like coroutines do.

When an interator is called, it executes the statements in its body in order. If it executes a yield statement, control is returned to the caller. In this, the iterator is similar to a coroutine whose state remains persistent over multiple calls. Subsequent calls on the iterator resume execution with the statement following the yield statement.

Examples:

i ::= 0;
loop while!(i < 5);
  #OUT + i + "\n";
  i := i + 1;
end;

This one looks just like cumbersome, Sather-specific syntax for a while-loop, but the following example shows that there's something different from usual:

sum ::= 0;
loop
  sum := sum + 1.upto!(10);
end;

If you wants to repeat a block for several times:

loop 7.times!;
  #OUT + "baby cries 'No'\n";
end;

And so on.

Building your own iterator is easy and using it can make your loop expressive. Let us suppose I want to write the first 20 Fibonacci numbers; I want something like this:

  loop
    #OUT + fibonacci!(20) + "\n";
  end;

A possible code for the iterator is:

  fibonacci!(once top:INT):INT is
    n_prev ::= 0;
    n_cur ::= 1;
    cnt ::= 0;
    loop while!(cnt < top);
      yield n_cur;
      tmp ::= n_cur;
      n_cur := n_cur + n_prev;
      n_prev := tmp;
      cnt := cnt + 1;
    end;
  end;

The output is:

1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765

With builtin iterators you can compute the dot product of two vectors (arrays), a and b, like this:

r:ARRAY{FLT};
loop
  r := r.append(|a.elt! * b.elt!|);
end;

Or, more like you would do in other languages:

r ::= #ARRAY{FLT}(a.size);
loop i ::= 0.upto!(a.size-1);
  r[i] := a[i] * b[i];
end;

But what if one of a or b is shorter than the other? Because of how the example was written, the problem arises when b has less elements than a. In the implementation with the iterators there isn't any problem because the first iterator to quit will make the loop to end.

A fix makes the code look uglier:

r ::= #ARRAY{FLT}(a.size.min(b.size));
loop i ::= 0.upto!(a.size.min(b.size)-1);
  r[i] := a[i] * b[i];
end;

A reminder: if there's an idiomatic way of saying something, do not imitate how you say it in other languages.

Coroutines

Given the way Sather iterators work, I think it is easy to implement something like this example I've built on in the post Crumbs of coroutines (and others).

Twisted minds

Sather has the iterator separate! which can be used to separate elements of an array, like this (example from the tutorial):

a ::= |1,2,3|;
loop
  #OUT + ", ".separate!(a.elt!.str);
end;

It is funny to note that Python does something similar with join:

print(", ".join([str(x) for x in [1, 2, 3]]))

If we want just the string:

s = ", ".join([str(x) for x in [1,2,3]])

In Sather it looks like:

s:FSTR;
larr:ARRAY{INT} := |1,2,3|;
loop s := s + ", ".separate!(larr.elt!.str); end;

Unfortunately we can't write |1,2,3|.elt!.str, but we have to write as I did above, or inline #ARRAY{INT}(|1,2,3|).elt!.str.

No comments:

Post a Comment