generalised set! in Racket

101 views
Skip to first unread message

Alexey Cherkaev

unread,
Jun 29, 2015, 5:56:36 AM6/29/15
to racket...@googlegroups.com
Hi all,

Common Lisp has a very useful idiom of SETF that can set values to arbitrary places. For example, one can set (1 2)-th item of an array A as:

(SETF (AREF A 1 2) 20.0d0)

Scheme preferred way is to use `*-set!` procedures. However, sometimes it is inconvenient. For example, if I want to swap two elements in the array/vector, I would like to use `swap`:

(define-syntax swap
(syntax-rules ()
((swap ?place1 ?place2 ?tmp)
(begin
(set! ?tmp ?place1)
(set! ?place1 ?place2)
(set! ?place2 ?place1)))))

But it will only work for vectors if `set!` can act as CL's SETF on generalised places, such as

(swap (vector-ref v 5) (vector-ref v 3) tmp)

SRFI-17 contains the definition of such a `set!` and I can use it from there (AFAIR Racket supports it).

I was wondering if there is more Racket-way of doing this? And is there maybe a reason why `set!` wasn't given the same power as SETF?

For example, I was thinking of defining syntax to access my implementation of multidimensional arrays
as

(define-syntax aref
(syntax-rules (set!)
[(set! (aref ?a ?i ...) ?v) (array-set! ?a ?i ... ?v)]
[(aref ?a ?i ...) (array-ref ?a ?i ...)]))

but obviously it won't work now as `set!` expects the `id` not an expression (`syntax-id-rules` won't work either as `aref` is not an `id`).

Regards, Alexey

Alexander D. Knauth

unread,
Jun 29, 2015, 12:36:17 PM6/29/15
to Alexey Cherkaev, racket...@googlegroups.com

On Jun 29, 2015, at 5:56 AM, Alexey Cherkaev <alexey....@gmail.com> wrote:

> For example, I was thinking of defining syntax to access my implementation of multidimensional arrays
> as
>
> (define-syntax aref
> (syntax-rules (set!)
> [(set! (aref ?a ?i ...) ?v) (array-set! ?a ?i ... ?v)]
> [(aref ?a ?i ...) (array-ref ?a ?i ...)]))
>
> but obviously it won't work now as `set!` expects the `id` not an expression (`syntax-id-rules` won't work either as `aref` is not an `id`).


