Skip to content
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

named parameters at function call site #479

Closed
marler8997 opened this issue Sep 14, 2017 · 14 comments
Closed

named parameters at function call site #479

marler8997 opened this issue Sep 14, 2017 · 14 comments
Labels
breaking Implementing this issue could cause existing code to no longer compile or have different behavior. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@marler8997
Copy link
Contributor

For some functions it makes sense to include the name of one or more parameters at the call site. This is common if it's a flag type parameter, if the parameter is a "unit type" like "seconds"/"meters", or if the function takes multiple parameters of the same type whose order is important but not clear. It is proposed that a function definition can annotate certain parameters to be named in which case the caller is REQUIRED to write the name of those parameters at the call site. With this annotation the name at the call site should be REQUIRED instead of optional to maintain the "one way to do things" philosophy.

fn sleep(named seconds : u32) {...}
sleep(seconds = 100);
sleep(100); // Error: parameter 1 requires the name "seconds"

fn doSomething(named makeItCool : bool) { ... }
doSomething(makeItCool = true);

fn add(a : i32, b : i32, named checkOverflow : bool) { ... }
add(2, 3, checkOverflow = true);
@andrewrk andrewrk added breaking Implementing this issue could cause existing code to no longer compile or have different behavior. enhancement Solving this issue will likely involve adding new logic or components to the codebase. labels Sep 14, 2017
@andrewrk andrewrk added this to the 0.2.0 milestone Sep 14, 2017
@andrewrk
Copy link
Member

A couple of notes:

  • A possible alternative if you have a bool parameter is to use an enum instead.
const Overflow = enum {
    Checked,
    Unchecked,
};

add(2, 3, Overflow.Checked);
  • It might make sense to have named parameters to prevent logic bugs if you have multiple parameters with the same type with different meanings. This way they are not mixed up with the wrong parameter order, if for example one re-arranges the order at the function definition but not the callsite.
  • Another use case for named parameters is if you have a lot of parameters. But this can be accomplished by passing a struct.

@PavelVozenilek
Copy link

