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
