Naming conventions for property/method/variable overlaps

Let’s suppose I have an object that has cooties. Potentially I may have all of the following at one place or another:

  1. A struct with a property called cooties.
  2. An access method called cooties(let’s limit ourselves to getting cooties, not setting them, which is bullying).
  3. A keyword argument called cooties for a constructor method.
  4. Code that uses cooties for a variable that will supply the cooties of a new object.

Uses 1–3 coexist in peace. I am happy with the following:

struct Kid
    name::String
    age::Int
    cooties::Int
    function Kid(name::String; age::Int, cooties::Int=0)
        new(name, age, cooties)
    end
end

name(k::Kid) = k.name
cooties(k::Kid) = k.cooties

new_kid = Kid("Kevin"; age=7, cooties=4)
if cooties(new_kid) > 1
    println("Ew! $name(new_kid) has cooties!")
end

But use case #4 is impossible, since cooties is now a method name in this scope. This is the price Julia pays for refusing the traditional object.method syntax.

Here are the alternatives that come to mind:

A. Put the Kid in a module and use import with fully qualified names at all times, or when you are anticipating this clash. In my MWE, that is a fine way to go, but it is less appealing when the module name is RationalFunctionApproximation. Also, if you are in a module already, it leads in the direction of a module hierarchy that otherwise seems superfluous.

B. Prefix all access methods with get_ and set_. A sensible solution without much extra wordiness and with the added benefit of tidier tab completions. But I don’t see it in most Julia packages. In fact, it seems to contradict what Blue style says.

C. Use more descriptive variable names, like the_cooties = 4 or I_hear_the_new_kid_has_this_many_cooties = 4. I should probably code like this anyway, right? Right?

D. Suffix the access methods, i.e., ageof() and cootiesof(). I suspect this would be awful and we should continue to pretend that typeof didn’t happen.

E. Access methods are for weenies, just use direct property access. Requires a syntactic difference between real properties and virtual ones, and it’s messy for compound or nested types.

Have I missed one? Does the Julia community have a clearly preferred option? I suspect, but I can’t prove, that it’s “we gave you namespaces so that you would use A.”

1 Like

There’s no perfect solution, but here are some thoughts:

If you’re coding inside functions, and you keep your functions short, you can can also keep your variable names short. My rule of thumb is that “the shorter the function, the shorter the variable names can be”. So, for example, this seems fine:

function migrate(source::Kid, destination::Kid)
    a = age(destination)
    c = cooties(source)
    Kid(name(destination); age=a, cooties=c)
end

(There’s another dimension, which is “the more generic a function is, the shorter the variable names can be”, but that doesn’t apply as often to scripts or application code as it does to library code.)

For internal code, it’s sometimes better to opt for option E, unless your codebase is complicated enough that you want to introduce some internal interfaces.

And there’s always the option to postfix with an underscore, like this:

cooties_ = cooties(kid)

It’s not pretty, but it gets the job done.

3 Likes

Not an answer, just a feedback on the fact that you used a very unusual word for non-native English speakers. This made your example more difficult to grasp…

1 Like

Our company uses get_X for methods, because the improved tab completion/greppability is really nice. I like it a lot.

1 Like

I have found that using modules and submodules provides the best user experience. It also helps a lot with method discovery. Here is a thread I posted about it.

I plan to write my packages this way moving forward. Here is my latest package using this approach for inspiration: AISCSteel.jl

And with your RationalFunctionApproximation being too long, you can always do:

import RationalFunctionApproximation as RFA

In fact I do that with my package:

import AISCSteel
import AISCSteel.Shapes.IShapes.RolledIShapes as RIS
1 Like

I don’t use a submodule. Just use the explicit name of the current module.

module RationalCootiesApproximation  
const RCA = RationalCootiesApproximation  # short-hand

struct Kid
    name::String
    age::Int
    cooties::Int
    function Kid(name::String; age::Int, cooties::Int=0)
        new(name, age, cooties)
    end
end

name(k::Kid) = k.name
cooties(k::Kid) = k.cooties

function cooties_report(all_kids, cooties)
    for k in all_kids
         @show RCA.cooties(k)
    end
    println("Other cooties: ", cooties)
end

end

Might look slightly off, but it quickly becomes just another idiom.

4 Likes