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 <c...@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 <skana...@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 <skana...@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@-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 >>> >>> >> >> >
____________________ Racket Users list: http://lists.racket-lang.org/users