Named & Default Arguments: draft SIP

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
49 messages Options
123
Reply | Threaded
Open this post in threaded view
|

Named & Default Arguments: draft SIP

Lukas Rytz
Hi All,

here's a first draft of the Named & Default Arguments SIP. I'm happy to get some feedback.

http://lampsvn.epfl.ch/svn-repos/scala/lamp-sip/named-args/sip.xhtml

Cheers: Lukas
Reply | Threaded
Open this post in threaded view
|

Re: Named & Default Arguments: draft SIP

Ricky Clarkson
I'd guess this is the reverse of the intended:

"It is also possible to specify a default value for an implicit parameter, in which case the default value wil always be picked before any implicit member."

Otherwise the keyword 'implicit' would have no meaning for that parameter.

2008/12/5 Lukas Rytz <[hidden email]>
Hi All,

here's a first draft of the Named & Default Arguments SIP. I'm happy to get some feedback.

http://lampsvn.epfl.ch/svn-repos/scala/lamp-sip/named-args/sip.xhtml

Cheers: Lukas

Reply | Threaded
Open this post in threaded view
|

Re: Named & Default Arguments: draft SIP

Lukas Rytz
On Fri, Dec 5, 2008 at 17:03, Ricky Clarkson <[hidden email]> wrote:
I'd guess this is the reverse of the intended:

"It is also possible to specify a default value for an implicit parameter, in which case the default value wil always be picked before any implicit member."

Otherwise the keyword 'implicit' would have no meaning for that parameter.

I should have put a [open for discussion] there; should we
 - disallow defaults for implicit parameters?
 - pick implicit members, if available, before the default?
 - or always pick the default?


Reply | Threaded
Open this post in threaded view
|

Re: Named & Default Arguments: draft SIP

Jorge Ortiz

I should have put a [open for discussion] there; should we
 - disallow defaults for implicit parameters?
 - pick implicit members, if available, before the default?
 - or always pick the default?

If you always pick the default, would there be any difference between:

  def foo(implicit i: Int = 0) = { ... }

and

  def foo(i: Int = 0) = { implicit val _i = i; ... }

?
Reply | Threaded
Open this post in threaded view
|

Re: Named & Default Arguments: draft SIP

David MacIver
In reply to this post by Lukas Rytz
On 12/5/08, Lukas Rytz <[hidden email]> wrote:
On Fri, Dec 5, 2008 at 17:03, Ricky Clarkson <[hidden email]> wrote:
I'd guess this is the reverse of the intended:

"It is also possible to specify a default value for an implicit parameter, in which case the default value wil always be picked before any implicit member."

Otherwise the keyword 'implicit' would have no meaning for that parameter.

I should have put a [open for discussion] there; should we
 - disallow defaults for implicit parameters?
 - pick implicit members, if available, before the default?

This option would be fantastically useful .

 - or always pick the default?

This is functionally equivalent to the first except that it lies. :-)

Reply | Threaded
Open this post in threaded view
|

Re: Named & Default Arguments: draft SIP

Bill Venners-3
In reply to this post by Lukas Rytz
Hi Lukas,

This is a neat feature. The spec say this:

def f(a: Int, b: Int)
def f(a: Int) = f(a, 1)

can be written as:

def f(a: Int, b: Int = 2)

Thus the compile must subtract 1 from default parameters. Or, you have a typo.

Bill

On Fri, Dec 5, 2008 at 7:54 AM, Lukas Rytz <[hidden email]> wrote:
> Hi All,
>
> here's a first draft of the Named & Default Arguments SIP. I'm happy to get
> some feedback.
>
> http://lampsvn.epfl.ch/svn-repos/scala/lamp-sip/named-args/sip.xhtml
>
> Cheers: Lukas
>
Reply | Threaded
Open this post in threaded view
|

Re: Named & Default Arguments: draft SIP

David Hall-6
In reply to this post by Lukas Rytz
Just curious:

Why did you decide that defaults should be assigned based on the
runtime type and not the static type? It's not what I would have
expected. (Implementation should be easier with static, yes? Less
mangling...). Any motivation in particular?

-- David

On Fri, Dec 5, 2008 at 7:54 AM, Lukas Rytz <[hidden email]> wrote:
> Hi All,
>
> here's a first draft of the Named & Default Arguments SIP. I'm happy to get
> some feedback.
>
> http://lampsvn.epfl.ch/svn-repos/scala/lamp-sip/named-args/sip.xhtml
>
> Cheers: Lukas
>
Reply | Threaded
Open this post in threaded view
|

Re: Named & Default Arguments: draft SIP

Jorge Ortiz
In reply to this post by Lukas Rytz
A summary and some thoughts/questions at the end.

Summary (for those too lazy or busy to read the whole thing (which you should do anyway, I omit a lot of critical details)):

