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:
my_func((my_struct_t){
.a_bool_flag = true,
.another_bool_flag = false,
.a_string = "Hello World"
});
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). function f(; a, b)
a+b
end
Default values are optional. This can only be called with named arguments like f(; a=5, b=7). Unfortunately the separation isn't required to be explicit when calling the function, so calling f(a=5, b=7) also works. Generally calling functions is extremely permissive (e.g. a function with two positional and two keyword arguments function g(a, b=5; c, d=8) can be called with keywords and positions interleaved g(1, c = 7, 5)), leading to potential confusion at the call site. Our coding guidelines enforce separation of positional and keyword at call sites, and with that additional restriction I have found Julias behaviour in this regard very pleasant. E.g.: calc_something(a, b; bool_flag=true)
is the best style for this type of thing that I have seen. my_func(/*a_bool_flag*/ true, yadda);https://pdimov.github.io/blog/2020/09/07/named-parameters-in...
myFunc({
aBoolFlag : true,
anotherBoolFlag : false,
aString : "Hello World"
});
const myFunc = ({ aBoolFlag, anotherBoolFlag, aString }) => { /* do something with them... */ }; v = calc_formula(ia, ib, is_gain=true)
You also have the option of defining a default value for the argument so the old call-sites don't even need modification.However, enum is also a heavy-duty solution. It requires slightly more typing, but more importantly, it requires exporting an enum to all call-sites. Both in C++ and in python this is not desirable.
I'd say the enum should be in the toolbox, especially if the flag is important to the business logic of the code, and is likely to thread throughout it. But for quick work, a key-word only argument can work just as well. Especially if the flag is never to be passed on.
enum gain_type { enable_gain, disable_gain }; def calc_formula(first, second, *, is_gain=False):
…The solution provided in the article is the way to go.
calc_formula(1,2,true)
would show as
calc_formula(a: 1, b: 2, is_gain: true)
but
calc_formula(1,2,some_var)
shows as
calc_formula(a: 1, b: 2, some_var)
Although on the flip side, it creates an incentive for the developer to choose sensible names for variables, functions etc.
Choose:
AddElement(object, true, false);
AddElement(object, true, true);
AddElement(object, false, false);
AddElement(object, false, true);
or AddElement(object, visible::on, deletable::off);
AddElement(object, visible::on, deletable::on);
AddElement(object, visible::off, deletable::off);
AddElement(object, visible::off, deletable::on);
The latter is more readable, you can spot bugs easier, you don't need to remember which parameter was for visibility, and which was for indicating deletable. And it doesn't take much more to write this than a confusing boolean. It doesn't scale.> I’d like a language that can contextually resolve enums, so I can just type something like calc_formula( ia, ib, is_gain ).
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.
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.
Actually there is one: records with default values.
Another trick, at least in js, is to use destructuring assignment, e.g.
function calc_formula({a, b, is_gain}){
...
}
calc_formula({a:1, b:2, is_gain:true})Does this create garbage for the garbage collector (which might be an issue for inner loops)?
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)`.
To be fair, the older I get, the more I like explicit arguments for everything like in Swift (and smalltalk iirc).
Instead of trigonometry functions, how would you refactor JS's fetch() with many of its behaviour-altering flags?
as @flavius29663 said (https://news.ycombinator.com/item?id=28593669) you can use the builder pattern
FetchBuilder()
.withUrl(ur)
.withMode("cors")
.withCache(true)
.withHeader('Content-Type', 'application/json')
.accept('*/*')
.post()
.then(response => response.json())
.then(data => console.log(data));If your function returns pizza, as mentioned in other comment, adding some toppings won't change the boundaries. It still returns pizza, with peperoni or not. It doesn't change how you make the pizza. You're just adding more data to it. You may solve it with syntax, language data structures, etc. Whatever you like to make it more readable to the caller. But probably you'd want to pass toppings in arguments.
On the other hand if you have a boolean param that changes how function works. That's questionable in my opinion. Say you want to return list of users from DB but omit interns, sometimes, and you need to call API (or query DB) to know if someone's intern. You could define `omitInterns` bool argument but it seems clunky to me.
I may be mistaken, though. As said: defining boundries is not easy.
This also touches other problem a bit. Should we strive to decrease `if` branching in our functions or not? I personally tend to branch very early on, so later I can follow straight path. That's not always possible, but if it is, it helps greatly. Makes code easier to follow.
now becomes 16 distinct functions.
pizza_with_pepperoni_and_bacon_and_mushroom()
Wow glad I took out those boolean params!or any combination of the above: CreatePizza() .WithMussroom() .Build()
Even better, you can add new ingredients without changing any of the existing signatures: CreatePizza() .WithProsciuto() .WithTomatoSauce() .Build()
First of all, this
function pizza(boolean pepperoni, boolean bacon, boolean mushroom, boolean artichoke)
breaks down when you want to add ham, potatoes and sausages to the pizza.Secondly, you can optimize for the common case:
fn pizza() # -> default pizza e.g. margherita
fn pizza(list_of_ingredients) # -> your custom pizza
if you we are talking of simple functions and not more complex patterns, such as piping, in Elixir I would do pizza |> add_ham |> add_mushroom |> well_done
when using boolean parameters you are also passing down a lot of useless informations (a pizza with pepperoni would include 3 false just to remove the ingredients from the pizza and only one true) and confining yourself to a fixed set of options, that could possibly also represent an impossible state. function getPizza(toppings: Iterable<Topping>): Pizza {
...
} 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);
Delphi ain't the most elegant language out there, but the set functionality is pretty nice.Citation, as they say, needed. :-)
Not knocking it, after all it's my daily driver. You can do quite a lot with it these days and it can be quite productive.
With keyword arguments, this problem goes away, and not just for boolean arguments but for arguments generally.
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.
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.
In a much simpler case of arcs in SVG, I still need to check flags to do the correct path (https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Pa...).
One, optional and keyword arguments can have defaults.
(defun calc-formula (ia ib &optional (gain t))
...)
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. (defvar *gain-enabled* t)
(defun calc-with-gain (ia ib)
...)
(let ((*gain-enabled* nil))
(defun calc-without-gain (ia ib)
...))The function inside a LET with a dynamic variable does not create a closure. If one calls CALC-WITHOUT-GAIN later, there is no binding - unless there is another dynamic binding of that variable by a different LET active.
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:
to decide what number is the calculated formula for (a: a number) and (b: a number), with gain or without gain
And within the function you could use ‘with gain’ and ‘without gain’ as if they were booleans: If with gain, decide on a+b;
And at the calling site you would call the function like so: Let c be the calculated formula for 3 and 4, with gain
(http://inform7.com/book/WI_11_14.html)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:
To recount the story so far, cursorily or in great detail…
Another similar Inform language feature is its mechanism for Boolean properties.You can declare a Boolean property on a ‘kind’ or a ‘thing’ just by saying that it ‘can’ be true:
A chair can be comfy.
The table can be scratched.
You can also use ‘either/or’ forms to make these booleans richer and more expressive: A chair can be comfy or hard.
(You can also go on and add more values, of course - at this point it’s really an enumeration)These sorts of properties on kinds become ‘adjectives’, and you can use them in both declarations:
The overstuffed armchair is a comfy chair in the living room.
And in expressions: If the target is a comfy chair…
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.These clever micro-optimizations are a pathology of bored, well-meaning developers.