Tricks to simulate named parameters in C++ make situation much worse (e.g. in Boost http://www.boost.org/doc/libs/1_33_1/libs/graph/doc/bgl_named_params.html). In language D people bend themselves backward trying tricks (one of several attempts https://forum.dlang.org/post/ni1tr0$1kln$1@digitalmars.com ) instead of doing the proper thing. Even using good old enums or structs is needless complication.

I support this feature.

Things to consider:

  1. Order of parameters will be strictly required or can named parameter appear at any place? (I'd recommend keeping original order, even when it results in more work when original function is changed).
  2. How about function pointers? Can I use assign it with a function having different parameter names or no named parameters? Requiring the same name everywhere may not be the best idea, functions do different things and may want to reflect it in the names.
  3. If optional parameter values will be implemented: how could one specify "I want the default here"? One possible solution:
    foo(param1_with_default = _, param2 = 42, param3 = 0);
  4. Function pointers & optional parameters combined together.
  5. Since parameter names are local they tend to be generic words (count). Using them may result in funny situations like:
    foo(count = count);

Using word named as for what will be (I guess) very common would take lot of screen space. Imagine function with 5 - 10 parameters, that is 30 - 60 characters plus. How about putting "!" in front of the required parameter name?

@andrewrk
Copy link
Member

A couple relevant pieces of information:

  • Optional parameters and function overloading has been rejected.
  • we get a lot of criticism for sigils. Probably a bad idea to add more when it's not strictly necessary.

@PavelVozenilek
Copy link

Function overloading based on number of parameters was also rejected? This one is without surprises.

@andrewrk
Copy link
Member

Function overloading based on number of parameters was also rejected?

Correct.

@marler8997
Copy link
Contributor Author

We talked a little about why function overloading was rejected, but I failed to find any "Issues" that talked about why optional parameters were rejected. Is there a discussion on that you could point me to, I'm curious what the arguments for/against were. Specifically on optional parameters with default values, not with function overloading.

@PavelVozenilek
Copy link

Sigils (what I understand under the term) may not be necessary. Function may look like:

fn foo(named par x : i32) -> i32 { ... }
fn foo(par ::: i32) -> i32 { ... }

That's still 4 character shorter.

@marler8997
Copy link
Contributor Author

marler8997 commented Sep 15, 2017

I think using named over a special character has a couple advantages. The first is that it's easier for a newcomer to immediately understand what's going on, it's more intuitive to decipher what "named" means rather than some symbol like ! or :::. This fits the "zen rule" of "Favor reading code over writing code" better I think. I think using a symbol makes sense when the meaning of that symbol is used in multiple contexts or so often that the extra time it takes to learn the symbol is worth the brevity.

Note that using words over symbols in general makes a language simpler to learn/remember because it's easier to remember a word like "named" than to remember a special character; though this is at the cost of typing more characters. If I gave you a list of semantics to learn like the following, would it be easier to remember the word version of the symbol version?

const, out, shared, in, named

!, #, @, $, *

The disadvantage I can see is that it's a few more characters.

In my opinion the extra characters are worth the benefits listed above, and adding a new special character for this one use case isn't justified.

@PavelVozenilek
Copy link

I think you are right.

@raulgrell
Copy link
Contributor

raulgrell commented Sep 15, 2017

Just a few questions:

What happens with named varargs parameters? What would the following mean? How would you call them? Below are my thoughts.

// Functions
const anon_last = fn(first: u8, many: ..., last: u8);
const named_last = fn(first: u8, many: ..., named last: u8);
const named_varargs = fn(first: u8, named many: ...);
const named_and_varargs = fn(named first: u8, named many: ...);
const interleaved_varargs = fn(first: u8, args_first: ..., second: u8, args_second: ...);
const interleaved_named_varargs = fn(named first: u8, args_first: ..., named second: u8, args_second: ...);

// Calls, these are fairly unambiguous
anon_last(42, "These", "are", "the", "varargs", 99);
named_last(42, "These", "are", "the", "varargs", .last = 99);

// Here it might get unintuitive
named_varargs(42, .many = "first", .many = "second"); // Explicit naming of parameters, but double 'assignment'?
named_varargs(42, .many = "first", "these", "are", "part", "of", "many"); // Implicit named vararg parameters?
named_and_varargs(.many = "these", "are", "part", "of", "many", .first = 42) // Can implicitly named vararg handle out of order? Continue on varargs until next named param? Require all params after a varargs to be named?

// These have to be defined/restricted. Can the compiler work this out?
interleaved_varargs(42, "args_first1", "args_first2", 66, "args_second1", "args_second2")

 // Is the position of the positional arguments relative to the start of the function or the previously named param?
interleaved_varargs(.second = 66, "args_second1", "args_second2", .first = 42, "args_first1", "args_first2")

Syntax

In the calls, I have prefixed the name with a . to make it clear it's not a regular assignment - I think it is analogous to initializing a struct with a positional vs parametric initializer list.

fn sleep(named seconds : u32) {...}
sleep(.seconds = 100);

Though I'm usually a strong proponent/supporter of sigils, in this case I agree that the named keyword is better.

@andrewrk
Copy link
Member

Is there a discussion on that you could point me to, I'm curious what the arguments for/against were. Specifically on optional parameters with default values, not with function overloading.

I think there is not a documented discussion. Feel free to start one.

@tiehuis tiehuis added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Sep 15, 2017
@hasenj
Copy link

hasenj commented Sep 19, 2017

I like the idea of optionally naming parameters at call sites to disambiguate, with the condition that:

  1. The order must be preserved
  2. The name must match exactly the parameter name at definition site

Point (1) is to prevent confusion and remove ambiguity about overloading and to make it clear that this feature is not meant to allow overloading or jumbling up of parameter order.

Point (2) has potential to be problematic since it makes the parameter names a part of the function's interface, in that if a library provider changes a parameter name, it would affect all users of the library.

It's how Swift does it, except in Swift naming the parameters is mandatory, not optional.

So, using enums keeps things simpler IMO and does not require any new features.

Although, another neat feature from Swift is implicit enum namespace when passing parameters.

const Overflow = enum {
    Checked,
    Unchecked,
};

pub fn add(i64, i64, Overflow) -> i64 { .... }

add(2, 3, .Checked);

But that can also be very confusing to read.

Overall it's kind of a "nice to have" but I don't see it as essential in any way, and can probably be left for potentially future higher level languages that want to compile down to zig.

@andrewrk andrewrk modified the milestones: 0.2.0, 0.3.0 Oct 19, 2017
@skyfex
Copy link

skyfex commented Dec 21, 2017

With this annotation the name at the call site should be REQUIRED instead of optional to maintain the "one way to do things" philosophy.

If you introduce named parameters like this, haven't you already violated "one way to do things"? You now have two ways of taking function parameters, and it's not always obvious which one you should use.

Here's a half-serious proposal: any function taking more than 2 non-enum parameters must be called with all named parameters, and otherwise they must be called without names. Then there would always be one way to do things, and when you have a function with lots of parameters, you know you'll be able to tell which one is which when reading code without an IDE.

Although, another neat feature from Swift is implicit enum namespace when passing parameters.

I think this should be a feature. I don't see how it can be all that confusing. I think using enums and types to explicitly annotate function parameters is a very good practice, and anything that encourages this is positive.

@andrewrk andrewrk modified the milestones: 0.3.0, 0.4.0 Feb 28, 2018
@andrewrk andrewrk added rejected and removed enhancement Solving this issue will likely involve adding new logic or components to the codebase. labels Nov 21, 2018
@andrewrk
Copy link
Member

Solved with #485 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking Implementing this issue could cause existing code to no longer compile or have different behavior. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

7 participants