As pointed out in some of the article's comments, a much better solution would be if all languages allowed putting the name in front of function parameters (and while at it, also not enforce a specific parameter order and skip default-value parameters).<p>A workaround in C99 (and more limited in C++20) is to use a single struct which bundles all the function parameters, and then use designated initialization, this also makes function paramaters optional, and at least in C99 they can appear in any order:<p><pre><code> my_func((my_struct_t){
.a_bool_flag = true,
.another_bool_flag = false,
.a_string = "Hello World"
});
</code></pre>
This is mainly useful for functions which take many parameters. One downside in C99 (but not C++) is that there's no way to define default values for struct items (other than zero/false).
In python, I love keyword-only arguments for this.
Then the caller has to write:<p><pre><code> v = calc_formula(ia, ib, is_gain=true)
</code></pre>
You also have the option of defining a default value for the argument so the old call-sites don't even need modification.
From the article:<p>> I’d like a language that can contextually resolve enums, so I can just type something like calc_formula( ia, ib, is_gain ).<p>Swift does this and it should be considered for any new language design. Enum.variant can be shortened to .variant, including for variants with data in them, .variant(arg). Perfect solution, because the dot is an autocomplete trigger.
In Erlang, one would usually pass atoms like 'with_gain' or 'without_gain', and substitute default values with arity-overloading: e.g. there would be two calc_formula functions, one with 2 arguments and one with 3 arguments, and the 2-argument one would simply call the 3-argument with the last parameter set to 'with_gain'.<p>And in case of really large number of parameters, one would generally pass either a map or (in older code) a proplist: #{is_gain => true, other_param => 42, ...} or [{is_gain, true}, {other_param, 42}, ...]. There is no beautiful syntax for destructuring all that stuff with default values, unfortunately.
This should really be solved by using named parameters or by writing docblocks so that IDE can show hints.<p>Another trick, at least in js, is to use destructuring assignment, e.g.<p><pre><code> function calc_formula({a, b, is_gain}){
...
}
calc_formula({a:1, b:2, is_gain:true})</code></pre>
I agree that using booleans like this can be confusing. But, imo, it's more confusing to have a bunch of wrapper functions that create abstraction madness.<p>I mostly write computation/math-related code and I find using named arguments to be a good practice. This is also quite similar to OP's enum approach, e.g. sth like `calc_formula(a, b, is_gain=True)`.<p>To be fair, the older I get, the more I like explicit arguments for everything like in Swift (and smalltalk iirc).
Named parameters misses the point. Functions should do one thing and only one thing. This is why we have cos, sin, and tan and not a universal function for trigonometry: trig(mode='cos', ...). Such functions often become cumbersome to use since they are essentially multiple functions baked into one.
In Delphi I try to use sets over boolean parameters. Then I can easily add new possible set members instead of introducing more parameters.<p><pre><code> type FuncParam = (fpDoThis, fpDoThat);
type FuncParams = set of FuncParam;
function MyFunc(arg1, arg2: integer; params: FuncParams): integer;
begin
result := 0;
if (fpDoThis in params) then
result := DoThis(arg1);
...
end;
// supply directly
MyFunc(123, 42, [doThis, doThat]);
// or indirectly
params := [];
if (condition) then
Include(params, doThat);
MyFunc(111, 222, params);
</code></pre>
Delphi ain't the most elegant language out there, but the set functionality is pretty nice.
In a language with only ordered arguments, sure, boolean arguments are generally unreadable, but so is more than one parameter generally unless they are logically equivalent (the arguments to an add function), following a convention from some other context (e.g., the arguments to an divide or subtract function), or each unique in type in a way that they could only have on relation to the function (e.g., the iterable and function arguments to a map function; you may have to work to remember the order when writing, but when reading the meaning should be clear.)<p>With keyword arguments, this problem goes away, and not just for boolean arguments but for arguments generally.
This reminds me of a time I worked with Typescript, and the team kept reopening discussions around the use of the "Any" type.<p>Trying to mitigate the boolean param dilemma, I would lean on Erlang and its multiple function signatures. It tends to force your solutions into initially harder but eventually more graceful form.<p>Generally, when my code starts showing these kinds of warts (silly parameters getting tagged on to function signatures), I take it as a sign that the initial tack I've taken no longer addresses the problem I'm trying to solve. More often then not it goes all the way back to an incomplete / outdated understanding of the business logic.
It is a bane of numerical code, in which there are many flags, quite a few parameters (with values 0. or 1.). Moreover, some flags are conditional (e.g. the value of the argument 5 matters only if the flag at position 3 is true).<p>In a much simpler case of arcs in SVG, I still need to check flags to do the correct path (<a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths" rel="nofollow">https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Pa...</a>).
There are a couple ways to approach it in Common Lisp, probably a few more than I have here.<p>One, optional and keyword arguments can have defaults.<p><pre><code> (defun calc-formula (ia ib &optional (gain t))
...)
</code></pre>
Two, you could use a dynamic variable and a closure. Outside the body of the let the gain var returns t, within the body of the let it returns nil.<p><pre><code> (defvar *gain-enabled* t)
(defun calc-with-gain (ia ib)
...)
(let ((*gain-enabled* nil))
(defun calc-without-gain (ia ib)
...))</code></pre>
Inform7 - an interactive fiction language - is often instructive in suggesting different approaches to syntax from those used in mainstream programming, and indeed in this case it has a couple of interesting constructions to avoid naked Booleans.<p>Most directly relevant to this, it has the concept of ‘options’ as arguments to ‘phrases’ (which are basically functions). This would let you write something like:<p><pre><code> to decide what number is the calculated formula for (a: a number) and (b: a number), with gain or without gain
</code></pre>
And within the function you could use ‘with gain’ and ‘without gain’ as if they were booleans:<p><pre><code> If with gain, decide on a+b;
</code></pre>
And at the calling site you would call the function like so:<p><pre><code> Let c be the calculated formula for 3 and 4, with gain
</code></pre>
(<a href="http://inform7.com/book/WI_11_14.html" rel="nofollow">http://inform7.com/book/WI_11_14.html</a>)<p>Obviously in Inform7 you are more likely to be using this in a much more naturalistic way, effectively adding ‘adverbs’ to the ‘verbs’ your phrases are defining:<p><pre><code> To recount the story so far, cursorily or in great detail…
</code></pre>
Another similar Inform language feature is its mechanism for Boolean properties.<p>You can declare a Boolean property on a ‘kind’ or a ‘thing’ just by saying that it ‘can’ be true:<p><pre><code> A chair can be comfy.
The table can be scratched.
</code></pre>
You can also use ‘either/or’ forms to make these booleans richer and more expressive:<p><pre><code> A chair can be comfy or hard.
</code></pre>
(You can also go on and add more values, of course - at this point it’s really an enumeration)<p>These sorts of properties on kinds become ‘adjectives’, and you can use them in both declarations:<p><pre><code> The overstuffed armchair is a comfy chair in the living room.
</code></pre>
And in expressions:<p><pre><code> If the target is a comfy chair…
</code></pre>
The idea that Boolean parameters are really adverbs and Boolean properties are really adjectives I think says something quite profound, and there’s definitely room for other languages to do better at acknowledging the first-class role these kinds of constructs could have with the right syntax.
What about copy pasting functions and make variants? With a hint in the name about what the variant does?
Copy pasting and modifying seems like a safe low effort working solution.
Is this considered bad practice?
I find that many of these principles can be distilled (and replaced) by the simple adage: what is the best way to write this to make my colleague's life easier?
If someone submitted a PR where all of their boolean parameters were actually enums I would reject it, open a PR of my own from the same branch, and reject that one too.<p>These clever micro-optimizations are a pathology of bored, well-meaning developers.