#lang racket
(require srfi/17 math/array)
(define (aref a . is)
(array-ref a (list->vector is)))
(set! (setter aref)
(λ (a . is+v)
(match-define (list is ... v) is+v)
(array-set! a (list->vector is) v)))
(define a (mutable-array #[#[1 2] #[3 4]]))
(aref a 0 1)
(set! (aref a 0 1) 20)
(aref a 0 1)

If you wanted, you could also define your own version of set! that does what you want, which (I think) you would need to do if you needed aref to be a macro.


Alexey Cherkaev

unread,
Jun 30, 2015, 11:10:14 AM6/30/15
to Alexander D. Knauth, racket...@googlegroups.com
Hi Alexander,

Thanks for your reply: I had something similar in mind (maybe I should check out math/array). I was just wondering if there was something more Racket-like (it still feels that SRFI is somewhat 'foreign' hack). And my last question remains: wouldn't it be beneficial to have such a generalised 'set!' system-wide? I understand that Racket focusses more on immutable structures, but there are still vectors and hash-tables which are inherently mutable and still have their niche.

Best regards,
Alexey

Michael Titke

unread,
Jun 30, 2015, 4:52:29 PM6/30/15
to racket...@googlegroups.com
As the mentioned SRFI states it:
"This is a proposal to allow procedure calls that evaluate to the "value of a location" to be used to set the value of the location, when used as the first operand of set!."

The term "location" is only used in the introductory part but for those who know the way behind the scenes it clearly refers to the concept of memory addresses of current register machines. By not introducing the domain of memory addresses (where you might as well SETF your graphics card ROM, EFI boot loaders and other registered beings in that "real" address space that is then virtualized but still contains your call stack) they push you into the world of nameless concepts which would be the same as lambda forms without define. Just try this in your favorite clean Scheme environment but don't call it algorithmic language then.

With generics it is possible to define the necessary methods for set! - one such method exists in GOOPS (sorry!) which is able to set slots of objects by accessor. The name or identifier isn't gone yet but its "sequence of symbols" or syntax just looks awful:
(define point (make <point> #:x 1 #:y 2))
(x point) => 1
(set! (x point) 2)
(x point) => 2

With common sense (and not behind scenes) x denotes both the value as well as a method to set it. Other programming systems use the dot syntax which works slightly better, e.g. point.x.

Generics and a generalized set! have been en vogue in the 80s as far as I know and have then been substituted by message passing interfaces where you usually define your own getters and setters for each and every variable which might behind the scenes turn out to be stored on a server on the next floor or it might just be your favorite terminal emulation dimming some little lights called pixels.

Back to the generalized set!: it leaves you in a world of invisible pointers or location which might even be called concepts. When one starts thinking in abstract concepts and goes from concept to concept one might end up where one has to invent new words to "grab" them mentally. This is what Latin is good for. But a generalized set! might just do away with that little rest of "language" that remained in the Algorithmic Language Scheme.

I think I'm going to purge some set! methods from VSI/goops.

Regards,
Michael

John Clements

unread,
Jun 30, 2015, 5:34:52 PM6/30/15
to Alexey Cherkaev, Alexander D. Knauth, racket...@googlegroups.com

> On Jun 30, 2015, at 8:10 AM, Alexey Cherkaev <alexey....@gmail.com> wrote:
>
> Hi Alexander,
>
> Thanks for your reply: I had something similar in mind (maybe I should check out math/array). I was just wondering if there was something more Racket-like (it still feels that SRFI is somewhat 'foreign' hack). And my last question remains: wouldn't it be beneficial to have such a generalised 'set!' system-wide? I understand that Racket focusses more on immutable structures, but there are still vectors and hash-tables which are inherently mutable and still have their niche.

This style of set! seems like a bad idea to me.

Specifically, one of the basic ideas of algebraic languages is that programs are compositional. Specifically, if I write (a (b x) c), then the meaning of this term depends on the meanings of a, (b x), and c. That is, I can combine these three values to get the result. Generalized set! breaks this intuition. Specifically, (set! (aref x 0 1) 13) does not depend on the value of (aref x 0 1). Rather, it pulls apart this term and uses its subterm’s meanings. Put differently, this is lvalues.

I know, I know, I sound like a pure-functional snooty-poo.

John



George Neuner

unread,
Jun 30, 2015, 6:43:56 PM6/30/15
to John Clements, racket users
On 6/30/2015 5:34 PM, 'John Clements' via Racket Users wrote:
> > On Jun 30, 2015, at 8:10 AM, Alexey Cherkaev <alexey....@gmail.com> wrote:
> >
> > ... wouldn't it be beneficial to have such a generalised 'set!' system-wide? I understand that Racket focusses more on immutable structures, but there are still vectors and hash-tables which are inherently mutable and still have their niche.
>
> This style of set! seems like a bad idea to me.
>
> Specifically, one of the basic ideas of algebraic languages is that programs are compositional. Specifically, if I write (a (b x) c), then the meaning of this term depends on the meanings of a, (b x), and c. That is, I can combine these three values to get the result. Generalized set! breaks this intuition. Specifically, (set! (aref x 0 1) 13) does not depend on the value of (aref x 0 1). Rather, it pulls apart this term and uses its subterm’s meanings. Put differently, this is lvalues.
>
> I know, I know, I sound like a pure-functional snooty-poo.

Don't confuse source syntax with what goes on under the hood - compilers
for imperative languages often go to great lengths to transform programs
into more "functional" forms. Search for "value numbering" and "static
single assignment" for more information. [Those aren't the only
functionalizing transformations, but they are the most commonly used.]

Moreover, assignment per se is not incompatible with composition -
that's just semantics. On real hardware, assignment is all you have:
"binding" is implemented as assignment to a (possibly) new location.
Tail recursion is an important case of invisibly "binding" new values to
the same locations (i.e. assigning to the control variables of an
imperative loop).

George

John Clements

unread,
Jun 30, 2015, 7:27:39 PM6/30/15
to George Neuner, racket users

> On Jun 30, 2015, at 3:43 PM, George Neuner <gneu...@comcast.net> wrote:
>
> that's just semantics.

XD




Neil Toronto

unread,
Jun 30, 2015, 7:37:47 PM6/30/15
to racket...@googlegroups.com
Let me expound a bit on John's pure-functional snooty-poo reply.

Semantics - what programs mean - is everything. Exactly how they're
compiled, interpreted, JITted, transformed, optimized, and otherwise put
through the meat grinder to make something useful, is secondary. If a
certain feature would void a property that *humans* find extremely
helpful when reasoning about programs, there had better be really good
motivation for it.

Carry on.

Neil ⊥

Alexander D. Knauth

unread,
Jun 30, 2015, 8:01:31 PM6/30/15
to John Clements, Alexey Cherkaev, racket...@googlegroups.com

On Jun 30, 2015, at 5:34 PM, 'John Clements' via Racket Users <racket...@googlegroups.com> wrote:

> Specifically, one of the basic ideas of algebraic languages is that programs are compositional. Specifically, if I write (a (b x) c), then the meaning of this term depends on the meanings of a, (b x), and c. That is, I can combine these three values to get the result. Generalized set! breaks this intuition. Specifically, (set! (aref x 0 1) 13) does not depend on the value of (aref x 0 1). Rather, it pulls apart this term and uses its subterm’s meanings.

Actually, this makes a lot of sense to me and is probably the best argument I’ve heard for why lvalues can be a bad idea.
(and some time ago I read the discussion on SRFI 17, just for fun looking at the reasoning on both sides)
Thanks!


Neil Van Dyke

unread,
Jun 30, 2015, 8:06:13 PM6/30/15
to Alexey Cherkaev, racket...@googlegroups.com
I have implemented generalized `set!` in Racket before, as a cute
exploratory exercise, but I haven't found a sufficiently compelling
benefit of generalized `set!`.

The closest I've imagined to benefit of generalized `set!`: let's say
you had really terse but nice referencing syntax for complex objects
(records, collections), and you wanted to make the assignment lvalue
syntax look the same as the rvalue syntax. For example, how in some
language we might have both of:
p = a[x][y]
a[x][y] = p
However, with Racket obviously coming from the Scheme tradition, Racket
is not about the terse syntax (not until you get into DSLs, which is
different). I don't see sufficient benefit to complicating the
semantics of the language just to make `(set! (vector-ref v 0) 42)`
equivalent to `(vector-set! v 0 42)`.

If you want to get into OO-like generalization, one could come up with a
scenario in which generalized `set!` could be used, but offhand I can't
think of an example that couldn't be done with a more conventional use
of OO objects and methods.

If someone has a great scenario for how generalized `set!` wins, I'd be
interested in seeing it. (For example, maybe there's a win in some
scenario of syntax transformation, in which you wind up with a `(set!
ACCESSOR VALUE)` pattern, and for some reason it's easy to get there but
hard to get to `(SETTER-PROC OBJ VALUE)`? Just a bad speculative
example; you'd need to go further.)

Neil V.

David Vanderson

unread,
Jun 30, 2015, 8:35:01 PM6/30/15
to racket...@googlegroups.com
Racket does have 'make-set!-transformer' that allows you to define syntax that cooperates with 'set!'.  I think that might work if you are defining your own datatype.  Have you seen that?

Thanks,
Dave
--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Matthias Felleisen

unread,
Jun 30, 2015, 9:29:45 PM6/30/15
to George Neuner, John Clements, racket users
John knows these things. His advisor co-invented SSA and preached the imperative-compilers-are-functional while he was in grad school. I know, I was there and watched, and I know the guy. I am sure it all sank in.

What you fail to see is that "it's just semantics" is a way of saying "why bother programming in languages such as Racket or ML or Coq. It's all assignment statements on the machine anyway." As we know from many years of experience of course is that this statement makes no sense to a good programmer. There are huge differences. And what John is getting at is that the language (as is) carries its meaning and its pragmatics on its sleeves. The very moment you introduce the kind of generalized assignment statement you lose this kind of clarity that comes with the language.

So let's all be happy now and program in ASM. It's assignments anyway and LHS values are lovely for the heart-ware purist :-)

-- Matthias

Alexey Cherkaev

unread,
Jul 1, 2015, 4:27:33 AM7/1/15
to Matthias Felleisen, George Neuner, John Clements, racket users
Hi all,

Thanks to all replies, at the moment I would tend to agree that generalised `set!` might not be such a great idea after all:
  • The notion of 'place' is essentially a pointer to a memory location, but it is not the first-class citizen: the following expressions are not equivalent: (let ((x (aref v 5))) (set! x 100)) and (set! (aref v 5) 100), and it might be the place for confusion.
  • The syntax part of it, `(set! (what-ever ...) new-value)`, however, I don't mind: `set!` is a special form/macro after all, not a procedure. No one would demand the procedural clarity from `let` or `define`. The question is rather if it worth having yet another special form.
Summarising the points above, what if instead of `set!` there is a more general abstraction 'location': it can hold the value, the value might be re-set, but on its own it is just a memory location. Basically, it's Racket's `box`. But the interesting twists would be getting the locations from mutable objects (like vectors and arrays). Something like this:

(define v (vector 0 1 2 3 4 5))

(vector-ref v 2) ;; -> 2 this gets you the value
;; Hypothetical procedure vector-loc
(vector-loc v 2) ;; -> "(boxed 2)"
(location-set! (vector-loc v 2) 200)
v ;; -> (0 1 200 3 4 5)
(let ((x (vector-loc v 3)))
   (location-set! x 300))
v ;; -> (0 1 200 300 4 5)

A quick implementation for vectors might look as follows:

#lang racket
(require racket/generic)

(define-generics location
  (location-ref location)
  (location-set! location new-value))

(struct vecloc (v ind)
  #:methods gen:location
  ((define (location-ref location) (vector-ref (vecloc-v location)
                                               (vecloc-ind location)))
   (define (location-set! location new-value)
     (vector-set! (vecloc-v location) (vecloc-ind location) new-value))))

(define (vector-loc v ind)
  (vecloc v ind))

(define v (vector 0 1 2 3 4 5))

(location-set! (vector-loc v 2) 200)

(let ((x (vector-loc v 3)))
  (location-set! x 300))

Wouldn't it resolve all the mentioned concerns? Now we can have named locations and we don't introduce any new syntax. What I don't know is how efficient this implementation is.


Best regards,
Alexey


--
You received this message because you are subscribed to a topic in the Google Groups "Racket Users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/racket-users/TWE4Yemx28Y/unsubscribe.
To unsubscribe from this group and all its topics, send an email to racket-users...@googlegroups.com.

Alexander D. Knauth

unread,
Jul 1, 2015, 12:09:15 PM7/1/15
to Alexey Cherkaev, Matthias Felleisen, George Neuner, John Clements, racket users
On Jul 1, 2015, at 4:27 AM, Alexey Cherkaev <alexey....@gmail.com> wrote:

Hi all,

Thanks to all replies, at the moment I would tend to agree that generalised `set!` might not be such a great idea after all:
  • The notion of 'place' is essentially a pointer to a memory location, but it is not the first-class citizen: the following expressions are not equivalent: (let ((x (aref v 5))) (set! x 100)) and (set! (aref v 5) 100), and it might be the place for confusion.
  • The syntax part of it, `(set! (what-ever ...) new-value)`, however, I don't mind: `set!` is a special form/macro after all, not a procedure. No one would demand the procedural clarity from `let` or `define`. The question is rather if it worth having yet another special form.
The syntax part of it is what srfi/17 does, and anyone who requires srfi/17 hopefully understands that it’s another special form, and that the (what-ever …) is not actually an expression, and hopes that people reading the code will also understand that. Because of the first point, it’s best that it’s in a separate library like srfi/17, not available by default, because that avoids confusion unless you require srfi/17 on purpose. 

Summarising the points above, what if instead of `set!` there is a more general abstraction 'location’:

Side note: Such a `location` abstraction doesn’t have to be all about mutability.

There is a purely functional version of this concept in Jack Firth's `lenses` package, which says that
lenses are functions that take a data structure and returns two values;
 the first being the “view,” or the value at that location,
 and the second being the “context” of that value, represented as a function that acts as a functional “setter,”
and these lenses are functional and compose-able.


Also, because lenses return two values, they don't have to allocate a new structure every time, which (I think) means slightly better performance.

Reply all
Reply to author
Forward
0 new messages