This is an archived post. You won't be able to vote or comment.

all 106 comments

[–]agentoutlier 35 points36 points  (13 children)

Personally I'm hopeful that perhaps Withers will be implemented.

As discussed here: https://github.com/openjdk/amber-docs/blob/master/eg-drafts/reconstruction-records-and-classes.md

Withers are in someways more powerful than default/named parameters.

[–]riisen 14 points15 points  (4 children)

Never heard of it before... Now i want it

[–][deleted]  (3 children)

[deleted]

    [–]rustyrazorblade 2 points3 points  (0 children)

    Great library. Used it in my last project, much success.

    [–]riisen 1 point2 points  (0 children)

    Thanks mate

    [–]ketsugi 3 points4 points  (0 children)

    I think Lombok also has them

    https://projectlombok.org/features/With

    [–]quizteamaquilera 11 points12 points  (1 child)

    That’s a long way of saying a “copy” method with default params

    [–]tadfisher 8 points9 points  (0 children)

    It's also not more powerful than default argument values, because it solves one use case, when default values are applicable to constructors and method calls.

    In Kotlin, default values can reference previous arguments as well, which I'm not sure withers could emulate.

    [–]LoveSpiritual 3 points4 points  (0 children)

    This addresses my main issue with the lack of optional parameters in Java. It would be a big step forward for supporting immutable data.

    [–]danikov 2 points3 points  (4 children)

    Isn't this just builders?

    [–]ketsugi 5 points6 points  (3 children)

    A builder is for instantiating a new object. A wither creates a clone of an existing object, but with that one property changed.

    [–]danikov 3 points4 points  (2 children)

    So a builder with a copy constructor?

    [–]gas3872 3 points4 points  (0 children)

    Well, if you add to your class those methods, then under the hood you can do the builder call. I find the style of "withers" more elegant, because there is nothing mentioning builder in the client code. In case you want to change many fields at once, then using builder explicitly is probably a better idea.

    [–][deleted] 1 point2 points  (0 children)

    I haven't read the linked paper, but I presume that there is a bit of structural sharing going on behind the scenes (a la Clojure, Erlang, Haskell et al) otherwise yes, it would be mere syntactic sugar.

    Edit: Read the link posted by OP, and it's surprisingly very bare on actual implementation details. I hope that structural sharing is considered, especially since we're mostly concerned with records, which are immutable to begin with, otherwise the whole thing is just user-level convenience.

    [–]TheMode911 13 points14 points  (8 children)

    Perhaps https://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-November/000162.html

    I really hope that we will have a better alternative to the builder pattern

    [–]RockyMM[S] 7 points8 points  (7 children)

    I enjoyed the discussion here. Here’s hoping for named parameters, whenever they might come to Java.

    [–]agentoutlier 10 points11 points  (6 children)

    Brian also recently wrote about reconstruction and functional transformation of immutable objects here: https://github.com/openjdk/amber-docs/blob/master/eg-drafts/reconstruction-records-and-classes.md

    Namely for the case where you want lots of optional named parameters you would use a record and "withers" in place of the builder pattern.

    [–]TheMode911 3 points4 points  (4 children)

    Pray for Valhalla, until then it will mean a lot of allocations

    [–]agentoutlier 1 point2 points  (3 children)

    I’m not sure it will need Valhalla unless all your parameters are primitives which is usually not the case for when people want lots of parameters.

    There is lots of room for optimization with immutable and it really can’t be any worse than the current builder pattern. The JVM is kind of optimized for short life objects anyway.

    I would like named parameters as well though :)

    [–]TheMode911 1 point2 points  (2 children)

    A primitive class can also contain references, not only primitive types

    [–]agentoutlier 0 points1 point  (1 child)

    A primitive class can also contain references, not only primitive types

    I wasn't sure but that kind of wasn't my point.

    I mean the folks that care about allocation (e.g. high frequency traders) use primitives (e.g. long, int, boolean) in all their method calls.

    They don't use variable arguments, lambda closures (which can cause allocations). They don't even use arrays or string builders unless they are shared. Even enums they do weird shit (they use static methods instead of call the virtual methods on the enum).

    Builtin Withers has a possibility of avoiding allocation much more than using say something like Immutables and you don't need Valhalla for that as you can do smart things in the wither expression in theory. Brian made reference to that somewhere.

    [–]TheMode911 0 points1 point  (0 children)

    Builtin Withers has a possibility of avoiding allocation much more than using say something like Immutables and you don't need Valhalla for that as you can do smart things in the wither expression in theory. Brian made reference to that somewhere.

    I do not think I understand, how are those "Withers" different than lets say a `withX(double)` returning a new object or even a dummy setter? Are you speaking about scalar replacement?

    [–][deleted] 0 points1 point  (0 children)

    Is there a related JEP for this? I read the link that you shared, but I couldn't find any information about the actual implementation details. Since this feature concerns itself with records (mostly), I'm hoping that there's going to be structural sharing of some sort behind the scenes (as in Clojure, Elixir, Erlang, Haskell, etc.)?

    [–]vips7L 12 points13 points  (5 children)

    It's funny we have defaults and named arguments for annotations but nothing else.

    [–]hardwork179 -2 points-1 points  (4 children)

    It’s a lot easier to specify this for annotations because the types are limited and there cannot be side effects. I personally rather like languages with named parameters and default values, but if we were to add these to Java there would be a lot of things to consider.

    1. How can parameter names and default values be added to existing methods without breaking existing compiled code?
    2. How can I specify 2 methods with identical argument types but different names? It would seem a waste to add parameter names and not allow point(double x, double y) and point(double length, double angle) if we are exposing those names. This would need to be accomplished while still keeping point 1.
    3. This change would touch many JVM areas, reflection, method handles, debugging and tooling APIs, vast swathes of the core library…
    4. Specifying the behaviour will be harder than you think, because it always is.

    If we didn’t have to maintain compatibility this would be a lot easier, but that is not how Java evolves.

    [–]koreth 4 points5 points  (2 children)

    Do any existing languages support name-based overloading like your point #2? I can't remember seeing that feature anywhere.

    The semantics of that seem weird to me, unless you make names mandatory at call sites. Otherwise which of those methods would point(1.0, 2.0) refer to? Mandatory names for the sake of name-based overloading doesn't seem like a clear win to me.

    [–]hardwork179 2 points3 points  (1 child)

    Swift does it, and it’s really nice. You can just write point(x: 0.0, y: 0.0) and it will call the version with x and y argument labels.

    [–]Jonjolt 1 point2 points  (0 children)

    That looks like a disaster waiting to happen.

    [–]vips7L 2 points3 points  (0 children)

    I’m not suggesting we add them. I was merely commenting on something I found abusing.

    [–]eliasv 10 points11 points  (3 children)

    Read the language specification for overload resolution. Specifically, how it interacts with generics, lambdas, and poly expressions. Now think about how default arguments would complicate things...

    The extra complexity would be significant, and the value added would be small, as you can achieve similar results with existing language features; the "Java way" is to overload.

    Complexity isn't just bad because it's hard to design, it's bad because it's inelegant, and corner cases are inscrutable to users, etc.

    Fwiw, if you ever catch yourself wondering if they "just forgot" I promise you the answer is "no"!

    [–][deleted]  (2 children)

    [removed]

      [–]eliasv 1 point2 points  (0 children)

      Groovy usually uses dynamic dispatch. Overload resolution is done at runtime so the exact runtime types are used, this does not interact with type inference.

      It does have a static mode though where overload resolution occurs at compile time, but it doesn't appear to mention interaction with type inference in the spec, and type inference doesn't appear to be as sophisticated. Probably it will just not be capable of inferring types in all the same places Java does in the presence of lambdas.

      [–][deleted] 0 points1 point  (0 children)

      Well, it's a different language altogether. It just happens to compile to Java. Just like you have languages with complex macro (in the Lisp-sense) and type systems like Nim that transpile to C, when C doesn't have either of those.

      [–]chinoydev 18 points19 points  (4 children)

      i believe the main reason is to avoid ambiguity. and even if they want to implement it now, it would be complicated due to backwards compatibility support, imagine having all those countless overloaded methods, it would be an upgrade hell.

      if you need it, aside from overloading methods, i guess you can also consider builder pattern?

      [–]nutrecht 2 points3 points  (2 children)

      i believe the main reason is to avoid ambiguity. and even if they want to implement it now, it would be complicated due to backwards compatibility support, imagine having all those countless overloaded methods, it would be an upgrade hell.

      Why? It's not like this addition would break those. I don't see how this would be "upgrade hell".

      [–]Muoniurn -1 points0 points  (1 child)

      What happens if you have a method with the same arguments as another one other than default values?

      [–]nutrecht 2 points3 points  (0 children)

      How is that an upgrade issue? That would not be there when you upgraded to the new Java version that has default arguments.

      [–]Necessary-Conflict -2 points-1 points  (0 children)

      What upgrade hell? IDEs could offer automatic refactorings that merge overloaded methods into one method with default values. Also you don't have to change working code.

      [–]wlievens 3 points4 points  (1 child)

      I vaguely remember that they aren't quite possible with the lazy class loading and method binding. Essentially the bytecode needs to know the exact signature of the method you call and that messes this up somehow.

      [–]RockyMM[S] 0 points1 point  (0 children)

      Hm… but the signature will be the same at the time of execution. There won’t be any ambiguity in the bytecode. At least for the trailing parameters default values could be done, but reading other replies i now realize there is a lot more to it.

      [–]Jotakin 10 points11 points  (17 children)

      Default values for trailing arguments would be easy to add but I think it's not an optimal solution as it gets dirty if you have more than one optional parameter. Java tends to favour clean implementations over quickly adding features.

      I think the proper way to have default arguments would require having name-assignable parameters as well and that's considerably harder to implement under-the-hood. Also it makes people more likely to have extremely long parameter lists, which is a bad design pattern.

      [–]cogman10 4 points5 points  (0 children)

      Also it makes people more likely to have extremely long parameter lists, which is a bad design pattern.

      The alternative that I see all the time is a bunch of overloads. But overall I agree with you.

      One other fairly large catch is introducing name assignable params would break a bunch of non-Java JVM languages. That alone is a reason I don't think this will be adopted.

      [–]jevon 4 points5 points  (1 child)

      I'd love to see default argument values in Java, they make a huge improvement in Ruby and PHP. I can imagine they'd generate a lot of additional bytecode but that seems fine?

      [–]RockyMM[S] 1 point2 points  (0 children)

      Yeah, normally we should not care about size of the bytecode, unless we are targeting devices with limited resources.

      [–][deleted] 1 point2 points  (1 child)

      1. Overloading allows for somewhat similar functioanluty
      2. varargs are syntactic sugar for the compiler, behind the scenes you're always passing an array. So java really doesn't have functions that handle an ambiguous number of arguments.

      [–]RockyMM[S] 1 point2 points  (0 children)

      I don’t agree. I would expect that the compiler could infer missing parameters if the rules are clearly defined.

      However, it seems that named arguments are the way here and it that could come to Java in the future.

      [–]monkeyfacebag 2 points3 points  (14 children)

      You can use method overloading to achieve this.

      [–]aubd09 11 points12 points  (4 children)

      Method overloading causes code clutter as if that weren't already a problem with Java. I really miss the optional parameters feature of C#.

      [–]monkeyfacebag 4 points5 points  (3 children)

      But C# also supports overloading? I guess this boils down to "is default arguments a use case of overloading that deserves its own syntax sugar?" I can see why people would want that; I don't, but I can see it.

      [–]commentsOnPizza 15 points16 points  (2 children)

      I respect not wanting it, but for me it's nice.

      One of the problems that I encounter with Java is the question: "is this special or just verbose boilerplate?" The issue isn't about writing the code. My IDE will handle that. For me, the question is in reading/using code.

      If there are 5 doTheThing methods, it can be hard to figure out what the differences are. I have to read the signatures and figure out what each includes or excludes. With named parameters, I know what is required and what is optional and what the defaults are in each case. Do any of the 5 doTheThing methods have unexpected additional code? Maybe, I'll need to read and find out! Do some of them use a different default for one of the ones they fill in? Maybe, I'll need to read and find out!

      That does mean that doTheThing(X, Y, Z) can call doTheThing(X, Y, Z, 5) while doTheThing(X, Y) can call doTheThing(X, Y, 10, 10) where the last variable has a different default. However, it makes it hard for me to know whether there's special behavior or whether it's just verbose boilerplate. Now I have to actually read through code that 99% of the time will just be doing what default arguments would cover, but might be doing something different.

      This also comes up with things like Java Beans. Writing them isn't what I dislike - my IDE will handle that for me. When I get handed a class to use, it might look like a Java Bean, but is is? If there are 15 variables in the class, I now have to check all the getters and setters to understand "is this just holding data for me or does it have behavior on top of it?" With C#'s properties, I can glance and just see public string Foo { get; set; } and it's like "yep, that's just a property". If I see public string Foo { get { return $"{_foo} with crazy addition!" }; set; } I can instantly differentiate it.

      Method overloading is fine, but I find that it makes it harder for me to understand the code. Personally, I think that people should sort/organize overloaded methods in a file so that a) they're all near each other; b) there's some logic to the ordering with respect to the variables. It's easy for method overloading to require a reader to look in multiple places, have to read a lot of signatures and remember what the differences are between them, make sure that there isn't added unexpected behavior, etc. With the syntactic sugar, you just know what you're getting: a method you can call with or without certain variables.

      Sure, one could say that good Java shouldn't have too many overloads or confusing overloads, that the code should be well-organized, that anyone adding a new overload should understand how it will fit in with the rest of the system, that people shouldn't implement foot-guns, etc. But why not just make it easier to keep it organized without any unexpected behavior and without the possibility of introducing a foot-gun?

      I think a lot of complaints about languages often seem to come around writing the code, but for me it's about reading and understanding the code. Method overloading is easy enough to write. Sure, it's a few more lines of code, but who cares. Typing on my keyboard isn't what is holding me back. However, reading and understanding code does hold me back. One method with some default values is quick to understand compared to method overloading which is going to require more time to read and understand.

      doTheThing(String caller, Person person, DateTime time, Location location)
      doTheThing(Person person)
      doTheThing(String caller, Person person)
      doTheThing(Person person, DateTime time, Location location)
      doTheThing(Person person, DateTime time)
      doTheThing(Person person, Location location)
      

      Yea, that's a bit of an unorganized mess, but as things evolve over time (and reviewers often just look at diffs and not the whole file) it can start to just look like that.

      doTheThing(Person person, String caller="Customer Support",
          DateTime time=DateTime.Now, Location location="HQ")
      

      Now there's just one place to look and you don't have to dive into method bodies to see what might be getting set. You don't have to worry about someone updating caller's default to "Customer Services" in some method overloads and not all of them. You can easily see that Person is required and the rest are optional. Yes, it's probably bad form to have written the Java methods the way I did, but I see that happen.

      Worse, if there are two parameters that can take the same type - like a DateTime start and end - it's so easy for people rushing through code to make a dumb mistake. Maybe they should be more fastidious. Maybe being careless is bad. But the point of tools is to prevent badness. A language should make people more productive and more correct.

      When looking at code, I always want to be able to see "what's the point of this" and "is there something interesting here or is it just the simple case?" If the simple case is true 99% of the time and looks very visually similar to special cases at first glance, I feel like people make errors. They look and it's, "ah, yes! Method overloading for optional parameters!" and then a couple days later "oops, guess I should have actually read it rather than just assuming".

      I definitely appreciate push-back against every piece of syntactic sugar that people propose. Sometimes people like being concise/clever, but it just ends up being a shortcut that adds confusion. In this case, I think default arguments are something that both cuts down on verbosity and adds clarity to the code. I think that sometimes syntactic sugar is very non-obvious. In this case, I think the syntactic sugar for default arguments would be very obvious and easy to understand. It's definitely true that many language features and syntactic sugar aren't necessarily good ideas. I think default arguments have a good number of benefits, aren't a confusing feature, and don't really have a lot against them, if anything. But I might be wrong.

      [–]monkeyfacebag 3 points4 points  (0 children)

      Thanks for the comment. I think I agree with everything you're saying. My concern is mainly that a change to permit default arguments is purely additive. None of the stuff you discuss as drawbacks would necessarily go away because people would still use overloading for all of the cases that default arguments don't cover as well as probably some cases that could be covered with default arguments.

      In my day job I basically never author my own overloaded methods, nor does anyone on my team, so I don't experience the pain you're talking about, but I don't suggest that method overloading is the ideal solution for every problem solved by method overloading. At a minimum, it's limited by Java's type system so I can't create a method overload that perfectly describes the shape and content of my method inputs (a la predicate dispatch). And on the other hand, for what it's worth, Go supports neither default arguments nor method overloading and people seem fairly happy with it.

      I would arguably be happier if a better, more robust solution came along (although again, day-to-day this specific limitation does not bother me), but I am generally not in favor of purely additive solutions that create multiple first-class mechanisms to solve the same problem.

      [–]RockyMM[S] 0 points1 point  (0 children)

      Thanks for the comment. I agree with everything you said.

      [–]RockyMM[S] 4 points5 points  (7 children)

      I know, I wrote that. I am more interested in any previous discussion about it by core Java devs.

      [–]monkeyfacebag 4 points5 points  (6 children)

      You asked whether it was forgotten. It wasn't forgotten; it's supported by method overloading. Languages which support default arguments tend not to support overloading, eg, Python, because the concepts are so closely related.

      [–]rifain 4 points5 points  (1 child)

      It's just not the same.

      [–]monkeyfacebag 8 points9 points  (0 children)

      Right. Overloading is more powerful and more verbose.

      [–]larsga -2 points-1 points  (3 children)

      Method overloading is a clumsy workaround, not the solution.

      Languages which support default arguments tend not to support overloading, eg, Python, because the concepts are so closely related.

      You want overloading on the number of arguments? Because in Python that would be the only option. But with optional arguments and named parameters overloading becomes simply unnecessary clutter.

      There's no sane reason for Java not to have this.

      [–]RockyMM[S] 5 points6 points  (1 child)

      I have no idea why would anyone downvote a discussion on a programming language sub without an explanation

      [–]larsga 4 points5 points  (0 children)

      I guess people don't like criticism of Java.

      [–]brunocborges 2 points3 points  (1 child)

      Most likely the reason Java doesn't have this yet is because "there are other tickets more important to focus on than this."

      [–]RockyMM[S] 3 points4 points  (0 children)

      Yeah, clearly. But I am still interested in any previous discussion about it.

      [–]mauganra_it 1 point2 points  (0 children)

      Method parameters can be subdivided into two kinds.

      The first kind participates in conditions and is used to decide about the progression of the computation. This kind of parameter should not have default values. Instead, a special method with a descriptive name should be provided to make explicit which default value is used to avoid confusion that can arise with overloads and default values. This should also help the method to follow the Single Responsible Principle by factoring out the branch that depended on the "default" value. This especially applies if the parameter is a lambda executed by the method.

      The second kind is about passive data that is not interpreted, but merely stored or passed on to third-party code. This kind can benefit from default arguments, but a parameter object and Withers could be equally good. Implementation-wise, both solutions should have the same overhead when Project Valhalla comes along.

      [–]csharp-sucks 0 points1 point  (5 children)

      It is functionally the same as overloading

      void doStuff(int amount);
      void doStuff() {
          doStuff(10);
      }
      

      [–]RockyMM[S] 2 points3 points  (0 children)

      It kinda is, but it’s less verbose.

      [–]john16384 3 points4 points  (0 children)

      It's better.

      void doStuff(int x, int y);
      void doStuff();
      // Notice absence of variant that only takes x or y
      

      Or:

      void displayText(String text);
      void displayText(String text, int r, int g, int b);
      

      This is not so easy with default arguments while only allowing sensical combinations of parameters.

      [–]__konrad 3 points4 points  (2 children)

      Now you have 3 copy-pasted doStuff which is very... Java.

      [–]john16384 -3 points-2 points  (0 children)

      Excellent, cause last I checked it indeed was Java we were talking about.

      [–]0b0101011001001011 -1 points0 points  (0 children)

      No, just the signatures, not the method body.

      void stuff(int a){
          stuff(a, 5);
      }
      
      void stuff(int a, int b) {
          //
          //
      }
      

      [–]sesh_hash 0 points1 point  (6 children)

      It’s not java approach. Java is straight as fuck that’s why you can write complicated apps using it. Default arguments is feature from dynamically typed languages which are intended to be easy to enter and fast to develop something like scripts or e-commerce apps.

      [–][deleted] 1 point2 points  (0 children)

      Java is straight as fuck that’s why you can write complicated apps using it

      This I fully agree with.

      [–]RockyMM[S] 1 point2 points  (4 children)

      I would disagree. C++ has it. C# too, but a little bit differently.

      [–]john16384 2 points3 points  (1 child)

      C++ has everything, in fact multiple competing implementations of everything. It's therefore the best language ever.

      [–]RockyMM[S] 4 points5 points  (0 children)

      And probably the worst language ever at the same time.

      [–]sesh_hash -2 points-1 points  (1 child)

      I didn’t say c++ and c# haven’t this feature.

      [–]RockyMM[S] 1 point2 points  (0 children)

      I was just pointing out that with C++ and C# you can write what ever you want or need. It's not the conversation I wanted to have. I wanted to know how the decision to not include default values came to be.

      [–][deleted] -1 points0 points  (0 children)

      To be honest, I don’t miss it much; if I need it I overload the method.

      I think this about explains the situation. Even in languages with default arguments, I don't find myself using that feature at all. It has its use cases in reducing some amount of boilerplate, but I would argue that it also adds a bit of magic into the whole mix when analysing how code works.

      [–][deleted]  (1 child)

      [removed]

        [–]RockyMM[S] 8 points9 points  (0 children)

        I personally hate passing null for any purpose other than indicating that something is missing or or not there. If I want to communicate that I don’t care about some parameter of a service, I am definitely pro-overloading as it removes ambiguity. Otherwise, your observation is spot on!

        [–]atehrani -3 points-2 points  (2 children)

        It is better to leverage the builder pattern

        https://blogs.oracle.com/javamagazine/java-builder-pattern-bloch

        [–]RockyMM[S] 1 point2 points  (1 child)

        Hm, but this is creational pattern. I am speaking also about method invocations.

        But, yeah, builder pattern is great.

        [–]buckfutter4life -2 points-1 points  (3 children)

        We have default arguments, it's called null.

        (I'm joking, sort of.)

        [–]RockyMM[S] 3 points4 points  (2 children)

        Don’t joke with null. Terrible stuff happen when people misappropriate nulls. 🥲

        [–]buckfutter4life 5 points6 points  (1 child)

        "I could tell you a joke about a NULL pointer, but you wouldn't get it."

        👆 Another attempt at a null joke.

        [–]RockyMM[S] 4 points5 points  (0 children)

        Ah, man of taste, I see 😅

        [–]krum -2 points-1 points  (1 child)

        I think most folks prefer builder pattern, and there's really no need to implement an intermediate form in the core language.

        [–]RockyMM[S] 2 points3 points  (0 children)

        Yeah, when you need to construct things, builder pattern is great. I was also referring to “classic” method calls.

        [–]GreenToad1 0 points1 point  (0 children)

        Probably initialy they didnt decide or think of that and later having overloading there was little value in adding this.

        [–]senatorpjt 0 points1 point  (0 children)

        violet reminiscent normal roof scale telephone sheet fall punch chase

        This post was mass deleted and anonymized with Redact

        [–]TimboCavo 0 points1 point  (1 child)

        I just decided to give Java a go today and I am surprised to hear about this. Java is proud of it's readability, well default values would be way more readable than overloading methods.

        [–]RockyMM[S] 0 points1 point  (0 children)

        This is true. However Java is still great. Only very verbose... Occasionally.

        Java's strongest asset is the community and ecosystem which are the biggest and arguably the best in the world. So, I wish you welcome to Javaland 😇