Parallel Assignment
January 11, 2019
I had trouble writing this program, so I found this snippet due to Kent Dybvig:
(define-syntax pset!
(syntax-rules ()
[(_ [x e] ...)
(for-each
(lambda (f v) (f v))
(list (lambda (t) (set! x t)) ...)
(list e ...))]))
That’s clear and elegant. I knew I needed for-each, and I knew that I couldn’t use let, because that would introduce a new environment and new variables, but I didn’t figure out those beautiful and very idiomatic uses of lambda. Maybe someday I’ll be as good a programmer as Kent Dybvig. Here’s an example:
> (define a 1) > (define b 2) > (define c 3) > (list a b c) (1 2 3) > (pset! (a b) (b c) (c a)) > (list a b c) (2 3 1)
You can run the program at https://ideone.com/jO200s.
#include <stdio.h> template <typename T0, typename T1> void assign(T0 &t0, T1 &t1, T0 e0, T1 e1) { t0 = e0; t1 = e1; } template <typename T> void swap(T &t0, T &t1) { assign(t0,t1,t1,t0); } int main() { int a = 0, b = 1; printf("%d %d\n",a,b); swap(a,b); printf("%d %d\n",a,b); swap(a,b); printf("%d %d\n",a,b); }The code in C in your email above does a swap of the values of x and y using t as temporary storage. C does not have parallel assignment
Well, you can do something similar to my C++ solution with a macro in C (assuming some compiler extensions):
#include <stdio.h> #define assign(t0,t1,e0,e1) \ ({typeof(e0) _e0 = (e0); typeof(e0) _e1 = (e1); \ (t0) = _e0; (t1) = _e1; \ }) #define swap(t0,t1) (assign(t0,t1,t1,t0)) int main() { int a = 0, b = 1; printf("%d %d\n",a,b); swap(a,b); printf("%d %d\n",a,b); swap(a,b); printf("%d %d\n",a,b); }That should be “typeof(e1) _e1 = …” of course. And swap should be SWAP if we use the convention that macros that evaluate their arguments multiple times should be in upper case.
I wrote this before reading the solution by @programmingpraxis
(Dybvig’s pset!). It’s not as clever as that one but I think it works
as needed.
(import (scheme base) (scheme write)) (define-syntax parallel-set! (syntax-rules () ((_ () ()) ; just for completeness (values)) ((_ (d0) (s0)) (set! d0 s0)) ((_ (d0 d1 d2 ...) (s0 s1 s2 ...)) (let ((t0 s0)) (parallel-set! (d1 d2 ...) (s1 s2 ...)) (set! d0 t0))))) (let ((a 'va) (b 'vb) (c 'vc) (d 'vd) (e 've) (f 'vf)) (parallel-set! (a b c d e f) (b c a e d f)) (display (list a b c d e f)) (newline))Here’s a solution in Common Lisp.
(defun every-other (lst) (labels ((rec (x acc) (if (null x) acc (rec (cddr x) (cons (car x) acc))))) (reverse (rec lst (list))))) (defmacro parsetf (&rest pairs) (let* ((vars (every-other pairs)) (vals (every-other (cdr pairs))) (syms (loop repeat (length vars) collect (gensym))) (env (mapcar #'list syms vals)) (setfs (mapcar (lambda (x y) (list 'setf x y)) vars syms))) (if (/= (length vars) (length vals)) (error "too few values") `(let* ,env (progn ,@setfs nil)))))Example Usage:
I didn’t realize the editor was using tabs. Here’s the same code, hopefully properly formatted (indented) this time.
(defun every-other (lst) (labels ((rec (x acc) (if (null x) acc (rec (cddr x) (cons (car x) acc))))) (reverse (rec lst (list))))) (defmacro parsetf (&rest pairs) (let* ((vars (every-other pairs)) (vals (every-other (cdr pairs))) (syms (loop repeat (length vars) collect (gensym))) (env (mapcar #'list syms vals)) (setfs (mapcar (lambda (x y) (list 'setf x y)) vars syms))) (if (/= (length vars) (length vals)) (error "too few values") `(let* ,env (progn ,@setfs nil)))))Example usage:
CL-USER> (let ((a 1) (b 2) (c 3)) (print (list a b c)) (parsetf a b b c c a) (print (list a b c)) nil) (1 2 3) (2 3 1) NILCommon Lisp already has PSETQ and PSETF.
(psetq a b b a) ; swap a and b
(psetf (aref v i) (aref v j) (aref v j) (aref v i)) ; swap a[i] and a[j]