Other people disagree with your claim that it is isomorphic, and they give arguments for that counterclaim. You can't win an argument by just repeating what you said before.
Concretely: if this is isomorphic, then there should be something like this and this in Rust, after being transported through the isomorphism. So, where in Rust do we have the equivalent of these rules?
There's been several reports from both sides of the issues here. I'd like to encourage everyone to take a few steps back.
For @Ichoran and @yigal100 in particular, please try to focus on how this RFC pertains to Rust. RFCs can be supported by existing art, but it's not productive in the pre-RFC phase to argue against the proposal unless there is hard, concrete, and unavoidable reasons that the RFC shouldn't be considered. It's the point of the RFC phase for review for acceptance. Pre-RFCs should be focused on the drafting, making sure the RFC as a proposal is well written, even if you disagree with the proposal.
It's unfair to call the syntax unintuitive and boilerplate-y if you don't even understand it. Please read the documentation for functions in Swift here.
The syntax is very simple:
func foo(param: Int) { }
foo(param: 5)
func foo(_ param: Int) { }
foo(5)
As this example shows, the underscore simply indicates that the parameter has no argument label (the identifier used by the caller). It's quite a stretch to claim that the addition of TWO characters (the underscore and the space) is boilerplate-y.
Calling it unintuitive is fair, IMO, since it is not something one will have seen in other languages. So it makes Swift code less readable for people coming from other languages. Of course all languages have things like that, but doing it for something as common as argument types is not necessarily a good idea.
Just a note: boilerplate doesn't need to be long. If you write the same thing every time (and @digama0 apparently had no recall of ever seeing anything but), then it's boilerplate.
Mostly, boilerplate is communicating information that you're comfortable assuming, but the other systems involved aren't, because they're supporting things that you aren't considering. Thus, you consider it boilerplate ("I just have to write this for no reason") while from a different perspective it's justified.
mut for bindings in Rust is boilerplate. It would be completely possible for the compiler to just make all bindings mut and nothing would change. Except things would change... but only if you're familiar with what leaving off the mut means.
What really makes something boilerplate-y is if the normal case requires writing more than the uncommon case. If the common case is unnamed arguments (as in the code @diagama0 had been exposed to), then specifying that the arguments should be unnamed is boilerplate. If the common case is that arguments are named (as is the main Swift naming conventions), then writing arg arg is boilerplate. This is IIRC a change that was made in Swift development, in that the first function argument used to be unnamed by default, and this was changed to match other arguments to avoid the need to write arg arg to get a named first argument.
It's still the case as I recall that most first arguments (of free functions) are still unnamed[1], so you could argue that requiring them to be _ arg is boilerplate, where the language could default to the common case, or you can argue that consistency is better in this case. Both are supportable positions.
verb(_ object: Type, preposition object: Type)... though this is perhaps better written as (object: Type).verb(preposition object: Type)? ↩︎
This is one component, but this alone is a terrible definition. Boilerplate refers to something much more specific than mere repetition. Based on this, every single construct in the language is boilerplate, assuming it occurs more than once in your program. Every keyword is boilerplate; types are boilerplate; enums are boilerplate; variables are boilerplate; control flow is boilerplate; ownership is boilerplate; references are boilerplate. And it doesn't matter how these constructs are used; the mere fact that they occur more than once in your program makes them boilerplate, according to this definition.
I mostly agree that this principle is part of the definition.
However, this principle does NOT apply to mut. Consider the following example:
let mut y = 10;
mut is not "communicating information that you're comfortable assuming". mut communicates that y is mutable; there's no way to assume that y is mutable without adding mut.
And there is a reason you have to write this: it's to tell the compiler that the variable is mutable ("I just have to write this for no reason").
Now consider this example:
let point: Point = Point::new();
In this example the explicit type annotation does "[communicate] information that you're comfortable assuming". A human can already assume that the type of the expression Point::new() is Point, so the the explicit type annotation is boilerplate in this case, and exemplifies the aforementioned principle.
Yes. And in that case, explicitly adding mut would be boilerplate. But the compiler doesn't do this, so I fail to see your point.
This makes no sense. If the compiler made all bindings mut, then the program will change whether you're familiar with what leaving mut off means or not.
No, this has nothing to do with whether or not something is boilerplate-y. I don't know where you got this from. Can you cite references for this claim?
In computer programming, boilerplate code, or simply boilerplate, are sections of code that are repeated in multiple places with little to no variation. When using languages that are considered verbose, the programmer must write a lot of boilerplate code to accomplish only minor functionality (emphasis added).
I encourage you to read the full article. In particular, read about the history of the term.
In Java programs, DTO classes are often provided with methods for getting and setting instance variables. The definitions of these methods can frequently be regarded as boilerplate. Although the code will vary from one class to another, it is sufficiently stereotypical in structure that it would be better generated automatically than written by hand. For example, in the following Java class representing a pet, almost all the code is boilerplate except for the declarations of Pet, name, and owner:
Java
public class Pet {
private String name;
private Person owner;
public Pet(String name, Person owner) {
this.name = name;
this.owner = owner;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getOwner() {
return owner;
}
public void setOwner(Person owner) {
this.owner = owner;
}
}
Most of the boilerplate in this example exists to fulfill requirements of JavaBeans. If the variables name and owner were declared as public, the accessor and mutator methods would not be needed.
To reduce the amount of boilerplate, many frameworks have been developed, e.g. Lombok for Java. The same code as above is auto-generated by Lombok using Java annotations, which is a form of metaprogramming:
@AllArgsConstructor
@Getter
@Setter
public class Pet {
private String name;
private Person owner;
}
But this means that we can't use named arguments when we are destructuring a struct directly on the arguments, which makes some kinds of functions unergonomic.
So, I quite liked this:
And it's more pronounced if you are writing functions that receive two or three points (some problem domains where Rust is applicable may have lots of functions like this, eg. code dealing with linear algebra)
It also addresses one concern regarding named arguments: that it makes the parameter names stiff: previously they would be changeable as we were developing the function, and now changing them is a breaking change.
On the other hand, it's better to trim down the proposal; this is the kind of thing that can be added later!
Using types purely for the purpose of associating a name with a parameter at the call-site is heavy-handed. That's the point @gbutler made. No one said anything about using types in general being heavy-handed. This is a strawman argument.
I couldn't agree more. It's interesting to note that the builder pattern generally only appears in languages without named parameters. In fact, when named parameters do exist, it's hard for me to think of reasons to use the builder pattern instead.
I can see the builder pattern being used when there more than 3 or 4 argument. Named arguments are not (to me) appropriate to a Struct::new() function taking 8 parameters or when the parameters are expected to be long/repeated.
An example is the Command type from the Rust standard library. A builder pattern works nicely for it, and while named arguments could be used in some parts, overall I don’t think they would be a great fit for all the functionalities that are available.
My goal is not to replace any one thing entirely with named arguments but to improve clarity/usability for function calls with few arguments, something which is forgotten by almost all the languages I have ever seen.
Functions taking « lots » of arguments often have more complicated patterns that would drown named arguments. That’s not always the case of course, just a feeling I have from my experience.
But then there are counter examples, especially domain specific ones, like maths, where some functions take a lot of argument and a builder would not match the maths. There named arguments are a nice to have, even with 8 parameters (especially since in maths it’s common for several of the parameters to have the same type)
From my experience, the more arguments there are, the higher the probability of some of them being optional/having widely used default, eg. port 22 for SSH when not specified.
A function building an HTTP request for example can take a whole heap of headers, a body, a port, a url. Not all those are equal in importance and with named arguments I would probably design it with a builder like this RequestBuilder::new(url).port(1245).body("have a nice day").compressed(false).auth_header(auth).lang_header(lang).header("recipient-name", value: "rustacean").header(/* other non usual header */) … .build()
Having all those in one function call would mean the port and unusual headers would be None most of the time, with is not good ergonomics to me (but you can disagree and that and not be wrong, that’s my opinion, not a fact)
Optional arguments. Mutually exclusive combinations of arguments. Passing a particular argument in multiple different forms (e.g. one method accepting impl intoIter<Item=T> and another called multiple times with a T each).
I've never used a language without function overloading (I'm only barely beginning to learn rust), so I never considered this. I was also assuming that default values for parameters exist as well. I should've been clearer on this.
Let me amend my previous comment:
In fact, when named parameters, function overloading, and default values do exist, it's hard for me to think of reasons to use the builder pattern instead.
I think at that point my response would change to "given the builder pattern, it's hard for me to see the value in named parameters, function overloading, and default values".
But that wasn't my question. If those aforementioned features exist, in what cases would the builder pattern be better? After all, there are plenty of languages that already have these features, so this is a very practical question.
Also, let's consider named parameters alone again: You don't see any value in named parameters given the builder pattern? This section from the proposal is particularly compelling to me:
I assume you wouldn't argue for the builder pattern for something as simple as the my_vec.insert call. So, then, why do you not see any value in my_vec.insert(2, at: 3) compared to my_vec.insert(2, 3)? It's very easy to forget which parameter is which. Have you ever had to check the declaration of a function to remember the meaning of each parameter?
Another example: Vec::reserve, which takes an additional capacity, not the total one. Maybe it’s obvious to others but I always have to look at the doc to be certain. Named arguments here would have made reviews of so many reserve call much much easier for me