Skip to content
This repository has been archived by the owner on Dec 31, 2022. It is now read-only.

Avoid abbreviated named arguments #24

Open
justsml opened this issue Feb 14, 2019 · 8 comments
Open

Avoid abbreviated named arguments #24

justsml opened this issue Feb 14, 2019 · 8 comments

Comments

@justsml
Copy link

justsml commented Feb 14, 2019

I wanted to float an idea to take advantage of additional benefits from destructured object param function signatures... But first I want to include my thoughts on the pattern.

Here's an excerpt from an article I'm writing to explore the 'why' and trade-offs of this approach.


Destructured named object parameters is preferred to positional arguments in almost every case.

Inevitably, every design decision involves trade-offs. So, let me frame 2 priority goals, clarity and memorability, as more important than extreme DRY adherence.

  1. Clarity. Using the same names for function parameters AND in their invocations' arguments helps cement the pattern deeper in the brain. Flexibility in the key sequence lets you draw attention to contextually important parameters.
  2. Limit guessing game. Help people read, evaluate, and understand usage examples & implementation code faster, while reducing need to repeatedly check the functions signature 😰(or relying on IDE features or keeping the docs open in another tab.)
  3. Improve Signal:Noise ratio. Positional arguments have virtually zero built-in indication of what each signifies. Related groups of names are naturally better "anchor points" in your brain.
  4. Better ergonomics for optional arguments (avoid needing to pass in an undefined argument).

Exceptions: There are a few use cases which I agree should be positional: for example the copy or mv file commands are a good fit for positional arguments. (A low, fixed arity tends to make things easier.)


With that considered, I feel argument names which are abbreviated or single letters are unnecessarily harder to memorize. Learning names is hard enough, and abbreviations invite too much confusion (common example are all over, see unix /usr path was originally UNIX source repository, now it's UNIX system resources, not user).

While it can be annoying typing accumulator in every reduce, it's unambiguous as it's not clear that an abbreviation would be acc instead of accum. Perhaps a non-standard term is a better fit on that one, say results or state. (This bit is a discussion for later...)

If single letter variables should be avoided in code generally (except in constructs like loops,) why are they ok in parameters when the knock-on effect is a leaky abstraction passing weirdness to every invocation.

Please let me know how you feel about my take.
I really appreciate your time Kyle!
Thanks in advance!

@getify
Copy link
Owner

getify commented Feb 14, 2019

I think your reasoning makes sense, and I would have tended toward that position originally.

But there's a tension at play here. Namely (pun intended), that there's an adoption resistance to this whole named-arguments approach, which is that it makes call-sites much more verbose already, even with one character names. As a result, I sense that people are a bit skiddish about adopting this style, and I'm sensitive to not make that worse by making the call-sites that much more verbose.

I wonder if there's a way to accommodate both viewpoints? Similar to unix command line parameters that often have both long and short parameter name equivs, I wonder if there's a way to have long argument names mapped/aliased to their short-name counterparts?

FPO already has the remap(..), so perhaps that machinery could be reused? That way, you could use acc or accumulator interchangeably. Thoughts?

@justsml
Copy link
Author

justsml commented Feb 14, 2019

I think that's a good balance. Docs can be verbose because their purpose is explaining, while implementation abbreviations could easily be written out if any confusion arose or some behavior was unexpected.

I wish something like this was valid:

const reduce = ({
    v = value, value = v, 
    acc = accumulator, accumulator = acc
  }) => {
  console.log('value', value)
  console.log('accumulator', accumulator)
}

reduce({v: 'test abbrev.', acc: []}) // ✅
reduce({value: 'test abbrev.', acc: []}) // ❌😿

@getify
Copy link
Owner

getify commented Feb 14, 2019

screen shot 2019-02-14 at 2 22 16 pm

@justsml
Copy link
Author

justsml commented Feb 15, 2019

Nicely done, didn't think of it like that! 🥇

@justsml
Copy link
Author

justsml commented Feb 16, 2019

Want me to toss together a PR?

@getify
Copy link
Owner

getify commented Feb 16, 2019

Yes... but first...

What should happen if someone passes both forms of a param (long and short)? What if the values are different?

And what about when FPO calls a user-provided function? Should it pass both forms?

@justsml
Copy link
Author

justsml commented Feb 16, 2019

  1. i feel the behavior should follow something similar to interesting quirk with named currying #20 - use 1st arg and don't throw or whatever is decided. (I know it's not the same so not 100% sure. It might be helpful to show warning about it while in dev mode? thinking like bluebird warnings)
  2. I haven't got enough info to even field a guess. Do you have a project discord/slack? Is there an alpha branch to float a trial balloon?

@getify
Copy link
Owner

getify commented Feb 16, 2019

I'm glad you bring up #20 because this is going to create a complication for how the internal currying works, and also the automatic adaptation for the Std namespaced functions. Hmmmm...

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants