Naming convention for X vs. a procedure that returns X

In my code, I sometimes have a pattern

(define ...
  (define func-result (func ...))
  ...)

That is, I call a function func and want to bind the result of the function call to a local identifier. Now, in many cases it would seem (kind of) natural to name the result like the called function leading to the result, especially if the function is a helper that's only called in this one place. Obviously, actually using the same name would be confusing, and it would shadow the function further down in the code.

Which naming convention would you use for the function call result vs. the called function? Actually using func-result feels a bit awkward, so I wonder if there's a better way (or ways).

Stefan

1 Like

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 variables names sometimes have a meaning too, here is an example:

(define (part:cuboid/pos scale)
  (define (s% base-vector)
    (vec3-non-uniform-scale base-vector
                            scale))

  (define +x (s% (pos 1 0 0)))
  (define +y (s% (pos 0 1 0)))
  (define +z (s% (pos 0 0 1)))

  (define o  (pos 0 0 0))
  (define m  (pos-add +x (pos-add +y +z)))

  (define ox (edge o +x))
  (define oy (edge o +y))
  (define oz (edge o +z))
  (define mx (edge m
                   (pos-add +x +y)))
  (define my (edge m
                   (pos-add +y +z)))
  (define mz (edge m
                   (pos-add +z +x)))

  (define oxy (face ox oy))
  (define oxz (face oz ox))
  (define oyz (face oy oz))
  (define mxy (face my mx))
  (define mxz (face mx mz))
  (define myz (face mz my))

  (list
   (face-coords oxy)
   (face-coords oxz)
   (face-coords oyz)

   (face-coords mxy)
   (face-coords mxz)
   (face-coords myz)))

This function creates non-optimized geometry for a cube/cuboid (it gets converted to opengl buffers somewhere else) this is just for illustration.
+x is an an axis. o is the origin. m either I don't remember, but I think I didn't come up with a meaning (m is basically the point at the other end of the main diagonal).
ox mx etc. define edges from o to x etc..
oxy is then the face that is defined by the two vectors ox and oy sharing the common point o defining a triangle that is extended to a rhomboid which is used as a opengl quad.
So sometimes my variable names have a domain specific logic to them and sometimes they are just short identifiers used similar to math variables.

2. mostly minimal scopes / internal functions

When my function is less short/mathematical I sometimes write functions with internal functions,
this has the benefit that it turns a long function into lots of little functions (where each can be given a descriptive name) turning a long scope that accumulates more and more names, into n short scopes and it also makes the steps/groups of operations explicit.
Also this allows you to use closures to avoid repetition where it doesn't make things more understandable, for example %s above makes it way more readable and skip-able than having its body 3 times in my opinion.

Why internal functions?

I find it makes code more readable when all scopes are pretty close to being as short as possible, if an internal function is only used by the surrounding function, why make it accessible to other functions?

Only makes the code harder to edit later because you don't know whether somebody else uses it (in the module, or have to check whether its provided).
(If your module only does one thing and is short it may be fine to use "sibling-functions" rather than "child-functions", because then the module provides clarity)

3. variable names describing data

I think 1. and 2. above are my priority, but somewhat orthogonal to them I also try to come up with short names that try to convey use/meaning/domain-knowledge (for less mathematical code) rather than naming what the function does that produced the value for that variable.

For example if I am processing lists I could do this (define first-result (first lst)) but this doesn't tell me anything new, instead I may write (define f (first lst) which still isn't better but at least more concise and kind of forces me to use short scopes because else it quickly becomes a mess.

What I actually want to write instead is (define user-name (first lst)) or something similar, that tells the reader what kind of data is expected.
So instead of having a name clash with (define remainder (remainder ... 60)) use (define minutes (remainder p1 60))
Example mixing mathematical and named:

(define-values (p1 seconds) (quotient/remainder p0 60))
(define-values (p2 minutes) (quotient/remainder p1 60))
(define-values (p3 hours)   (quotient/remainder p2 60))

4. bringing the language closer

Switching from language user to library author / language designer.

I think in languages like python this is often restricted to questions like "How can I use (and abuse) the existing syntax and semantics to somehow represent my problem in a more straight forward way".
But in racket we have more flexibility there.

The example above is basically my function local naming convention that may be bad in other situations. Instead of it you also could think, maybe I could create some kind of abstract geometry algebra, implement that as a macro and then write some other description, that in the end generates the same or similar data. (I didn't do that because I currently have reasons to focus my time on other stuff and stay more low level)

example

But here is an example of a utility macro I use that turns a function local convention into something more formal, generating what would be repetitive:

(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))

Here define-attributes is a custom macro that gets a set of inputs (x y z) a prefix vec3- and a set of suffixes (in this case also (x y z)) it then generates local bindings (via define-values). It also has optional renaming not shown here.
This expands to something like this:

(define-values (xx xy ... zz) (values (vec3-x x) (vec3-y x) ... (vec3-z z)))

Basically it creates all the permutations of the inputs and the suffixes. (If somebody is interested in this macro I may create another post with it or even a package, but I might need to add syntax/loc for proper source locations.)

1 Like

Since learning racket, I think my go-to has been to use a prefix (most often the-, sometimes a-), so for example:

(define (X) …)

;; later
(define the-X (X))

(define (foo an-X) …)

OTOH, I like Simon's point about mathematical names (where possible I would prefer names like mass, etc., but some subexpressions of formulas just don't have names) and meaningful names. Flipping back, the (define user-name (first lst)) example strikes me a bit odd because I would rather have (struct user [name]), and then we're back to (define the-user-name (user-name the-user)).

1 Like

If the function is small, then it is okay for local variable names to be short.

Example:

(define (format-name user)
  (define un (user-name user))
  (~a "<b>" un "</b>"))

Here it is obvious that un is short for "user name".

If the variable is repeated multiple times, it can even improve readability.

1 Like

Many thanks everyone. I think it's all helpful, so I don't want to pick a particular answer as "the" solution.

That said, special thanks to Simon for taking the time to write such a detailed answer! :slight_smile:

For the record, I just saw in my code the pattern:

(define func-value (func ...))

Similar to the -result suffix example in my initial post, this uses a -value suffix.