Hacker News new | ask | show | jobs
by alextgordon 3826 days ago
Other languages seem to get it right. Even C++!

    #include <vector>
    static int foo(std::vector<int> x = {}) {
      x.push_back(10);
      return x.size();
    }
    int main (int argc, char const *argv[]) {
      printf("%d\n", foo()); // 1
      printf("%d\n", foo()); // still 1
      return 0;
    }

    def foo(x=[]):
      x.append(10)
      return len(x)
    
    print(foo()) # 1
    print(foo()) # 2? wtf?
4 comments

C++ its own insane behavior, where default arguments are determined at the call site instead of at runtime. This means your function can get another function's default arguments.

    #include <iostream>
    struct Base {
        virtual void foo(const char *name = "base") {
            std::cout << "Base impl with " << name << " param\n";
        }
    };
    
    struct Derived : public Base {
        virtual void foo(const char *name = "derived") override {
            std::cout << "Derived impl with " << name << " param\n";
        };
    };
    
    int main(void) {
        Base *b = new Derived();
        b->foo();
    }
prints "Derived impl with base param"

Fortunately Python avoided that bit of silliness.

This behavior is pretty reasonable to me.
ES6 got this right too.

This design decision makes me die a little every time I have to do:

  def foo(x=None):
      if not x:
          x = []
      ...
That will get you into trouble as well, please use:

    if x is None:
        x = []
... etc.
Or even "prettier":

  x = [] if x is None else x
I'm partial to:

x = x or []

Only works when x is truthy for all legal x values, excluding x=0.
I'm not familiar enough with the innards of other languages to make any definitive comments on it, but I will say that the challenge is in combining a high-level language with late-binding closures with memoization. I'm not aware of a strategy for that which is always intuitive for every program you write; I think it's just a language design combination that has a very high probability of developing "gotchas" no matter what you do.

Also, on a related note, I really love late binding and memoization, so I'm definitely biased on the side of "I understand why the language designers made this decision, and though the ramifications bother me a little bit, I much prefer this way".

On a third note (because hey, why not), with type hints being a thing now, maybe it would be worth considering a default eval hint. It could default to current behavior, and therefore retain backwards compatibility, but you could add syntax to declare defaults as new variables. That might be a smart compromise.

Small tip: you can shorten your snippet by completely removing main's parameters and the return 0 and still have a valid c++ program.
I did pause to think about this, but sod's law dictates that if you remove the parameters to main, you will have to add them back in again. :)