# Define-attributes macro, is this useful, ideas for improvements?

**URL:** <https://racket.discourse.group/t/define-attributes-macro-is-this-useful-ideas-for-improvements/300>\
**Category:** Questions & Answers\
**Created:** [November 29, 2021, 8:18am UTC](https://racket.discourse.group/t/define-attributes-macro-is-this-useful-ideas-for-improvements/300 "2021-11-29T08:18:17Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![simonls](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/simonls/32/170_2.png) [@simonls](https://racket.discourse.group/u/simonls)\
**Post date:** [November 29, 2021, 8:18am UTC](https://racket.discourse.group/t/define-attributes-macro-is-this-useful-ideas-for-improvements/300/1 "2021-11-29T08:18:18Z")

</div>

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.

> [@Naming convention for X vs. a procedure that returns X](https://racket.discourse.group/t/naming-convention-for-x-vs-a-procedure-that-returns-x/299/2):
>
> Of course this is just my opinion, I also may have forgotten something I do sometimes, just can't think of right now. 1. short(/mathematical) functions I usually try to keep my functions extremely short (but that isn't always practical), while having most of my hints through naming that aid in code readability in the function names rather than local variable names. So in these very short functions I treat variables more like short math variables like r1, r2, a, b, c, etc.. Although those short…

Here is some code that illustrates how I use it:

```scheme
(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:

```scheme
(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.

---

<div class="post-metadata">

**Author:** ![EmEf](https://avatars.discourse-cdn.com/v4/letter/e/53a042/32.png) [@EmEf](https://racket.discourse.group/u/EmEf)\
**Post date:** [November 29, 2021, 4:43pm UTC](https://racket.discourse.group/t/define-attributes-macro-is-this-useful-ideas-for-improvements/300/2 "2021-11-29T16:43:34Z")

</div>

Use

```scheme
(datum->syntax 
     stx 
     (symbol-concat (hash-ref prefix-map id)
                    (hash-ref suffix-map attr)) 
     stx
     stx)

```

to get an arrow-connection to `lx` from

```scheme
(define-attributes (l r) vec3- (x y z))

```

Adjust these extra arguments to your needs.

---

<div class="post-metadata">

**Author:** ![SamPhillips](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/samphillips/32/15_2.png) [@SamPhillips](https://racket.discourse.group/u/SamPhillips)\
**Post date:** [November 30, 2021, 12:41am UTC](https://racket.discourse.group/t/define-attributes-macro-is-this-useful-ideas-for-improvements/300/3 "2021-11-30T00:41:24Z")

</div>

[`format-id`](https://docs.racket-lang.org/reference/syntax-util.html#%28def._%28%28lib._racket%2Fsyntax..rkt%29._format-id%29%29) also can handle some of the details of making identifiers from parts of strings, symbols, and identifiers.

---

<div class="post-metadata">

**Author:** ![benknoble](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/benknoble/32/16_2.png) [@benknoble](https://racket.discourse.group/u/benknoble)\
**Post date:** [November 30, 2021, 1:35am UTC](https://racket.discourse.group/t/define-attributes-macro-is-this-useful-ideas-for-improvements/300/4 "2021-11-30T01:35:45Z")

</div>

The macro seems useful: it reminds me a bit of `match-define` for pulling out pieces of structs or similar, but orthogonally has this sort of cross-product going on with mappings, prefixes, and attributes. I wonder what a match-expander would look like?

I agree with @SamPhillips that you probably want `format-id` with its `#:source` argument: reading, the macro feels very procedural (à la CommonLisp) rather than pattern-matching (à la, well, Racket). Perhaps with judicious use of `format-id`, you could get the template to something like

```scheme
#'(define-values (to ...) (values exp ...))

```

_Some_ amount of procedural-like code is necessary to break hygiene, but I think `#:with`, `format-id`, and templating could get there. I'll give it a shot if I have some time (unlikely).

---

<div class="post-metadata">

**Author:** ![simonls](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/simonls/32/170_2.png) [@simonls](https://racket.discourse.group/u/simonls)\
**Post date:** [November 30, 2021, 10:39am UTC](https://racket.discourse.group/t/define-attributes-macro-is-this-useful-ideas-for-improvements/300/5 "2021-11-30T10:39:00Z")

</div>

Like @benknoble suggested I tried to rewrite the macro using more pattern matching, at first that seemed a bit difficult. But by using a helper macro, fixing my syntax-class (so that it keeps the original identifiers in `#:with to ...`) and using `format-id` like @SamPhillips suggested, I managed to get it quite conscise and now it shows arrows properly. Here is the new version:

```scheme
#lang racket

(provide define-attributes)

(require syntax/parse/define
         (for-syntax racket/syntax))

(begin-for-syntax
  (define-syntax-class name-mapping
    #:description "name-mapping"
    [pattern from:id
             #:with to #'from]
    [pattern (from:id)
             #:with to '||] ; empty symbol
    [pattern (from:id to-id:id)
             #:with to #'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 len])) x y z len
(define-syntax-parser define-attributes
  [(_ (ids:name-mapping ...+) prefix:id (attributes:name-mapping ...+))
   #`(begin (define-attributes-id #,this-syntax ids prefix attributes ...) ...)])

(define-syntax-parser define-attributes-id
  [(_ loc id:name-mapping prefix:id attribute:name-mapping)
   #:with newid (syntax-property
                    (format-id #'id "~a~a" #'id.to #'attribute.to #:subs? #t)
                    'original-for-check-syntax #t)
   #:with accessor (format-id #'loc "~a~a" #'prefix #'attribute.from #:source #'loc)
   #:with expr #'(accessor id.from)
   #'(define newid expr)]
  [(_ loc id:name-mapping prefix:id attributes:name-mapping ...+)
   #'(begin (define-attributes-id loc id prefix attributes) ...)])

;; example
(struct vec3 (x y z))

(define (x² x) (* x x))
(define (vec3-length v)
  (define-attributes ([v]) vec3- (x y z))
  (sqrt (+ (x² x) (x² y) (x² z))))

(define-syntax-rule (fmt x ...) (begin (displayln (~a (~a (quote x) ": " x " ") ...))))

(module+ main
  (define l (vec3 3 5 7))
  (define r (vec3 0 2 4))

  (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

  (fmt lx ly lz rx ry rz)
  (fmt x y z)
  (fmt ox oy oz)
  (fmt l.x l.y l.z)

  (let ()
    (define-attributes ([l]) vec3- (x y z [length len])) ; x y z len
    (fmt x y z len)))

```

![racket_attributes_3fps](https://global.discourse-cdn.com/free1/uploads/racket/original/1X/1ba5a4a6b788cb9fd91cc579d0d608bf3b6d0f67.gif)

---

<div class="post-metadata">

**Author:** ![Kalimehtar](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/kalimehtar/32/394_2.png) [@Kalimehtar](https://racket.discourse.group/u/Kalimehtar)\
**Post date:** [February 14, 2022, 1:13pm UTC](https://racket.discourse.group/t/define-attributes-macro-is-this-useful-ideas-for-improvements/300/6 "2022-02-14T13:13:06Z")

</div>

Could you make a package for this macro?

---

<div class="post-metadata">

**Author:** ![simonls](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/simonls/32/170_2.png) [@simonls](https://racket.discourse.group/u/simonls)\
**Post date:** [February 14, 2022, 1:29pm UTC](https://racket.discourse.group/t/define-attributes-macro-is-this-useful-ideas-for-improvements/300/7 "2022-02-14T13:29:09Z")

</div>

yes, but this reactivates an issue for me about single package vs split packages.

I think I will create a separate topic about that.

> [@Single Package vs Multiple (-lib -doc -test) & Convenience](https://racket.discourse.group/t/single-package-vs-multiple-lib-doc-test-convenience/693):
>
> Convenience Authoring vs Using Packages When I author a simple package with only a little bit of functionality, I really like the convenience of creating a single package and putting everything inside it. When I want to use a simple package I prefer if it is split up into (-lib -doc -test) because that allows me to depend only on the -lib part of the package and thus avoid slow installation because for example the docs are unnecessarily built for that dependency. These opposing "forces of conv…

---

<div class="post-metadata">

**Author:** ![simonls](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/simonls/32/170_2.png) [@simonls](https://racket.discourse.group/u/simonls)\
**Post date:** [February 16, 2022, 3:21am UTC](https://racket.discourse.group/t/define-attributes-macro-is-this-useful-ideas-for-improvements/300/8 "2022-02-16T03:21:53Z")

</div>

I created a package [define-attributes](https://pkgd.racket-lang.org/pkgn/package/define-attributes), as an exercise of playing through what that other topic is about, I created it as a single package.

I think in the future with some new solution it would be good if this was enough.  
With the current status quo I think it isn't.  
In the documentation of this package I used `pict-lib` to create a picture that is shown in the documentation.  
I think it is unreasonable for users of `define-attributes(-lib)` to now not only have a dependency on `rackunit-lib`, `scribble-lib` and `racket-doc` but also on `pict-lib`.

So for now, to make the package usable with the status quo, I will split the package in `define-attributes` and `define-attributes-lib`, where the former additionally contains documentation and tests.

---

<div class="post-metadata">

**Author:** ![dstorrs](https://avatars.discourse-cdn.com/v4/letter/d/898d66/32.png) [@dstorrs](https://racket.discourse.group/u/dstorrs)\
**Post date:** [February 16, 2022, 5:12pm UTC](https://racket.discourse.group/t/define-attributes-macro-is-this-useful-ideas-for-improvements/300/9 "2022-02-16T17:12:44Z")

</div>

Cool macro, and thanks for packaging it up. I played with it for a bit and found that the base case works well but I'm confused about the 'length' bit. Here's the test code I used, which should be straight copy/paste from the documentation:

```scheme
#lang racket

(require define-attributes)

(struct vec3 (x y z))
(define l (vec3 3 5 7))
(define r (vec3 0 2 4))

(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 len])) ; x y z len                           

```

This does not work. The exception I get is:

```scheme
; /Users/dstorrs/bmtc_dev/app/test.rkt:11:0: vec3-length: unbound identifier                   
; in: vec3-length                                                                            
; Context (plain; to see better errortrace context, re-run with C-u prefix):                   
; /Users/dstorrs/.emacs.d/elpa/racket-mode-20211130.1748/racket/syntax.rkt:66:0    

```

What am I missing?

---

<div class="post-metadata">

**Author:** ![simonls](https://yyz2.discourse-cdn.com/free1/user_avatar/racket.discourse.group/simonls/32/170_2.png) [@simonls](https://racket.discourse.group/u/simonls)\
**Post date:** [February 16, 2022, 5:22pm UTC](https://racket.discourse.group/t/define-attributes-macro-is-this-useful-ideas-for-improvements/300/10 "2022-02-16T17:22:08Z")

</div>

whoops I didn't add the definition for `vec3-length` to the documentation which is a normal function accepting a vec3 and returning its length, I will add it to the example.

The function is:

```scheme
(define (x² x) (* x x))
(define (vec3-length v)
  (define-attributes ([v]) vec3- (x y z))
  (sqrt (+ (x² x) (x² y) (x² z))))

```

This is simply to illustrate that the "attribute" can also be what I would call a "computed attribute" something you access as if it were an attribute but is actually computed from the value that is being "accessed".

* * *

I added it, thank you for letting me know, it should show up in a few hours when the documentation is rebuild.
