Vitaly Magerya escribió:
> Aubrey Jaffer wrote:
>> An example of an exception
>> is the use of a pseudo-random-number-generator having shared state:
>>
>> (require 'random)
>> (+ (random 99) (random 99))
>>
>> The concurrency-safe version could be written:
>>
>> (require 'random)
>> (let* ((rnd1 (random 99))
>>        (rnd2 (random 99)))
>>    (+ rnd1 rnd2))

In the example above you want *sequential* evaluation (so as not to mess
up with random internals, if those are not synchronized, becasue random
that has an internal state), but you can evaluate operands in any order
because '+' is commutative.

For this sequential evaluation you may use let*, of course.

But let's think of this case instead:

(- (random 99) (random 99))

In this case you want sequential evaluation and *ordered* evaluation (so
the first argument is the minuend and the second is the subtrahend,
because '-' is not commutative).

For this case "let*" is, again, what you want, because it evaluates
bindings sequentially *and* in an specified order: from left to right.


> 
> I have a rather long list of questions, some of which are more like
> statements; but please bear with me, as I want to understand the
> implications of such design better.
> 

Ok.

> For one thing, `let*'-wrapping in addition to preventing concurrency
> also restricts the order of evaluation; but you only want sequential,
> and don't care which exactly. Note that many optimizing compilers
> shuffle the order of evaluation to achieve better register usage. So
> shouldn't we specify additional sequential-call form (`scall') to
> explicitly mark call sites where you want to forbid concurrency?

You're right. A priori (more on this later) we would need:

- a 'let' that works concurrently.
- a 'let+' that works sequentially.
- a 'let*' that works sequentially and in an ordered fashion.

The fact is that R6RS just says that let bindings "are to be evaluated
in an unspecified order", but doesn't explain what happens if they're to
be evaluated concurrently. That's why we asked for R7RS to define this
in more detail.


> 
> I wonder what would be the ratio of explicitly sequential call sites
> to implicitly concurrent one in a typical program. Or may it be better
> to explicitly mark concurrent call sites (e.g. with `pcall'), not the
> sequential ones?
> 

There're Schemes out there that provide a "pcall", as in [2] below.

> Another thing, let's say I have an expression like this:
> 
>   (f (x) (y))
> 
> Am I required to know if both `x' and `y' share the same mutable state,
> so I can't call them in parallel? What if I don't know (e.g. I'm
> calling some user-passed functions, or the documentation did not
> specify such details), do I change each call to `scall'?

That depends on two things: what the R7RS specification says about
concurrency and the way you implement that R7RS specification.

Let's assume R7RS explicitly says:

"The effect of any concurrent evaluation (of lambda arguments, of let
bindings, of 'map', etc.) must be consistent with some sequential order 
of evaluation".

(As a side note: I wonder if that's a good restriction or not, it seems
to me to be too strong, but I haven't had time to think about it. 
Crystal Scheme [3] didn't had such a strong restriction, and amusing 
things happened as a result).

(As a second side note: I'd love an effect-free R7RS, without set! & 
friends)

This is restriction is equivalent to the so called "serializable
transaction isolation" [1], roughly speaking: transactions (parallel
threads) just see what they change.

You could then build a Scheme implementation that has a special
environment that behaves just like those databases (roughly speaking).
Each thread behaves like a database transaction, and scheme variable 
bindings behave like data in your database.

If you had such a "thread-serializable isolation level environment" 
(thread-aware, for short) then you wouldn't really have to worry about 
calling things in parallel. You just start transactions (threads) and 
all those transactions behave as if they were executed serially, one 
after the other.

> 
> What if at some point in the development both `x' and `y' where pure,
> but then I added `random' to both; do I now have to go through all
> the code that uses them and sequentialize the calls? Do I also have
> to find all the functions that call `x' or `y' and sequentialize calls
> to those functions as well?

You could also implement a Scheme compiler that handles all those issues
and looks for side effects in code, but I think the approach above is
probably simpler.

> 
> Isn't this a rather major penalty for using side-effecting code? I just
> wonder how much of a problem this may be in practice.

I don't know. I haven't experimented with it (yet). There're some papers
out there I want to read before tackling a concurrent Scheme implementation.

> 
> Finally, how do I use code which I cannot sequentialize? E.g. if we
> specify `map' to be concurrent, how do I (map random '(1 2 3))?
> Or shall we have a separate `sequential-map' in the standard library?
> 

If R7RS addresses concurrency issues then yes, there could be a "cond"
(sequential) and a "pcond" (concurrent), and there should be a "map"
(concurrent) and a "smap" (sequential), and possibly many more.

Just as there *could* be concurrent, sequential and ordered sequential
"let"s, as seen above.

Cheers,
Antonio


[1]
http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#SERIALIZABLE

[2]
http://www.scs.cmu.edu/afs/cs/project/ai-repository/ai/lang/scheme/code/ext/threads/0.html

[3]
Crystal Scheme: pcall with a not-so-strong concurrency constraint
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.40.6613

_______________________________________________
r6rs-discuss mailing list
[email protected]
http://lists.r6rs.org/cgi-bin/mailman/listinfo/r6rs-discuss

Reply via email to