* Arguments can be named at call-time:
    def foo(a: Int, b: String) = ...
    foo(b = "Hello, world!", a = 0)

* Arguments can have a default value:
    def foo(a: Int, b: Int = 1) = ...
    foo(0)  // equivalent to foo(0, 1)

Points up for discussion:

* Local type inference for arguments with a default value
    def foo(a = 0) = a
    // equivalent to def foo(a: Int = 0): Int = a
  Pros: Concise
  Cons: Possibly confusing

* Argument evaluation order
  Option 1) Arguments evaluated in definition-site order
    Pros: Easier to implement
    Cons: Source of bugs, confusion, and pain
  Option 2) Given arguments evaluated in call-site order, then defaulted arguments evaluated (in definition-site order?)
    Pros: More intuitive
    Cons: Implementation overhead

* Defaults for implicit parameters
  Option 1) Use implicit if available, otherwise use default
    Pros: "Fantastically useful." - DRMacIver
    Cons: More "magic". Call-site confusion as to what is actually being used.
  Option 2) Disallow
    Pros: Simpler
    Cons: Less useful

Also notable:
* Only works with methods, functions not included. Methods converted to functions have parameter names and defaults erased.
* Default arguments can depend on earlier arguments in the same parameter list
* Resolution rules hurt my brain, will revisit later
* Names and defaults can be overriden in subclasses. Must use name of the compile-time type but get defaults from the run-time type. (wtf?)
* Defaults are inherited by subclasses.
* Parameter lists using all defaults must still be explicitly written, even if empty (). (Does this apply to the first parameter list?)

Thoughts:

I'm on the fence about type inference for arguments with defaults (leaning towards a yes, but very tentatively). I'd vote in favor of call-site evaluation order and the "fantastically useful" option for implicits with defaults.

Questions:

(Echoing David Hall) What's the reasoning behind getting defaults from runtime (rather than static) type?

SIP seems unclear on this: do you need to give the empty parameter list even for the first parameter list? For example:

  object c {
    def get(index: Int = 0) = ...
  }
  c.get(10)  // valid
  c.get()      // valid (index = 0)
  c.get        // valid?

Is this being contemplated for as early as 2.8.0? If the collection library is getting a major redesign anyway, the redesign should take into account these features.

Great work Lukas!

--j

On Fri, Dec 5, 2008 at 9:54 AM, Lukas Rytz <[hidden email]> wrote:
Hi All,

here's a first draft of the Named & Default Arguments SIP. I'm happy to get some feedback.

http://lampsvn.epfl.ch/svn-repos/scala/lamp-sip/named-args/sip.xhtml

Cheers: Lukas

Reply | Threaded
Open this post in threaded view
|

Re: Named & Default Arguments: draft SIP

csar
In reply to this post by Lukas Rytz
Some thoughts/questions on it

1. Evaluation order
f(a:Int,b:Int)
f(b=getList(), a=getInt())
I'd prefer that the evaluation order was getList, getInt -  more intuitive (to not mention the principle of least surprise), especiall when writing
f(b=getList(),
  a=getInt())



2. Scope of var-symbols
f(a=1)
Is 'a only defined at the scope of the assignment or in the whole parameter list so that
f(a=1,b=a)
is valid too (the logic from match/case with quoting could be applied here)

3. Overriding
I agree that the static type defines the symbol names, the proposed resolution of default values is reasonable as it provides the equivalent semantics as the "adhoc-polymorphism"

In general I am a bit uneasy with named arguments: Imagine a simple refactoring
def minus(a:Int,b:Int) = a-b
minus(a=2,b=1)
>1
towards
def minus(b:Int,a:Int) = b-a
minus(a=2,b=1)
>-1

Perhaps an alternate "indexed naming" could be useful here
minus( _1=2, _2=1) //returns 1 regardless of name changes
This fits into the idea of treating param-lists as tuples

/Carsten
 


On Fri, Dec 5, 2008 at 4:54 PM, Lukas Rytz <[hidden email]> wrote:
Hi All,

here's a first draft of the Named & Default Arguments SIP. I'm happy to get some feedback.

http://lampsvn.epfl.ch/svn-repos/scala/lamp-sip/named-args/sip.xhtml

Cheers: Lukas

Reply | Threaded
Open this post in threaded view
|

Re: Named & Default Arguments: draft SIP

Lukas Rytz
In reply to this post by Jorge Ortiz
Thanks for the summary and all the feedback!


On Sat, Dec 6, 2008 at 08:56, Jorge Ortiz <[hidden email]> wrote:
* Argument evaluation order
  Option 1) Arguments evaluated in definition-site order
    Pros: Easier to implement
    Cons: Source of bugs, confusion, and pain
  Option 2) Given arguments evaluated in call-site order, then defaulted arguments evaluated (in definition-site order?)
    Pros: More intuitive
    Cons: Implementation overhead

