[racket] set!: eval-ing to lvalues
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>