↑ Up |
A coroutine is a function that can be stopped at some state and resumed later. At its stop it yields a value.
An easy example:
g = || fn*|| yield 1 yield 2 yield 1 yield 4 end
This is used as follows:
> i = g() > i(), i(), i(), i(), i() [1, 2, 1, 4, empty] > g().list() [1, 2, 1, 4]
The following is a simple prime number test:
isprime = |n| (1..n).count(|k| n%k==0)==2
We can make it a little bit faster:
isprime = |n| n>1 and (2..).until(|k| k*k>n).all(|k| n%k!=0)
Now we want to state a function that filters elements from
an iterable object by a given predicate p
.
Iterable.filter = fn|a;p| y = [] for x in a if p(x) y.push(x) end end return y end
Let us test it out:
> (1..20).filter(isprime) [2, 3, 5, 7, 11, 13, 17, 19]
But what if we want to calculate the first 10 prime numbers? One could provide a test range with upper bound, large enough, and take 10 of them afterwards:
> (1..100).filter(isprime).list(10) [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
But the more primes we want, the larger this upper bound has to be. The problem is, that we must know how large it is in before. For prime numbers there are mathematical results. To be more precise, we would need to invert the prime number counting function π(n). This means we want to solve the equation π(n)=10. A known mathematical result in case of n≥17 is
n/ln(n) < π(n).
By transitivity of inequalities we have
a < π(n) and 10 ≤ a implies 10 < π(n)
for all a. We place in a:=n/ln(n) and obtain
10 ≤ n/ln(n) implies 10 < π(n).
The first such n is 36 with π(36)=11.
This was somewhat complicated, and there are mathematical problems that are much more complicated. In general, we simply don't know how large such a range has to be.
The solution is, not to know the range in before. In order to do that, we use a coroutine.
Iterable.filter = |a;p| fn*|| for x in a if p(x) yield x end end end
Let us test it out:
> (1..).filter(isprime).list(10) [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
The prime numbers will be printed instantly:
for p in (1..).filter(isprime) print(p) end
Or for convenience:
(1..).filter(isprime).each(print)
A coroutine, that we can send data to, takes an additional
argument. If this argument x
is made optional,
the coroutine may be used like an ordinary generator:
g = |a,b| fn*|x=null| for k in a..b yield [x,k] end end g(1,4).each(print) # Output: # [null, 1] # [null, 2] # [null, 3] # [null, 4]
Now, let us send data into it:
i = g(1,2) print(i("a")) print(i("b")) print(i("c")) print(i("d")) # Output: # ["a", 1] # ["b", 2] # empty # empty
It may be used this way:
i = g(1,4) for x in "a".."z" y = i(x) if y==empty then break end print(y) end
Or more elaborately:
for x in ("a".."z").map(g(1,4)) print(x) end # Output: # ["a", 1] # ["b", 2] # ["c", 3] # ["d", 4]
Reaching the upper bound of the range fed into coroutine
g(1,n)
implicates the iterator to be exhausted too:
for x in ("a".."d").map(g(1,100)) print(x) end # Output: # ["a", 1] # ["b", 2] # ["c", 3] # ["d", 4]