New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: spec: Default / Optional function parameters #21909

Closed
Innectic opened this Issue Sep 16, 2017 · 13 comments

Comments

Projects
None yet
10 participants
@Innectic

Innectic commented Sep 16, 2017

Languages like Python and TypeScript supply ways to omit arguments when calling with either a default provided, or just none at all.

Default parameter example from Typescript:

function buildFileName(directory: string, fileName: string, delim = "/"): string {
    return `${directory}${delim}${fileName}`;
}

let res = buildFileName("/root", "test.txt");  // valid, results in /root/test.txt
let res = buildFileName("c:", "test.txt", "\\"); // valid, results in c:\test.txt

Optional parameter example from Typescript:

function doMath(x: number, y?: number): number {
     if (!y) return x; 
     return x + y;
}

let res = doMath(1); // valid, results in 1
let res = doMath(1, 2); // valid, results in 3

Use Case

For instance, creating a reader:

func NewReader(rd io.Reader) {}
func NewReaderSize(rd io.Reader, size int) *Reader {}

This can be compressed into one function:

func NewReader(rd io.Reader, size = 4096) *Reader {  /* ... */  }
// This should work too:
func NewReader(rd io.Reader, size = defaultBufferSize) *Reader { /* ... */ }

@gopherbot gopherbot added this to the Proposal milestone Sep 16, 2017

@gopherbot gopherbot added the Proposal label Sep 16, 2017

@dsnet

This comment has been minimized.

Member

dsnet commented Sep 16, 2017

Related to #12854, which I believe is a better solution to this. Damien's first example shows how optional arguments could effectively be accomplished.

@dsnet dsnet changed the title from proposal: Default / Optional function parameters to proposal: spec: Default / Optional function parameters Sep 16, 2017

@jimmyfrasche

This comment has been minimized.

Member

jimmyfrasche commented Sep 17, 2017

I believe #12854 or https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis are superior solutions to this problem.

I don't think default arguments could be made to fit into Go without a great deal of complexity. They raise a number of questions:

Given a type with a method M(a A, b B = defaultB) does a value of that type satisfy an interface expecting M(A)?

Is the default value part of the type? If not, then it's unclear why two functions of identical type can be called differently. If so, why is this just for func parameters—why can't I write type I int = 4 or type I = int = 4? And if they're part of the type, then the type of f(int) is different than the type of f(int=1) but does that mean that f(int=2) and f(int=3) have different types? What are the rules for type conversion?

What does this print:

func f(s []int = []int{1, 2}) {
  fmt.Println(s)
  s[0], s[1] = s[1], s[0]
}

func main() {
  f() // [1 2]
  f() // [1 2] or [2 1] ?
}

While I'm sure these could all be worked out, they all have serious implications for the complexity of the language specification and implementations and hence a programmer's ability to reason about Go programs.

@ghost

This comment has been minimized.

ghost commented Sep 17, 2017

Given a type with a method M(a A, b B = defaultB) does a value of that type satisfy an interface expecting M(A)?

The question you need to ask is will that function break when it tries to call M(A) on a value of type M(A,B=D)? Clearly it will not, so all is fine.

@urandom

This comment has been minimized.

urandom commented Sep 18, 2017

What does this print:

func f(s []int = []int{1, 2}) {
fmt.Println(s)
s[0], s[1] = s[1], s[0]
}

func main() {
f() // [1 2]
f() // [1 2] or [2 1] ?
}

This should be pretty much equivalent of:

func (f s[]int) {
   if s IS_NOT_PASSED {
       s = []int{1,2}
   }

   ...
}