What others do:
 - F# always evaluates arguments in definition-site order
 - Python on the other hand uses the argument order at call-site


* Defaults for implicit parameters
  Option 1) Use implicit if available, otherwise use default
    Pros: "Fantastically useful." - DRMacIver
    Cons: More "magic". Call-site confusion as to what is actually being used.
  Option 2) Disallow
    Pros: Simpler
    Cons: Less useful

Of course its useless to pick a default before an implicit parameter, I just brought that third option up for completeness. The one we finally chose has to the most useful, least-surprising and most consistent.
So it looks like people think the "fantastically useful" Option 1 is best.

 
(Echoing David Hall) What's the reasoning behind getting defaults from runtime (rather than static) type?

I think it makes sense to use the runtime type. When I override the default arguments and the implementation of a method in a Subclass, the new implementation is probably adjusted to the new defaults. So when dynamic binding choses the implementation of a subclass, it should also chose the defaults of the subclass.

For the names, there is no choice. Since it's the compiler who has to handle the named arguments, we have to look at the compile-time type.
 


SIP seems unclear on this: do you need to give the empty parameter list even for the first parameter list? For example:

  object c {
    def get(index: Int = 0) = ...
  }
  c.get(10)  // valid
  c.get()      // valid (index = 0)
  c.get        // valid?

I think it should not be allowed to omit a parameter list, so I vote for disallowing it.

Lukas
Reply | Threaded
Open this post in threaded view
|

Re: Named & Default Arguments: draft SIP

Lukas Rytz
In reply to this post by csar
On Sat, Dec 6, 2008 at 11:27, Carsten Saager <[hidden email]> wrote:
Some thoughts/questions on it

1. Evaluation order

f(a:Int,b:Int)
f(b=getList(), a=getInt())
I'd prefer that the evaluation order was getList, getInt -  more intuitive (to not mention the principle of least surprise), especiall when writing
f(b=getList(),
  a=getInt())

OK, so we have two votes for call-site order.


2. Scope of var-symbols
f(a=1)
Is 'a only defined at the scope of the assignment or in the whole parameter list so that
f(a=1,b=a)
is valid too (the logic from match/case with quoting could be applied here)

No. You'd have to store the first argument in a local value.


In general I am a bit uneasy with named arguments: Imagine a simple refactoring
def minus(a:Int,b:Int) = a-b
minus(a=2,b=1)
>1
towards
def minus(b:Int,a:Int) = b-a
minus(a=2,b=1)
>-1

Well, there are lots of ways to write confusing code. I agree that named arguments will
introduce yet another, but I their usefulness still justifies their addition. Replacing names
with indicies (like "_1") really defeats the purpose of named arguments.
Reply | Threaded
Open this post in threaded view
|

Re: Named & Default Arguments: draft SIP

Lukas Rytz
In reply to this post by Lukas Rytz
On Sat, Dec 6, 2008 at 15:18, Stepan Koltsov <[hidden email]> wrote:
Lukas, specification is great!

Please, clarify, how this method will be translated (when first
argument has no default value):

def f(a: Int, b: Int = 1, c: Int = 1) = ...

mask for b is 0x2 and mask for c is 0x4, right?

Yes that's what I was planning to do.
By the way: as you may have guessed, I took the idea of using a bitmask
from one of your posts in an earlier discussion on named arguments. Thanks :)
Reply | Threaded
Open this post in threaded view
|

Re: Named & Default Arguments: draft SIP

David MacIver
In reply to this post by Lukas Rytz
On Sun, Dec 7, 2008 at 12:58 PM, Lukas Rytz <[hidden email]> wrote:
On Sat, Dec 6, 2008 at 11:27, Carsten Saager <[hidden email]> wrote:
Some thoughts/questions on it

1. Evaluation order

f(a:Int,b:Int)
f(b=getList(), a=getInt())
I'd prefer that the evaluation order was getList, getInt -  more intuitive (to not mention the principle of least surprise), especiall when writing
f(b=getList(),
  a=getInt())

OK, so we have two votes for call-site order.

Just to clarify: This is call site order for the specified one and definition site order for the defaults?

Reply | Threaded
Open this post in threaded view
|

Re: Named & Default Arguments: draft SIP

Ismael Juma
In reply to this post by Lukas Rytz
Lukas Rytz <lukas.rytz <at> epfl.ch> writes:
> What others do:
>  - F# always evaluates arguments in definition-site order
>  - Python on the other hand uses the argument order at call-site

