Abbreviating function names, defdelgate, macros and MUMPS

After a year with Elixir I grew tired of writing, reading, and thinking in long, core function calls that I used to the point they felt like boiler plate. This is not Elixir specific, but here it seems easy to create a solution. Recently, I’ve been making modules and functions with abbreviated names (often single letters) for core functions or slight modifications of them.

For example, from my module S

def s(string, splitter), do: String.split(string, splitter)`
def st(string), do: String.split(string, "\t")

Today I learned about defdelegate which would work for the straight alias S.s, but not for creating something like S.st. This has me asking a few questions.

  1. What are the advantages of using a macro to generate an “alias” like those described. In my readings it seems that creation of a DSL is benefits from macros. I seem to have missed why macros are preferred over functions (I’m sure this is basic) ?
  2. Without digging in and picking it apart, I can’t follow exactly what is happening in defdelgate. Other than creating an alias function what is going on ?
  3. Off the topic of Elixir, but related to abbreviated module/functions names: Are there any languages other than MUMPS that have these built in?

Thanks to all.

Some thoughts:

  1. defdelegate/2 can define delegates for both S.s/2 and S.st/1 - unless I’ve missed something else in your expectations?

  2. defdelegatge/2 is just creating a function that calls the target function. Its a macro so that interpreting the arguments is done once at compile time and not every time at runtime.

1 Like

What you’re doing is optimizing your code for writing at the expense of reading, but most code is read many more times than it is written, so it’s a bad tradeoff in the long term. I would not want to join your team and have to maintain this code you’ve written.

In order to optimize writing speed, you should look into custom snippets in your editor of choice. If your current editor can’t do that, then switch to one that does and you’ll be able to train your finger muscle memory and reuse that for any language you write.

That being said, I do have a utility module in my work codebase that offers shortcuts to look up our core domain entities. We don’t go so far as single letter function names, and this is not used by business logic itself. It’s useful for troubleshooting in iex.

7 Likes

I totally second this.

One of the things that really pisses me off in Software Development is when I have to read code that uses abbreviations, because it’s not readable, I need to lookup what all that abbreviations mean each time I read the code. It’s just a pure waste of my time and energy.

In my opinion code should just look as much as possible as reading a book.

So, I have a question for you:

How you gonna track in your memory all this short-names when you arrive to a lot of them, like more then 20/50/100 or whatever is the limit for your brain to memorize them?

2 Likes

Happy New Year kip and thanks for engaging

Regarding #1 I am able to get S.sn to work using defdelegate if I add a “dummy” variable with a default
iex(1)> defmodule S do
...(1)> defdelegate sn(text, spl \\\ "\n"), to: String, as: :split
...(1)> end
iex(2)> S.sn("foo\nbar")
[“foo”, “bar”]`

However, that seems a bit cloogey. What I would prefer to do is

iex(3)> defmodule S do
...(3)> defdelegate sn(text, "\n"), to: String, as: :split
...(3)> end
** (ArgumentError) defdelegate/2 only accepts function parameters, got: "\n"...

Regarding #2
That was my assumption, but when I benchmarked the aliasing functions above vs using a macro I saw no significant difference. However, just now as I’ve been writing, it occurs to me that benchee likely compiles each test prior to running.

Perhaps I should write a modified defdelegate. Grokking the original would be good in itself.

Thanks again and all my best.

Happy New Year Greg and exadra37. Thank you for your replies. They are clearly thoughtful.

I was hesitant to even begin to go down this path. Your points were all ones I considered and frankly fretted over. In the past I’ve run groups where I was dogmatic about explicitness. Finally I felt comfortable based on the following

  1. I (or anyone worth their salt) could throw together a script to expand my abbreviated forms to the originals in short order if it was ever necessary. Given my domain that is unlikely and once I have the macro discussed with kip it should be even simpler.

  2. My editor allows me to view associated @doc fields with a simple mouse over of the function. So if in the rare case I question what S.st or S.sn etc mean it is right there.

  3. Most importantly, I saw that the core functions were acting as a barrier for my reading and thinking about code. String.split(term, "\t") seems to require more of my brain than S.st(term) and certainly more than |> S.st

For example
iex(1)> S.r("foo_x", "_x") |> S.c()
"Foo"

rather than

iex(1)> String.replace("foo_x", "_x", "")
|> Macro.camelize()
"Foo"

The majority of the second is essentially boiler plate. In the first what makes this code different stands out. I am also taking advantage of having a S.r/2 that blanks what is being replaced. Perhaps best of all I was able to forget that for some reason camelize is Macro.camelize in elixir

Mnemonics: The first letter is that of the core module. The second is usually for the function. Additional ones tend to be for specific uses of the function. Also I’ve worked to be consistent so that S.st and E.jt are split by and join by tab, respectively. Currently I have ~125. A few I’ve deprecated as capricious as I don’t use them enough to remember them.

In the end it likely comes down to domain. If I were running a group that needed to slot in elixir programmers with no additional training, or was writing for a client that needed the same, this would likely be another discussion.