11 ms·
Get rid of those boolean function parameters (2015)
- 10x-dev 5y agoIntelliJ solves this nicely by showing the parameter name at call sites, effectively making it look like the language has named parameters.
- fxtentacle 5y agoFully agree. No need to change the source code to fix what isn't broken.
- tsimionescu 5y agoWhile 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)`.
- 10x-dev 5y agoThe 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.
- rrobukef 5y agoOTOH, 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?
- Zababa 5y ago> ... 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.
- tsimionescu 5y agoThat 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?
- 10x-dev 5y ago> 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
- im3w1l 5y agoSo 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?
- tsimionescu 5y agoNo 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...
- fxtentacle 5y agoYour 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.
- tsimionescu 5y ago
- CRConrad 5y ago> 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.
- fxtentacle 5y agoThen apparently you need a code review tool which is as clever as the IDE.
- jurip 5y agoThat 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.
- Sjonny 5y agoCode is for humans, not for computers. 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.
- deleted 5y ago[deleted]
- fxtentacle 5y agoWith 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.
- Zababa 5y agoThe 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.
- fxtentacle 5y agoMost 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?
- Zababa 5y agoI 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.
- dhosek 5y agoAlthough, 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.
- Sjonny 5y agoExcept 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.
- 3jckd 5y agoI 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).
- rocqua 5y agoIn 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.
- BenFrantzDale 5y agoIn 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.
- gpderetta 5y agoI 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 };
- rocqua 5y agoThe '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.
- InitialLastName 5y ago>... 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?
- flohofwoe 5y agoAs 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).
- adzm 5y agoThis even works in latest MSVC as well as clang and gcc! https://pdimov.github.io/blog/2020/09/07/named-parameters-in-c20/ https://pdimov.github.io/blog/2020/09/07/named-parameters-in...
- flohofwoe 5y agoYep, 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.
- mistahenry 5y agoKotlin does this, and I find it’s massively improved the readability of our codebase
- amw-zero 5y agoMany newer languages support named function arguments: Swift, Kotlin, etc.
- sbuttgereit 5y agoSome rather old fashioned languages do as well... for example PostgreSQL's PL/pgSQL supports named function arguments, too.
- drath 5y agoThis 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})
- dugmartin 5y agoMy 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.
- seanwilson 5y ago> 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)?
- kevincox 5y agoIn 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.
- stared 5y agoIt 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/Paths https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Pa...).
- cormacrelf 5y agoFrom 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.
- henrikeh 5y agoFwiw, Ada does this too.
- Camillo 5y agoDoes 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.
- henrikeh 5y agoAda doesn't use the "dot syntax"; instead the enumeration literal is written as is without a dot. If I write: 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); Then this is a valid call: Foo( Dec, Dec ) https://godbolt.org/z/eP5qMj3K1 https://godbolt.org/z/eP5qMj3K1 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/html/AA-4-7.html https://www.adaic.com/resources/add_content/standards/05aarm...
- cormacrelf 5y agoIt’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.
- 5y ago
- Joker_vD 5y agoIn 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.
- peoplefromibiza 5y ago> There is no beautiful syntax for destructuring all that stuff with default values, unfortunately. Actually there is one: records with default values. https://erlang.org/doc/programming_examples/records.html https://erlang.org/doc/programming_examples/records.html
- quda 5y agoyou should document your code instead of over-complicating it.
- mtreis86 5y agoThere 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) ...))
- lispm 5y agoDEFVAR 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.
- mtreis86 5y agoThanks 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?
- cassepipe 5y agoWhat 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?
- Jtsummers 5y agoYes. 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.
- debacle 5y agoIf 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.
- nirvdrum 5y agoCould 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.
- debacle 5y agoIt's a readability optimization, not a performance optimization.
- CRConrad 5y agoWhat's wrong with readability optimizations, be they micro- or macro-?
- debacle 5y agoThe 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."
- CRConrad 5y agoDid 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?
- 5y ago
- jameshart 5y agoInform7 - 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 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.
- bjourne 5y agoNamed 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.
- flohofwoe 5y agoA 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.
- treve 5y agoReligiously 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?
- bjourne 5y agoGood 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.
- peoplefromibiza 5y ago> how would you refactor JS's fetch() as @flavius29663 said (https://news.ycombinator.com/item?id=28593669 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));
- magicalhippo 5y agoIn 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.
- unnouinceput 5y agoDelphi has overload and default parameters. The problem in article is non existent for a good Delphi programmer.
- CRConrad 5y ago> Delphi ain't the most elegant language out there Citation, as they say, needed. :-)
- magicalhippo 5y agoAny 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.
- btbuildem 5y agoThis 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.
- dragonwriter 5y agoIn 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.
- tpoacher 5y agoI 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?
- mrslave 5y agoIsn't this called Boolean Blindness?