While we're talking about what others do, it might be worth reading what Alex
Buckley (JLS and JVMS lead) has written about them (in case you haven't):

http://blogs.sun.com/abuckley/entry/named_parameters

Ismael

Reply | Threaded
Open this post in threaded view
|

Re: Named & Default Arguments: draft SIP

Stepan Koltsov
In reply to this post by Lukas Rytz
On Fri, Dec 5, 2008 at 18:54, Lukas Rytz <[hidden email]> wrote:
> here's a first draft of the Named & Default Arguments SIP. I'm happy to get
> some feedback.
>
> http://lampsvn.epfl.ch/svn-repos/scala/lamp-sip/named-args/sip.xhtml

Python do have problems with named arguments:

===
>>> def f(a=[]):
...  a.append(1)
...  return a
...
>>> f()
[1]
>>> f()
[1, 1]
>>> f()
[1, 1, 1]
>>> f()
[1, 1, 1, 1]
===

Python evaluates default argument values once when method is defined,
and then reuses this value.

C++ named arguments work good:

===
$$ cat na.cxx
#include <iostream>

int *x(int *ref = new int[1]) {
    return ref;
}

int main() {
    std::cout << x() << ", " << x() << std::endl;
    return 0;
}
$$ ./a.out
0x100150, 0x100160
===

S.
Reply | Threaded
Open this post in threaded view
|

Re: Named & Default Arguments: draft SIP

Ricky Clarkson

C++ named arguments work good:

Except that they don't exist.  C++ has default arguments, not named arguments.
Reply | Threaded
Open this post in threaded view
|

Re: Named & Default Arguments: draft SIP

Jorge Ortiz
In reply to this post by Lukas Rytz

I think it makes sense to use the runtime type. When I override the default arguments and the implementation of a method in a Subclass, the new implementation is probably adjusted to the new defaults. So when dynamic binding choses the implementation of a subclass, it should also chose the defaults of the subclass.

I'm still not convinced about this. It would make it impossible, in general, to know at call-time what default will be used. Since omitting a parameter is a call-site decision, it seems dangerous to ask programmers to make the decision about whether to rely on a default or not with incomplete information about what that default will be.

Since Python has no static types, I'm assuming it uses runtime type defaults. What about F#?

SIP seems unclear on this: do you need to give the empty parameter list even for the first parameter list? For example:

  object c {
    def get(index: Int = 0) = ...
  }
  c.get(10)  // valid
  c.get()      // valid (index = 0)
  c.get        // valid?

I think it should not be allowed to omit a parameter list, so I vote for disallowing it.

This would break the convention of "no parameter list for methods with no side-effects, empty parameter list for methods with side-effects". Not that I'm particularly attached to it.

--j
Reply | Threaded
Open this post in threaded view
|

Re: Named & Default Arguments: draft SIP

David MacIver
On Sun, Dec 7, 2008 at 4:16 PM, Jorge Ortiz <[hidden email]> wrote:

I think it makes sense to use the runtime type. When I override the default arguments and the implementation of a method in a Subclass, the new implementation is probably adjusted to the new defaults. So when dynamic binding choses the implementation of a subclass, it should also chose the defaults of the subclass.

I'm still not convinced about this. It would make it impossible, in general, to know at call-time what default will be used. Since omitting a parameter is a call-site decision, it seems dangerous to ask programmers to make the decision about whether to rely on a default or not with incomplete information about what that default will be.

In much the same way that the ability to override methods makes it impossible to know at call time what implementation will be called? :-)

Reply | Threaded
Open this post in threaded view
|

Re: Re: Named & Default Arguments: draft SIP

Jorge Ortiz
In reply to this post by Ismael Juma
This brings up some really interesting points, especially about variable arguments.

(And, for the first time, I'm genuinely grateful that Scala is binary incompatible between releases. It avoids so many pitfalls here.)

--j

On Sun, Dec 7, 2008 at 7:19 AM, Ismael Juma <[hidden email]> wrote:
Lukas Rytz <lukas.rytz <at> epfl.ch> writes:
> What others do:
>  - F# always evaluates arguments in definition-site order
>  - Python on the other hand uses the argument order at call-site

While we're talking about what others do, it might be worth reading what Alex
Buckley (JLS and JVMS lead) has written about them (in case you haven't):

http://blogs.sun.com/abuckley/entry/named_parameters

Ismael


Reply | Threaded
Open this post in threaded view
|

Re: Re: Named & Default Arguments: draft SIP

Martin Odersky
On static vs dynamic defaults:

One principle which looks reasonable is to make default parameters behave like overloaded methods. I.e. if you have a method which an optional second argument, there are two ways to express this. With a default parameter:

  def foo(x: Int, y: Int = 1)

or by overloading:

  def foo(x: Int) = foo(x, 1)
  def foo(x: Int, y: Int)

Now assume we change the default in a subclass:

  def foo(x: Int, y: Int = 2)

This would correspond to an override like this:

  override def foo(x: Int) = foo(x, 2)

The overriding method gets applied based on the dynamic type of the receiver. So by analogy, it makes sense that the default parameter behaves the same way.

Cheers

 -- Martin




123