Naming convention for structs

Hi everyone,

I am wondering if anyone else has run into this issue and if there is some commonly-used work-around?

The issue is the name of my struct and the name of a binding for an instance of the struct usually ends up having the same name which causes issues. For example:

(struct person
  (name age))

(define (person-update-age person new-age)
  (struct-copy person person
    [age  new-age]))

The problem is I would like to name the parameter to the person-update-age also person. This then shadows the person struct-id and causes the code to raise an error.

I was thinking of adding '$' to my struct names to avoid this conflict. For example:

(struct $person
  (name age))

(define (person-update-age person new-age)
  (struct-copy $person person
    [age  new-age]))

This seems to work ok, but I would like to check if there is already a community-established convention for this situation?

Regards

This is a FAQ. See a recent discussion at https://racket.discourse.group/t/choosing-good-names/2900 .

TL;DR: some people use a-person / the-person for an instance of person. Some people abbreviate instance names. But there’s not really a “community-established convention”.

1 Like

I've seen the a- / the- notation used in a somewhat readable formal semantics system.
It used 'a-' when introducing a parameter (binding it), and 'the' when referring to it.

eg.

To increment a number
add one to the number.

Unfortunately, 'a-person' and 'the-person' are recognised in Racket
as having nothing whatsoever to do with each other.

-- hendrik

One thought: if you're writing these update/set functions yourself, you're doing more work than you need to. Check struct-plus-plus.

#lang racket

(require struct-plus-plus)

(struct++ person (name age) #:transparent)

(define bob (person 'bob 17))
bob
; Displays: (person 'bob 17)                       
                                                 
(set-person-age bob 18)  ; functional update, not mutational                                        
; Displays: (person 'bob 18)                                                     
                   
(set-person-age bob (add1 (person.age bob)))
; Displays: (person 'bob 18)                                                                        

(update-person-age bob (λ (current-val) (+ 10 current-val)))
; Displays: (person 'bob 27)                                                                        

; Also, keyword constructor and all fields have setters/updaters                                    
(person++ #:name 'bob #:age 37)
; Displays: (person 'bob 37)                                                                        

(set-person-name bob 'tom)
; Displays: (person 'tom 17)  

If we want, we can make the-number refer to a-number by defining a custom top.

#lang racket/base

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

(provide (rename-out [my-top #%top]))

(define-syntax (my-top stx)
  (syntax-parse stx 
    [(_my-top . id:id)
     (define str (symbol->string (syntax-e #'id)))
     (define n   (string-length str))
     (if (and (>= n 4)
              (string=? (substring str 0 4) "the-"))
         (with-syntax ([a (format-id #'id "a-~a" (substring str 4 n))])
           (syntax/loc #'id a))
         (syntax/loc stx
           (#%top . id)))]))

Now the following program works:

(require "the-top.rkt")

(define (subscript a-string a-number)
  (~a the-string "_" the-number))

(subscript "foo" 42)
1 Like

Use p as short for the person.

Note that field accessors are prefixed with the struct name (here person).
Thus in an expression like (person-age p) no one doubts that p is a person.

Thanks everyone,

The person-update-age function is just for the example. I do not write those types of functions :slightly_smiling_face:

I like the a-person convention for the binding. I will try a couple of things and see what works for me.

Regards

Interesting. I had no idea that was possible.
It's worth further study.
Do I presume correctly that it does not restrict the 'the-' form to binding
occurrences and the 'a-' form to bound occurrances?

-- hendrik

Do I presume correctly that it does not restrict the 'the-' form to binding
occurrences and the 'a-' form to bound occurrances?

Consider the example:

(define (foo a-number)
   (+ the-number 1))

Here the-number is unbound, so the expander expands into (#%top . the-number).
Since we imported #%top from a module that exported my-top as #%top,
the expander continues looking at (my-top . the-number).

The transformer of my-top looks at the identifier. If it begins with the- it rewrites it to an identifier that begins with a- instead. Here it becomes a-number (and has the same context as the original identifier). If the identifier doesn't begin with the- it just leaves it as-is.

In short, all unbound references caused by identifiers of the form the-x are rewritten into references of the form a-x.

Of course I meant to have written this the other way around:

Do I presume correctly that it does not restrict the 'a-' form to binding
occurrences and the 'the-' form to bound occurrances?

-- hendrik

Actually after playing around with different options, adding a '^' suffix for structs feels better and I will switch to that as a convention. :grinning:

An suffix ^ is already used for units.

https://docs.racket-lang.org/guide/Signatures_and_Units.html

Do I presume correctly that it does not restrict the 'a-' form to binding
occurrences and the 'the-' form to bound occurrances?

Well, the macro does nothing more than:

In short, all unbound references caused by identifiers of the form the-x are rewritten into references of the form a-x .

So an a- identifier behaves as normal. Wrt. the- identifiers. Try this:

(define (foo a-number x)
  (let ([the-number 42])
     (+ x the-number)))
(foo 1 2)

I expect to see 44 as result.

Nooooooo :slight_smile:

Dammit, thanks for letting me know.
I will now choose a different suffix and update a lot of code :sweat_smile:

I have changed my suffix to '.' so my struct ids now look like this:

(struct person.
  ...)