I had a
define-attributes macro for a while now in a project, I don't use it a whole lot, but where I do it seems to make the code less verbose. But I think my understanding of source locations is still not the best so the macro may need some tweaks in that regard.
In this post I used it to illustrate a point (at the end) and that got me thinking, maybe this could be useful for others.
Here is some code that illustrates how I use it:
(define (vec3-squared-length v) (define-attributes ([v]) vec3- (x y z)) (+ (x² x) (x² y) (x² z))) (define (vec3-normalize v) (define-attributes ([v]) vec3- (x y z [length l])) (if (= 0 l) v (vec3 (/ x l) (/ y l) (/ z l)))) (define (matrix4-from-rotation-axes x y z) (define-attributes (x y z) vec3- (x y z)) (matrix4 xx yx zx 0 xy yy zy 0 xz yz zz 0 0 0 0 1)) (define (matrix4-from-translation v) (define-attributes ([v]) vec3- (x y z)) (define _ 0) (matrix4 1 _ _ x _ 1 _ y _ _ 1 z _ _ _ 1))
Here is the current implementation:
(require (for-syntax syntax/parse)) (provide define-attributes) (begin-for-syntax (define (symbol-concat l r) (define s1 (syntax-e l)) (define s2 (syntax-e r)) (string->symbol (string-append (symbol->string s1) (symbol->string s2)))) (define-syntax-class name-mapping #:description "name-mapping" [pattern from:id #:with to (syntax-e #'from)] [pattern (from:id) #:with to '||] ; empty symbol [pattern (from:id to-id:id) #:with to (syntax-e #'to-id)])) ;(define-attributes (l r) vec3- (x y z)) lx ly lz rx ry rz ;(define-attributes ([l]) vec3- (x y z)) x y z ;(define-attributes ([l o]) vec3- (x y z)) ox oy oz ;(define-attributes ([l l.]) vec3- (x y z)) l.x l.y l.z ;(define-attributes ([l]) vec3- (x y z [length l])) x y z l (define-syntax (define-attributes stx) (syntax-parse stx [(_ (ids:name-mapping ...) prefix:id (attributes:name-mapping ...)) (define (mapping keys values) (make-hasheq (map cons (syntax->list keys) (syntax->list values)))) (define prefix-map (mapping #'(ids.from ...) #'(ids.to ...))) (define suffix-map (mapping #'(attributes.from ...) #'(attributes.to ...))) (define definitions (for*/list ([id (in-list (syntax->list #'(ids.from ...)))] [attr (in-list (syntax->list #'(attributes.from ...)))]) (cons (datum->syntax stx (symbol-concat (hash-ref prefix-map id) (hash-ref suffix-map attr))) (with-syntax ([accessor (datum->syntax stx (symbol-concat #'prefix attr))]) #`(#,#'accessor #,id))))) (define identifiers (map car definitions)) (define expressions (map cdr definitions)) #`(define-values (#,@identifiers) (values #,@expressions))]))
Do you have ideas how this macro could be improved?
-> source locations:
#:subs #t, functional-style macro implementation
Do you find the macro useful?
Do you know of similar macros?
Do you prefer other ways of doing the same/similar thing?
-> seems useful and a bit similar to match-define, with a twist
Should I create a package for this?
Should it be a tiny package? (I think that might make it most likely that someone actually uses it)
-> for now I won't create a package for this, I may add it to some package in the future, but also feel free to do what you want with the code (public domain)
I am not quite sure what is possible with source locations and macro introduced bindings.
I am not a regular drracket user, but this macro doesn't show an arrow for the bindings that are created by this macro.
I tried to add source location information to the newly generated bindings in a few different ways, hoping that would cause drracket to draw an arrow from the use, to the corresponding id in
(define-attributes (x y z) ...), but that didn't seem to do anything.
Are arrows only for 1:1 mappings?
Is there a way to have something similar for identifiers that were introduced based on other identifiers,
or am I simply missing something in my implementation?
(Maybe I need a function that lifts the define-values to the surrounding internal definition context so that the newly introduced identifiers become more visible? But I think that already happens automatically...)
In general I am not sure whether this macro seems "rackety" to other people, the way it creates new identifiers based on suffixes.