Thus it will not be influenced by a new call. If the default argument is initialized with a global variable, the regular rules apply (i.e. whether it's a pointer or not). In both cases it seems the generated code would be the same, I think.

I do not see a purpose for optional parameters, if default ones are supported. They seem like a special case that we can go without.

@robpike

This comment has been minimized.

Contributor

robpike commented Sep 19, 2017

This feature was deliberately omitted. See the discussion in talks/golang.org/2012/splash.article about default arguments. They are a bad idea as they make it much easier to build confusing functions that complicate the API.

@urandom

This comment has been minimized.

urandom commented Sep 19, 2017

@robpike

API patching is a concern, but the thing is, nothing prevents us from doing such patching even now. Right now we can and do use either structs or functional arguments do achieve default/optional arguments (the stdlib even uses struct pointers for this), and there's nothing stopping anyone from adding an extra function or struct key later on, which doesn't match the current behaviour of the API.

So the current situation is that it is a bit more cumbersome, without actually solving the problem cited in the article. Furthermore, the current solutions have a much higher initial cost of creation, especially for functional arguments; and that's for a feature that seems to have proven its usefulness. A decend support for default arguments will only result in a cleaner and more understandable code in the long run.

@griesemer

This comment has been minimized.

Contributor

griesemer commented Sep 19, 2017

@urandom There are always ways to circumvent the current situation but as @robpike said, there's good reason for the status quo. Also "usefulness" of a feature is not a meaningful argument for its inclusion. Virtually every feature is "useful" in some way or another, otherwise it wouldn't be proposed.

@robpike

This comment has been minimized.

Contributor

robpike commented Sep 20, 2017

@urandom: "support for default arguments will only result in a cleaner and more understandable code in the long run". That's just it, the designers of the language disagree with that claim when applied to large code bases. As code with default arguments grows, it too often becomes harder to understand, not easier, as functions and their invocations become more intricate and more entangled.

I consider this issue closable but will let discussion continue.

@ghost

This comment has been minimized.

ghost commented Sep 20, 2017

It is more than just about optional arguments. It also raises the prospect of named arguments, which have their own value.

What is easier to understand?

F(3.44, 2.44, 5.44, 7.55, 3.141, 6.666)

OR

F(width=3.44, height=2.44, depth=5.44, color=7.55, velocity=3.141, weight=6.666)

On top of named arguments, you then also have the prospect of kwargs style argument passing/receiving, yes we are basically talking about Python here.

The point is there are more advantages to optional parameters than merely them having default values.

@griesemer

This comment has been minimized.

Contributor

griesemer commented Sep 20, 2017

@fcntl The designers of the language have also contemplated named arguments. One thing to consider is what programming style should be enforced once a language provides named arguments (and people invariably will want to enforce a style): Must they always be named? Never? Only when there's more then 3 arguments? Can some names be left out? Does the order matter? Should it be always in declaration order for readability? Why not? etc. etc. It opens a Pandorra's box of questions that need to be answered. The marginal (in most cases) benefit comes at a cost that is not commensurable.

More generally: If you have a lot of code like with 5 or more arguments, and it's not regular (say as part of building a large table), you probably should rethink your approach. In those cases it's probably better to pass a struct and then you can name the fields as you please. Finally, there are comments.

In other words, Go provides you with plenty of mechanisms to write readable function calls even without the ability to explicitly name your arguments.

@myitcv

This comment has been minimized.

Member

myitcv commented Sep 20, 2017

I believe #12854 (snip) ... is a superior solution to this problem.

Complete agree.

Furthermore, that with ~2.5 years of experience with a medium-sized Typescript project (which I'll grant you is not a huge evidence base), writing the language, writing tools to work with the language (this is a hugely important consideration from our perspective)... Go's current position is far preferable, for at least all the reasons outlined by @robpike, @griesemer and others.

@tj

This comment has been minimized.

tj commented Mar 15, 2018

I feel like we sort of need some solution here, right now there's maybe 6 or 7 reasonable ways to define "options" with defaults, using the functional style, structs, methods, chained methods, zero values, tags, etc. None of them are very self-documenting, you can't see what the default values are really, or they're arbitrarily embedded in tags.

To me at least one of my favorite things about Go is that it's opinionated and most Go code looks very similar, however, I feel like this is a big part of where they all start to differentiate and it's such a common task, people shouldn't be burning cycles on it ideally.

@ianlancetaylor

This comment has been minimized.

Contributor

ianlancetaylor commented Mar 20, 2018

Closing this approach in favor of #12854, as discussed above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment