49 comments

[ 84.5 ms ] story [ 513 ms ] thread
One of the things I wish C# did was allow for inline declaration of enumerations in method parameters.

Something like:

    void DoSomething(Thing thing, enum logging{WithLogging,NoLogging}){}
Because I'd frequently like to be able to make my parameters more explicit, but the overhead of creating a new enum type seems a tad heavyweight.

Edit: Raised it as an issue on GitHub with more details - https://github.com/dotnet/roslyn/issues/3497

That's a nice idea. It'd also limit the scope so it has a short name, instead of something like `DoSomethingLogging`.
Honestly, this is a really nice piece of notation -- I'd really like to see this in C++ (just because that's the language I personally use the most).
Ocaml can do this

  let do_something (thing : Thing.t) (logging : [`With_logging | `No_logging])  =
  ...
Even better, it can even infer the type based on you using it (for example matching on it):

  let do_something thing logging =
    let logging_as_bool = 
      match logging with
      | `With_logging -> true
      | `No_logging -> false
    in
    ....
Why not just use named parameters at the call site instead?

    DoSomething(thing, logging: true);
That's what we usually do in C#.

Required parameters as positional, and optional as named.

doSomething(req_param_1_value, req_param_2_value, optional_param_1 : value, optional_param2: value);

I definitely like this approach in my own code. I think the main advantage of a different approach such as enums is enforcing the explicitness at the call site for all users, especially in library functions.

Another thing to consider: In some cases, it may make more sense to split the function into two versions, one for 'true' and one for 'false'. Then you gain the enforced explicitness. The function could call a private method to share code and pass the Boolean. Clearly that isn't ideal for all situations, but it might help in some situations today.

> Another thing to consider: In some cases, it may make more sense to split the function into two versions, one for 'true' and one for 'false'. Then you gain the enforced explicitness. The function could call a private method to share code and pass the Boolean.

The author describes exactly this approach and implementation, under the heading "Distinct functions".

(comment deleted)
Swift can help with this. There's certain ways to specify that a parameter must be named.
I hate to be that person, but these are lessons in coding that we learn during the 'self documenting code' life lesson. Being part of any team and you'll find yourself doing these sort of changes during code reviews. Not quite refactoring, more spit and polish.
I'm not really sure what the point you're trying to make is; What other purpose would a software blog have than to share information gained through experience?

Is it that the information is not helpful to others, because it is not helpful to you?

I guess I forget that there are a whole bunch of people on here that don't code at all. I think is really is lesson two, the one where you learn about functions and what to name them and about parameters.

It's not helpful to anyone who has written more than a few lines of code. Which is why I replied in surprise. HN doesn't let me down vote, so I comment.

I like to think that stuff on here should represent what hackers (coders) would find interesting. Advanced coding, distributed systems, the other wooly things like marketing, people skills etc. I didn't think this fell under the scope of hacker news. It's not news to any hacker.

I disagree. I've worked with people who have written tens of thousands of lines of code, but didn't see anything wrong with throwing a boolean parameter into the mix. It's not that they were bad developers in other ways. It's just that they valued immediate productivity over long term maintainability.
You'd be surprised how many authors of libraries have never heard of that lesson. A lot depends on how you learned to code and whether or not you were exposed to good programming practices. Universities tend to teach you that in a rote-learning way, which is as if they didn't teach you at all. And self-directed learners tend to encounter best practices at random.

As for the scope, I think the rest of comments here show that this article is indeed in the scope of Hacker News.

Named parameters are nice in this case, e.g. in Python

    def calc_formula(a, b, is_gain=False):
        ...
    
    calc(a, b, is_gain=True)
(comment deleted)
That comes up again and again. For me, named arguments solve that problem.

    float CalcFormula(int a,int b, bool isGain) {..}

    var x = CalcFormula(4, 5, isGain: true);
Yes, named arguments are a nice solution. Espeically if it can be forced (only passable by name).
You may love Ada:

    function Calc_Formula(A, B : Integer; Is_Gain : Boolean) return Float is ...

    X : Float := Calc_Formula(4, 5, Is_Gain => True);
Actually that was 'real' C#, so the language I'm working with does support this feature.

That said, Ada is certainly something I want to learn more about at some point.

I'm mostly coding in Java, which doesn't have named params... but I don't like either extra variable declarations just for this, nor mysterious booleans.

My current habit is to use comments for non-enforced named params; e.g.,

   fetchLatestFoo( long barId, false/*fetchContent*/ );
No compile-time checking, etc., but it still serves the purpose.
I think booleans (flags) are unavoidable at times. Consider a case where you want a flag to switch verbose logging on and off selectively. Writing an enum is a viable solution, but heavy weight nonetheless compared to the primitive boolean-flag based approach.

I agree with the author that with flags in fn-signature, the code becomes tough to reason about at the call-site; but a preferred solution, IMO, is to use "named arguments" or "labeled arguments" in the languages that allow them and/or write proper doc-strings in languages that don't.

For instance in OCaml you'd do (note: specifying types is optional):

    let calc_formula (a :int) (b :int) ~(is_gain :bool) :float =
      if is_gain then
        (* something slightly different *)

      (* common stuff *)
You'd call the above as:

    calc_formula 10 20 ~is_gain:true
Optional arguments allow for a terser call-site syntax at the cost of implicit behaviour:

    let calc_formula ?(is_gain=true) a b  = ...
You could then call the above fn:

    calc_formula 10 20
    calc_formula 10 20 ~is_gain:false
> I think booleans (flags) are unavoidable at times. Consider a case where you want a flag to switch verbose logging on and off selectively.

In my experience, even these situations tend to grow arms and legs pretty quickly, so you end up with a 'verbose' boolean that accepts four values: True, False, "superverbose" and "off". Then you notice that in some places the function is being called with an array as the value for 'verbose', because you're in a language where arrays are implicitly cast to Boolean (I'm looking at you python).

