[racket] set!: eval-ing to lvalues

From: Carl Eastlund (cce at ccs.neu.edu)
Date: Sun Jun 9 18:02:12 EDT 2013

There are a lot of tradeoffs in decisions like this.  Personally, for
instance, I've never found the desire for a generic set!; I just use a
specific set! or store a box (mutable reference cell) wherever I need to
mutate something.  The impact of an lvalue system is going to be different
for each Scheme that implements it.  And no, the impact would probably not
be limited to the code that uses mutation.  Features like this usually
impose a small cost on everything; having two paths through execution means
that every step that doesn't use an lvalue might still be checking "am I
using an lvalue now?".  If lvalues mean keeping around references to
containers longer than they might be needed, it can have a space impact too
-- the feature might delay garbage collection of the container.

I don't mean to say this feature might not be useful, and it might be very
interesting research to implement it in an existing Scheme.  I just mean
it's not immediately obvious that it's a win/win scenario.  Predicting the
impact of features like this is difficult, because they're interacting with
so much else in the language.

Carl Eastlund

On Sun, Jun 9, 2013 at 12:28 PM, Sean Kanaley <skanaley at gmail.com> wrote:

>  Thanks for the explanation.  I suspected it was for efficiency reasons,
> but as I've never implemented a "real" scheme, I don't know the trade off.
> I wonder how bad it is.  Way back, they invented lisp.  Then they said it
> was too slow for real stuff.  Now they say other languages are too weak for
> real stuff, and lisp's relatively little slowness is made up for by its
> power.  And here we are, saying a powerful feature would slow it down!
> Is it 2x, 3x, 4x, 10x, infinityx slower to implement some kind of lvalue
> system?  And wouldn't that system be necessary only in code that uses the
> mutation?
> On 06/09/2013 08:18 AM, Carl Eastlund wrote:
>  Sean,
>  Not every Scheme uses an interpreter with an eval function as its primary
> method of execution, or even at all.  Racket uses a bytecode interpreter
> and a JIT native-code compiler; the eval function simply triggers
> compilation to bytecode.  These give a great deal more efficiency than
> running via eval, and supporting multiple modes of execution would be
> significantly more expensive.  Evaluating to values by default, rather than
> to addresses, also gives the compiler a great deal of flexibility.  It
> doesn't need to keep track of the addresses where it found things and refer
> to them there in case they are about to be mutated; once they have been
> "found" via evaluation, they can be copied to register and the original
> address can be forgotten, if that's most expedient.  I'm not a compiler
> implementer myself, so I'm sure others can probably give more specific
> details.  In the meantime, I hope this explanation is helpful.
> Carl Eastlund
> On Thu, Jun 6, 2013 at 4:12 PM, Sean Kanaley <skanaley at gmail.com> wrote:
>>    Hello all,
>>  I was curious why Scheme and now Racket does not inherently support a
>> generic set!.  I found an SRFI
>> http://srfi.schemers.org/srfi-17/srfi-17.html that suggests a generic
>> method solution requiring a lookup for the "real" setter (and so needing a
>> special setter for every data type.  What is the disadvantage of simply
>> changing eval to take a "fetch?" parameter that decides whether to
>> ultimately resolve addresses to values?  Then set! is evaluated with this
>> as #f and can operate on whatever that address is.  I have implemented this
>> in a toy interpreter with the bare minimum of forms plus vectors to test.
>> The vector-ref type of function gets applied as usual to the vector and
>> index arguments, except that if it's within a set! as the left argument
>> where the fetch? is #f and the final fetching of the address given by
>> vector-ref never happens.
>>  Here's the critical pieces:
>> 1. setting
>>  "update" is what changes the store
>>  set! is of course a clause within eval
>>  the last parameter to eval in the first line says don't fetch
>> [(set! addr-x x) (match-let* ([(v*s addr s) (eval addr-x e s #f)]
>>                                   [(v*s v s) (eval x e s)])
>>                         (update addr v s))]
>> 2. evaluating symbols (another clause)
>>  the symbol only returns its address with fetching off
>> [(sym y) (let* ([addr (lookup y e)]
>>                     [val (if fetch? (fetch addr s) addr)])
>>                (v*s val s))]
>>  3. the "built-in" (part of environment) vector-ref called vec@
>>  "fetch?" will be false if (vec@ ...) is the first argument to set!
>>  "a" is the base address of the vector
>>  (define (vec at -f e s fetch? v i)
>>  ...unimportant stuff...
>>          (let ([val (if fetch? (fetch (+ a i) s) (+ a i))])
>>           (v*s val s)))))
>>  So long as all built-in types have this conditional fetcher, then every
>> user type built on top of them won't need a special setter.  And since this
>> would work just as well for inc! types funtions, from now on
>> (vector-set! v i (add1 (vector-ref v i))
>>  is
>>  (inc! (vec@ v i))
>>  I assume this has already been thought of and therefore discarded, but
>> why?
>> ____________________
>>   Racket Users list:
>>   http://lists.racket-lang.org/users
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.racket-lang.org/users/archive/attachments/20130609/0e3e410b/attachment-0001.html>

Posted on the users mailing list.