[racket] set!: eval-ing to lvalues

From: Sean Kanaley (skanaley at gmail.com)
Date: Tue Apr 22 22:10:08 EDT 2014

I'm slightly late to the lens party but Haskell seems to have a solution to
this problem that simultaneously solves another--pattern matching--the
location of a value shouldn't be hard-coded similarly in 50 different
places that deconstruct a struct just to save typing on an accessor, god
forbid the struct change shape or not even be a struct at all anymore.  You
want e.g. current-player-health, not the first of the last of the 3rd
struct pos of the first.

Well with lenses, you can just say

*currentPlayerHealth = _head . 3rdstructpos . _last . _head*

And now you can either set:

*set currentPlayerHealth 4 theState*

or get:

*view currentPlayerHealth theState*

or modify:

*over currentPlayerHealth (+1) theState*

and other stuff.

But these are not macros!  You can thus pass in a lens to determine the
thing you are setting, e.g. in the game of Dominion you might want to move
a card from the player's hand to the table or from the hand to discards, or
from A to B in general.  You can pass in A and B and modify those places
with real functions.

So for Racket, I'm sure everybody who has ever used a mutable struct has
had the desire to pass the field name to modify:

*(struct test (x y) #:mutable #:transparent)*


*(define (over <field-name> f a-test)*
*  (set-test-<field-name>! a-test (f (test-<field-name> a-test))))*


*;example usage*

*(let ([t (test 1 2)])*

*  (over y add1 t))*
*=> '(test 1 3)*

Thanks to people smart enough to understand that a hylomorphism is just an
endofunctor in the Hask category of profunctorally-dual product monoids
with just a hint of commutative free applicative comonads and a dash of
F-algebra, this is possible!

I only found one post in the Racket group on lenses, so I am essentially
trying to spread awareness of this, the most powerful thing ever created.
I mean, the type of a lens in Haskell takes 4 parameters.  It has to be
good.


On Sun, Jun 9, 2013 at 6:02 PM, Carl Eastlund <cce at ccs.neu.edu> wrote:

> 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/20140422/0c9a2eb1/attachment-0001.html>

Posted on the users mailing list.