There are too many cases where the optional parameter is not really a boolean, but merely a sum type which coincidentally only has two members at the moment, but is likely to grow to more members.

I have seen these cases crop up myself. But... as Martin Fowler would say, YAGNI... you ain't going to grow arms and legs...
For that situation the least-smelly answer is logger classes with a severity method

  logger.error("message")
or a log statement with a severity parameter

  logger.log(LOGLEVEL.ERROR, "message")
That lets you change the capture level based on the situation without the need to recompile your application, on a fine granularity (eg class-level). Getting errors in production? Edit one line of XML to turn up the capture level and go.

High-performance logging frameworks like SLF4J handle the first case very intelligently, there's almost no overhead to skipping a logging call. If used wisely with a high-performance logging engine (Log4j2 or Logback) there shouldn't be many calls needed, and except in very narrow situations the performance impact (if you could find one) would be drastically outweighed by the improvements in maintainability. Code quality does have its costs at times - otherwise we'd all be writing assembly. It's not going to be the straw that breaks the camel's back when it comes to performance.

Even simpler: Just use a const variable instead of a constant literal in the caller code:

  const bool useWayA = true;
  int result = foo(a, b, useWayA);
instead of

  int result = foo(a, b, true);
This would essentially be the enum option: giving names to constants. But this way wouldn't enforce the convention, you could still use `true` or `false`.
Even in PHP (no named arguments):

    class Route
    {
       const RECURSIVE     = true;
       const NON_RECURSIVE = false;

       public function __construct($pattern, $recursive = Route::NON_RECURSIVE) {
         // ...
       }
     }
     $route = new Route($pattern, Route::RECURSIVE);
Why not

   $route = new Route($pattern, $recursive = true);
My guess is: The fact that Booleans only encode a single distinction fools us into thinking we can be sloppy with naming their values or the places those values are stored (not just function parameters). In any given context, that distinction may seem trivial.

It takes a good deal of practise to know from experience how wrong that would be. So take my advice and always have a copy of this article under your pillow :)

So I get that

    calc_formula(a, b, false) 
Is unclear but so are `a` and `b`

    ctx.arc(100, 200, 300, 0, Math.PI*2, false)
or

    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 100, 200, 0,
                  gl.RGBA, gl.UNSIGNED_BYTE, null);
How are those examples any more self documenting? I don't see anything special about boolean anymore than the other arguments. If you really want your code to be clear (and I'm not saying I do this) you'd need to either use a language that allows naming every argument or else put every argument in a variable with a descriptive name

    var centerX = 100;
    var centerY = 200;
    var radius = 300;
    var arcStartRadians = 0;
    var arcEndRadians = Math.PI * 2;
    var counterClockwise = false;
    ctx.arc(centerX, centerY, radius,
            arcStartRadians, arcEndRadians,
            counterClockwise);
I'll say that I do that (define constants to variables/consts/#DEFINE/etc and then use them as arguments), and I do it for exactly the reasons you describe. I also try to make magic numbers as self-explanatory as possible - rather than (say) use the number 84600 I'll write 60 * 60 * 24 for when I want the number of seconds per day.

It's way easier to remember what's going on when you come back a year later, and all but the simplest compilers will be smart enough to bash it down to constant parameters if that's possible.

Yeah, more lines of code, but I think readability trumps terseness (within reason). Some functions just use a bunch of parameters and it's not really the extra line to name the parameter that's causing the issue. Also if you're using an editor that does code folding you could just fold that block away.

I'm not sure if the transposition was intended, but your comment shows another reason why using a calculated value here is good.
Personally, I tend to put the function arguments' names from the API documentation in comments if its kind of obscure or I guess that I'll forget if I need to revisit this piece of code. As in...

    texImage2D(100, 200, 300,
        0 /*start*/, Math.PI*2 /*end*/, /*ctrClockwise*/ false);
Doesn't Smalltalk have a convention whereby the name of the function specifies the purpose of the variables, like

    arcWithCenterX: withCenterY:
or similar? (I vaguely recall this, but can't find any examples online.)
Yeah, and Objective-C and now Swift inherited that same convention.

Objective-C:

    [self frob:param1 withObject:param2];
Swift:

    self.frob(param1, withObject:param2)
after going through and starting to learn Swift, I really wish

  self.frob(param1, withObject:param2)
could be written as

  self.[frob:param1 withObject:param2]
In an old job, I saw some code like this

    refunder.Refund(orderId, customerEmail, true, false, payment=="visa", order.shipping == 0, true, true, order.day == "sunday")
Not verbatim, but you get the idea -- and yes, the refund method was even more confusing than then invocation
Ugh, what about avoiding flags entirely? I think "slightly" different behaviour is a smell of god-functions. Why not split the functionality in two, perhaps composable, functions?
This would be a great case for pattern matching in Elixir.
Wouldn't the use of named parameters solve the confusion aspect that's cited as the reason to not use boolean parameters? Not that the use of enums or distinct function wrappers is a bad approach (it's probably more elegant), but the hash approach does eliminate quite a bit of ambiguity.

With languages that include full pattern matching support and atom/symbol literals (like Erlang and friends, for example, and like most other functional languages IIRC), you could alternately match on a particular symbol instead of relying on a boolean flag.

the problem with the "distinct functions" case is if end up doing this:

  if is_gain: calc_formula_is_gain(a, b)
  else: calc_formula(a, b)
However a completly agree it makes sense with sin/cos functions.
Or hover your mouse over the call, and see the prototype in a popup. Its maybe easier than visually parsing that complex enum form.

The problem with enums as class members is, they have to be scoped when used. I wish that method invocation included the scope of the class being called on.