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))]))
Here are a few questions:
Do you have ideas how this macro could be improved?
-> source locations: format-id
+ #: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)
source locations
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.