Get rid of those boolean function parameters (2015)
mortoray.comAs 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).
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).In Julia you have positional and keyword arguments, separated by a semicolon.
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.:function f(; a, b) a+b end
is the best style for this type of thing that I have seen.calc_something(a, b; bool_flag=true)Many newer languages support named function arguments: Swift, Kotlin, etc.
Some rather old fashioned languages do as well... for example PostgreSQL's PL/pgSQL supports named function arguments, too.
For C* langs you can just insert a block comment with the arg name:
my_func(/*a_bool_flag*/ true, yadda);The downside to this approach is it often doesn't survive refactors. People change the method signature, update call sites, and often ignore the comments. Named parameters avoid this downside, it's a real shame named parameters are not more common in languages.
Linters can check for this sort of thing, for example Error Prone[0] has a lint[1] for this.
Totally agree this is better to be in the language proper so we don't need this extra tooling.
This even works in latest MSVC as well as clang and gcc!
https://pdimov.github.io/blog/2020/09/07/named-parameters-in...
Yep, in C mode (not C++) this has been working in MSVC already since VS2015. Clang also allowed the full C99 designated intitialization feature set in C++ long before C++20 as non-standard language extension.
In JS we can use JSON/object literal syntax and object destructuring to serve a similar purpose:
myFunc({ aBoolFlag : true, anotherBoolFlag : false, aString : "Hello World" }); const myFunc = ({ aBoolFlag, anotherBoolFlag, aString }) => { /* do something with them... */ };I started doing this because of React but at this point ({}) is my default way of starting a function. The only thing I dislike is that it's not super ergonomic for explicit typescript declarations (but great when using typescript to check .js files).
I don't find it too bad to do
interface IFunctionParameters { userId: string; name: string; age: number; } const example = ({userId, name, age}: IFunctionParameters) => {...}The most annoying thing about this to me (and to be clear, I do exactly this all the time) is when you mouseover `example` at a use site, all you see is `IFunctionParameters`, not the definition of `IFunctionParameters`. At least in VS Code.
That's a good point, I've lost count of the times I've hovered my mouse over that IFunctionParameters tooltip expecting a nested tooltip to appear with the actual types
Kotlin does this, and I find it’s massively improved the readability of our codebase
In python, I love keyword-only arguments for this. Then the caller has to write:
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.In C++ I’ve gotten in the habit of `enum class is_gain : bool { no, yes };` so the call site is `v = calc_formula(ia, ib, is_gain::yes);`. An advantage is that such stron Boolean-like types can be passed repeatedly and maintain their type safety.
The 'require named argument' solution is less strong than the enum solution. (An enum is also available in python). Indeed re-use of the plain bool in Python is less clear, as is passing it on. This makes the enum the best solution.
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.
It's worth noting that in Python, the enum will be slower than using a bool (how much slower? I don't know - I haven't measured it), if for no other reason than the repeated name lookups. Is it worth fretting over for something that's called occasionally? Probably not. If it's something that's going to be called a lot, e.g. in a tight loop, then it's something to be concerned about.
Another solution if you write fully-typed Python is to use typing.Literal:
where the function is defined ascalc(x, y, mode="gain")
This is IMO one of the best things type annotations provide. Combined with Protocol you can write much better structured code with little or no runtime costs.def calc( x: float, y: float, *, mode: Literal["gain", "render"], ) -> float: ...
>... it requires exporting an enum to all call-sites. Both in C++ and in python this is not desirable.
Given the reasonable namespacing that C++ and python [modules] provide, and that you have to export the complete calling specification of the function to the caller anyway (whether it's an enum, a positional boolean or a keyword argument), what's the drawback of the enum option?
In Python, if you use ' import x from' now it becomes 'import x, y from'. Can especially make refactoring more work.
If you use namespaced imports then the call is going to become very long by having the package name included twice.
In C++ you are dealing with needing to drop the enum in a header file, requiring a two file change and making headers bigger. The call-side has the same potential namespace problem, but less badly.
I also enums as a low syntactic overhead solution for strongly typed booleans in C++, although I would do something like:
enum gain_type { enable_gain, disable_gain };A lightweight alternative I often use is to make a named constexpr at the callsite. `constexpr bool IS_GAIN = true; v = calc_formula(ia, ib, IS_GAIN);`
I forget which version it starts in, but in newer python’s you can have keyword only arguments (they can’t be positional like in your example)
def calc_formula(first, second, *, is_gain=False): …
IntelliJ solves this nicely by showing the parameter name at call sites, effectively making it look like the language has named parameters.
Except this isn't an issue that should be solved at the IDE level. Not everybody is using the same IDE and has all the same features, or even the same options enabled in an IDE.
The solution provided in the article is the way to go.
Although, it doesn't always. For example,
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.
Fully agree. No need to change the source code to fix what isn't broken.
Code is for humans, not for computers.
Choose:
orAddElement(object, true, false); AddElement(object, true, true); AddElement(object, false, false); AddElement(object, false, true);
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.AddElement(object, visible::on, deletable::off); AddElement(object, visible::on, deletable::on); AddElement(object, visible::off, deletable::off); AddElement(object, visible::off, deletable::on);With a good IDE, the first one can be configured to look like the 2nd one.
But the 2nd one will always be more verbose, no matter if you need it or not.
So I'd choose the first one.
The first one will probably look confusing if you're looking at the repo through a web interface though. Or if you're looking at examples in a readme. I have personally never been limited by my "raw code writing speed", and if that was the case, I would look into touch typing/autocompletion before sacrificing readability.
Most source code will also be badly readable if you print it out ;)
So should we change our use of C++ to make it more GitHub-friendly? Or should we fix GitHub to properly work with the C++ source code that already exists?
I think it's not an issue of Github or not Github. Either you think of code as plain text first, or you think plain text is just another way to present it. I think of code as plain text first, and thus I like programming languages that don't need to be analyzed to be displayed properly. Other people may not think the same.
While I think parameter names at all call sites is the right solution, I don't think it's good enough to have this in the IDE alone. I still have to review code online that says `add(a, true)`, even if IntelliJ showed me `add(a, ignore_negatives:true)`.
The point is that this is a presentation issue. There is nothing wrong with the model. It would not be impossible for the reviewing software to analyze source code and display named parameters.
That would require every bit of software that presents this program not only to understand my programming langauge, but also to have enough context on the rest of the program to actually recognize the function and know what each parameter represents.
Not to mention, code is itself a presentation layer. Why would you put some presentation concerns in one layer (e.g. identifier names, indentation&styling), but others in another presentation layer?
this is true actually. every place we read code needs to also parse the code. it’s already happening today, that’s how you get syntax highlighting. the future of all code review in the browser is a more similar experience to coding in an ide. and eventually the browser will replace the ide (and you could say this is already happening too)
> every bit of software that presents this program not only to understand my programming language
You can either change your program to fit existing tools, or you can build smarter tools. I prefer the latter.
> code itself is a presentation layer
Not for the tool it isn't
edit: I think we can all agree that ideally we fix this in the language itself by adding optional named parameters
No matter how smart the tool is, unless it can see the definition of the function, it can't guess the parameter name. Code is often shared on mail, chat programs etc, requring me to send compilable code snippets to get nice presentation out of a blob of text would be significant overkill...
Your IDE could automatically add those annotations as rich text or embedded HTML when you copy the source code out from the IDE into the E-Mail.
Would it be rich text when I copy to WordPad and HTML when I copy to a web page? Would it also support Markdown and maybe wiki ML? Would IntelliJ know that when I'm pasting code to an Emacs buffer in java-mode it should paste plain text, but when I'm pasting to an Emacs buffer that is an email, it should instead add org-mode annotations?
Overall this concept of copy/pasting with context is nice, but it only works for applications that have a defined API between them, such as MS Office OLE objects. In all other cases, pasting plain text is better than any "smart" solution.
So your solution is to write code that is currently hard to read in the hope that someone will make a tool that presents it nicely in 10 years?
OTOH, programming for the human and not for the computer, included caring about presentation. I could say there's nothing right with the model either - a non-issue, personal preference.
... An older presentation issue is the tabs versus spaces flamewar. At least that one has burnt out. Maybe because of the rise of IDE's?
> ... An older presentation issue is the tabs versus spaces flamewar. At least that one has burnt out. Maybe because of the rise of IDE's?
My guess would be not IDEs but autoformatting tools, especially when communties like Go all follow one style.
> The point is that this is a presentation issue.
I think the point was that it shouldn't be.
> There is nothing wrong with the model
Seems to me there is.
Then apparently you need a code review tool which is as clever as the IDE.
That helps when you are writing the code. It's less helpful when you are modifying a function being called from all over the code base. Arguably smart enough refactoring tools will help, but smart enough compilers have been doing wonders for decades, too.
From the article:
> 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.
Fwiw, Ada does this too.
Does this feature have a general name? Swift users seem to call it "dot syntax", which is not a good name. I want to google "C++ should have <this feature>" and find a proposal from 2013 that never moved forward, but "C++ should have dot syntax" is just silly.
Ada doesn't use the "dot syntax"; instead the enumeration literal is written as is without a dot. If I write:
Then this is a valid call:type Number_System is (Bin, Dec, Oct); type Month is (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec); procedure Foo (A: Number_System; B: Month);
https://godbolt.org/z/eP5qMj3K1Foo( Dec, Dec )When the type is explicit, the Ada standard calls this a "Qualified expression". But I would just say that it is a kind of type inference for enumerations.
https://www.adaic.com/resources/add_content/standards/05aarm...
The official Swift name of the syntax is “implicit member expression”.
https://docs.swift.org/swift-book/ReferenceManual/Expression...
It’s not just for enums in Swift, it’s applicable to all kinds of static functions, constants, initialisers. Has to be static (in the Swift/Java/etc sense) to work. Maybe “contextual members” or “contextual statics”. I like the latter.
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'.
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.
> There is no beautiful syntax for destructuring all that stuff with default values, unfortunately.
Actually there is one: records with default values.
This should really be solved by using named parameters or by writing docblocks so that IDE can show hints.
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})My rule of thumb these days in JS/TS is all functions with more than 2 parameters should be refactored to a single object parameter using destructuring. I don't start with a single object because most times YAGNI applies.
> My rule of thumb these days in JS/TS is all functions with more than 2 parameters should be refactored to a single object parameter using destructuring.
Does this create garbage for the garbage collector (which might be an issue for inner loops)?
In theory yes. But for hot inner loops you will probably get it optimized away. (This is a common pattern so optimizer try to find it and undo it. Furthermore hidden classes for objects are common and when you destructure directly in the argument list escape analysis is pretty easy) It probably does hurt your performance for warm and cold code but that likely isn't too significant.
So yes, if performance is critical you should probably profile and consider avoiding this pattern, but for most cases the performance impact is very minor.
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.
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).
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.
Religiously splitting functions with boolean arguments doesn't always result in more maintainable code.
Instead of trigonometry functions, how would you refactor JS's fetch() with many of its behaviour-altering flags?
> how would you refactor JS's fetch()
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));I'm not following how moving the options out of the function parameters and into the call chain makes the actual function more maintainable. It's still doing the exact same thing with the exact same options it's just pulling them from elsewhere. If anything you now have more functions to maintain on top of the function that does many different things based on the calling info.
// The original "misses the point" trig(mode="cos", type="hyperbolic") // The style fetch() uses today trig({mode: "cos", type: "hyperbolic"}) // The builder refactor trigBuilder().withMode("Cos").withType("hyperbolic").calculate()Personally I don't like using with prefix in builders, I was simply presenting an example from another comment.
the difference, IMO, is - in Javascript - that this
would fail silently while// The style fetch() uses today trig({mode: "cos", typ: "hyperbolic"}) ^^^
would trigger a compilation error// The builder refactor trig().mode("cos").typ("hyperbolic")(val)but, IMO, passing objects is good enough most of the times, and I consider it a much better solution over passing boolean flags
I see the builder pattern as a way to manage lack of keyword arguments. I see very little difference between your example and the actual fetch API that takes an object as JS's version of keyword arguments.
Languages with good support for named/keyword arguments have more features such as required arguments and preventing duplicate arguments. With builder patterns your only real option is to make the builder constructor have required arguments (or throw a runtime error upon finalizing the builder).
> . I see very little difference between your example and the actual fetch API that takes an object as JS's version
true
the only difference is in the tooling
code completion for methods names works much better than autocompletion for objects' fields.
And you can't mistype a method name, it would not run and give you back a - hopefully - meaningful error, while the same is not true for objects' fields.
> code completion for methods names works much better than autocompletion for objects' fields.
That is true for vanilla JS, unknown parameters will be ignored and unset will be set to undefined. However for languages that support keyword arguments (or even TypeScript[1]) the tooling should be even better than for the keyword argument case.
[1] https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABMO...
Good rules of thumbs are given in the "Deciding which to use" section of the article. For the fetch() function, I'd keep most parameters as is since they don't change the essense of the function. But "cache" and "redirect" do (following redirects can cause N http requests rather than just one and using the cache perhaps 0) so I'd refactor them as new functions. Imagine adding retry functionality to the fetch() function using parameters. I think you can see how this leads to feature creep.
I do agree with you completely. But the problem here is as old as programming itself. It's not always easy to define function boundries.
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.
A valid use case for functions with many arguments are setup/init/creation functions which sometimes take dozens of configuration options. It's definitely better to do such setup operations in a single 'atomic' function call than spreading this out over dozens of configuration functions where it isn't clear when and in what order those should be called, or a combinatorial explosion of atomic setup functions, one for each valid combination of setup options.
function pizza(boolean pepperoni, boolean bacon, boolean mushroom, boolean artichoke)
now becomes 16 distinct functions.
Wow glad I took out those boolean params!pizza_with_pepperoni_and_bacon_and_mushroom()pizza is very suitable for the builder pattern: CreatePizza() .WithBacon() .With(artichoke) .Build();
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()
What is the difference of this and doing an object paramater, similar to JS?
createPizza({bacon: true, artichoke: true});
This has the same benefit you describe of being able to add parameters without altering any old call sites
createPizza({prosiuto: true, tomatoSauce: true});
Not OP, but it's useful for if:
- something is done to object when some props is set or validation, such as .withStuffedCrust("cheese"), it'll set the internal props as crust="stuffed" and stuffContent="cheese"
- it's branching. So rather than making user looking for the components or configuration themselves, library author can guide them with builder. Such as:
In this case withTrailerAttachment (and possibly withOpenBack or withBox) won't show up if you call withTwoTires(), and attach won't show up if you don't call withTrailerAttachment().myVehicleBuilder.withSixTires().withTrailerAttachment().attach(container)In my example the could matter, or not. I see this all the times. In your example, can you make the order matter? In my example, after each selection, you can limit or expand the further options.
Nothing to stop you from doing the same within the code of the multi-parameterized single function, is there?
etc. No?if Shrimp in Fillings then begin Add(Shrimp); Add(ShrimpOil); // So yummy together Fillings.Remove(Jalapenos); // Don't go together end; if Jalapenos in Fillings then begin Add(Jalapenos); end;You just moved the problem to a different layer.
In your case you would end up calling your function like this: DoToppings(true, false, true, true);
Whereas in my case you would call the builder directly with (only) the params you want different than the defaults.
You could use named arguments, but that doesn't solve the problem completely. You will still have a large method signature, hard to use, harder to refactor, harder to test, error prone.
It also causes a lot of redundant code: the called usually only cares about one or 2 arguments, it's rare that you *need* to pass more. The rest of the args are filled in by defaults in the implementation. There could be elegant ways to handle the defaults, like overloading methods or just passing in defaults in the method signature. Default value are pretty bad IMO, for all the reasons above. Btw, if you *need* to pass that many arguments to a function, that is another code smell worth it's own discussion.
Multiple overloaded methods could have about the same amount of code int he implementation like the builder pattern, but they have a huge drawback: the caller cannot mix and match which arguments they want to pass in. If you have 4 arguments, there would be quite a high combination of parameters (18 possibilities? - 18 functions); Using the builder pattern you have to implement 4 methods only, and you're covering all the possible combinations the client might want. You can also limit some combinations in elegant ways right in the IDE while the developer is writing the code.
Think of FluentAssertions https://fluentassertions.com/introduction They have literally hundreds of possible assertions that are represented by object instances. You can combine them in an almost infinite number of possibilities.
Sure, there is no black and white, and depends on the language, the builder pattern is a good tool to have in the toolbox.
> You just moved the problem to a different layer.
No, I think you're misunderstanding. Weird... Aha: My fault, sorry.
> In your case you would end up calling your function like this: DoToppings(true, false, true, true);
What?!? Heck no, that wasn't what I meant, why would you think that? [Goes repeatedly clicking "parent"] Aha, I see: Sorry, the threads and sub-threads have branched so I got confused as to where we are.
No, that wasn't what I meant at all. I got this sub-thread mixed up with sibling ones, and was talking in the context of languages with native enums and sets (roughly, Pascal and its descendants), where you do:
And then the body of function MakePizza uses that set as per my GP comment.type PizzaFilling: (tuna, shrimp, peperoni, ham, gorgonzola, jalapeno) ; // Etc, etc... PizzaFillings: set of PizzaFilling ; function MakePizza(PizzaFillings);It's called not with a bunch of anonymous booleans like you wrote, but with a set of enumerated descriptively-named fillings as a parameter; say, a TakeOrder function builds this set by starting from an empty one (or perhaps tomato and cheese already in as defaults?) by the customer's specifications, and then calls
> Sure, there is no black and white, and depends on the language,MakePizza(OrderedFillings);Yeah, I was attempting to show how the problem you mentioned doesn't exist in languages with better / saner types. Again, sorry for getting the contexts mixed up; I though that was what you were talking about too, and just didn't get.
> the builder pattern is a good tool to have in the toolbox.
Urgh, yeah, I suppose so... At least in languages where you need it, because they lack other more basic (Heh!) amenities.
Duh, I meant
of course.function MakePizza(Fillings: PizzaFillings);
not necessarly.
First of all, this
breaks down when you want to add ham, potatoes and sausages to the pizza.function pizza(boolean pepperoni, boolean bacon, boolean mushroom, boolean artichoke)Secondly, you can optimize for the common case:
if you we are talking of simple functions and not more complex patterns, such as piping, in Elixir I would dofn pizza() # -> default pizza e.g. margherita fn pizza(list_of_ingredients) # -> your custom pizza
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.pizza |> add_ham |> add_mushroom |> well_doneWhat kind of monster puts potatoes on a pizza?
You don't know what you're talking about :)
In the image: pizza with potatoes, a typical roman recipe
https://i0.wp.com/www.puntarellarossa.it/wp/wp-content/uploa...
I don't care who invented it. It sounds fucking terrible.
You might as well put some mashed potatoes in your rice and some pasta in a sandwich while you're at it.
It's like someone said "what type of carbs would you like with this meal" and the answer was "yes".
> I don't care who invented it. It sounds fucking terrible.
Sorry, but why should people care about what sounds terrible to you?
> put some mashed potatoes in your rice and some pasta in a sandwich while you're at it
If you weren't too obsessed with yourself, you'll know that that pasta actually exists, it's called "pasta e patate" and someone has put it in a sandwich for sure...
there is also a very popular variant made of pasta, potatoes and mussels.
> It's like someone said "what type of carbs would you like with this meal" and the answer was "yes".
It's like someone asked you "what are you first World problems" and your answer was "yes"
The recipe I'm talking about come from Italian rural tradition, when people were poor and carbs were the only thing they could afford to eat to keep being alive, not a privileged people's self inflicted fictional problem.
Sorry for the brutal honesty.
But you anglophones are not qualified to judge other culture's food. Your food is usually terrible.
> Sorry, but why should people care about what sounds terrible to you?
I never asked you to care what sounds terrible to me.
Opinions are like assholes. Everyone has one, most are full of shit, and I don't really care if you don't like mine.
If you supposedly don't care what I think, why bother trying to tell me my opinion is wrong? You can't have it both ways.
> you weren't too obsessed with yourself, you'll know that that pasta actually exists
Ok sure buddy. It's totally on me that a meal that may consist of entirely carbs is not common/popular outside of Italy. Totally my fault.
> It's like someone asked you "what are you first World problems" and your answer was "yes"
Eating a diet of just carbs is literally not a first world problem, it's a 3rd world problem because people can't afford (or can't adequately store) proteins, fresh vegetables etc.
> when people were poor and carbs were the only thing they could afford to eat to keep being alive, not a privileged people's self inflicted fictional problem.
People have eaten much worse sounding things than your double-carb special, no doubt. The difference is - you're bollocking on like it's a perfect meal, and the idea that it doesn't sound appealing is apparently insulting to you.
Who could have possibly ever foreseen that a dish made out of necessity because people literally had nothing more than two kinds of otherwise bland starchy carbohydrates, would not seem appealing when other options are available?
An opinion isn't brutal honesty bub. You have one, I have one. The difference is I am well aware that mine is an opinion.
Good job on the generalisations though. "the food of half a billion people is usually terrible, here come try some carbs on carbs.".
> why bother trying to tell me my opinion is wrong?
Simple. Because you started it.
> Ok sure buddy. It's totally on me that a meal that may consist of entirely carbs is not common/popular outside of Italy. Totally my fault
Exactly it's totally your fault for being ignorant.
Pasta/rice with potatoes it's common in many "not born yesterday" cultures in places like China, India, Africa, not exactly a small percentage of the World population.
> would not seem appealing when other options are available?
You're still talking out of ignorance.
There were a lot of other options. Meat, for example, was common back then and of higher quality than today, but meat was sold for money, because rich people loved it.
> , it's a 3rd world problem because people can't afford (or can't adequately store) proteins, fresh vegetables
complaining about carbs it's a fictional problem white privileged people invented to feel special.
So, yes, it's a first World problem.
Also: I've said poor, not 3rd World, which, BTW, has been changed to developing countries.
The fact that American pilgrims, that were poor, haven't developed a balanced diet and starved to death or due to nutritional deficiencies, says a lot about the terrible diet they had back home and nothing about other cultures that did, at the same time, being equally poor.
Don't try put words in my mouth, please.
> you're bollocking on like it's a perfect meal
See?
You can't handle the truth.
I've only said you don't know what you're talking about.
Never said anything about the quality.
I, for example, don't eat pasta with potatoes but I do eat pizza and potatoes.
And am not a "monster".
> The difference is I am well aware that mine is an opinion
But you aren't aware that your opinion is also wrong, so technically you are making a mistake, I told you it, but don't wanna learn.
Tell me you come from a British colony without telling me.
> "the food of half a billion people is usually terrible, here come try some carbs on carbs."
Yes, exactly.
It is so terrible that they usually eat other culture's food.
You don't regularly eat haggis, Which is, BTW, sheep inside sheep, or shepherd’s pies, fish and chips, bangers and mash ..., do you?
I love potatoes on pizza and order it at multiple pizza places. They add flavor and creaminess. The secret is to cook the potatoes properly and not just throw some french fries on the pizza.
But it should just be:
function getPizza(toppings: Iterable<Topping>): Pizza { ... }
In Delphi I try to use sets over boolean parameters. Then I can easily add new possible set members instead of introducing more parameters.
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.Delphi has overload and default parameters. The problem in article is non existent for a good Delphi programmer.
> Delphi ain't the most elegant language out there
Citation, as they say, needed. :-)
Any anonymous function will do :P
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.
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.)
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.
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.
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).
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...).
There are a couple ways to approach it in Common Lisp, probably a few more than I have here.
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)
...))DEFVAR declares a variable to be special. That causes ALL uses of that variable to use dynamic binding.
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.
Thanks for correcting me. I know there is a way to do something similar to what I had in there, but I don't remember what it was. Any idea?
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.
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.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?
Yes. It's not a bad first-pass, but it can easily lead to problems if there are too many instances of it. In particular, if there is any error in the initial program that's gone undetected or any change to the requirements that it implements, then you have to fix every single copy. Good luck.
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?
Isn't this called Boolean Blindness?
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.
These clever micro-optimizations are a pathology of bored, well-meaning developers.
Could you please elaborate? To me, it looks like the article posits using boolean flags instead of enums is a code legibility issue, not a performance matter. There may still be good reason to reject a large PR such as the one you described. But, I don't get where the micro-optimization appears.
It's a readability optimization, not a performance optimization.
What's wrong with readability optimizations, be they micro- or macro-?
The next person to read that isn't going to say "Wow this is so clever." They are going to say "Wow this guy didn't know about booleans."
Did you just skip over the beginning of the discussion, where everyone agreed that having a bunch of anonymous booleans is bad for readability, and circle back to advocating the status quo that everyone else is trying to improve on?
Everyone doesn't agree that this is bad for readability, and the status quo is the status quo for a reason.
Two, even. Two reasons:
1) Too many languages don't have native enumeration and set types to make a clean and elegant solution even possible.
2) Too many developers don't know how to do it, even if they use a language where it would be possible; some because they couldn't figure it out, but most probably just out of rote habit.
And yeah, sure, not quite everyone. But most, AFAICS. And I haven't seen any coherent argument against it from anyone actively claiming it's bad (Idunno, are there any besides you?), so feel free to try again and contribute something more convincing than your previous attempt.