This page is no longer maintained — Please continue to the home page at www.scala-lang.org

names / defaults

22 replies
rytz
Joined: 2008-07-01,
User offline. Last seen 45 weeks 5 days ago.
Named and default arguments are now in trunk. The main changes are:

- MethodType now takes (params: List[Symbol]) as argument, no more List[Type]
- To create parameter symbols for a synthetic MethodType, use
    methodSymbol.newSyntheticValueParamss(argtypess)

- the expression "foo(x = _)" was previously parsed as
       foo(x$1 => x = x$1)
   but is now parsed as a named application:
       x$1 => foo(x = x$1)

- a "copy" method is added to case classes
- the tree copiers in the compiler are now called "treeCopy", no longer "copy"

To learn how to use named / default arguments and how they are implemented,
see http://www.scala-lang.org/sid/1


Cheers: Lukas
extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: names / defaults

On Sat, May 30, 2009 at 11:23:47AM +0200, Lukas Rytz wrote:
> Named and default arguments are now in trunk.

Cool, something new to play with.

One thing I noticed so far is the output below. The first error message
is great, the two following it should not be issued IMO, both because
they are noisy and because they make no sense - the messages are written
as if I am trying to pass a and b of the wrong types to f, whereas
they're actually being used as formal parameters in the illegal overload.

scala> class a { def f(a: Int = 5, b: String = "abc") = "first" }
defined class a

scala> class b extends a { def f(b: String = "abc", a: Int = 5) = "second" }
:5: error: multiple overloaded alternatives of method f define default arguments
class b extends a { def f(b: String = "abc", a: Int = 5) = "second" }
^
:5: error: type mismatch;
found : Int(5)
required: java.lang.String
class b extends a { def f(b: String = "abc", a: Int = 5) = "second" }
^
:5: error: type mismatch;
found : java.lang.String("abc")
required: Int
class b extends a { def f(b: String = "abc", a: Int = 5) = "second" }
^

...and similarly, putting them in the same class issues too many errors.
Especially, synthetically generated method names make error messages
much harder to read (but it probably shouldn't get that far anyway.)

scala> object t1 {
| def f(a: Int = 5, b: String = "abc") = "first"
| def f(b: String = "abc", a: Int = 5) = "second"
| }
:5: error: multiple overloaded alternatives of method f define default arguments
def f(a: Int = 5, b: String = "abc") = "first"
^
:6: error: multiple overloaded alternatives of method f define default arguments
def f(b: String = "abc", a: Int = 5) = "second"
^
:6: error: method f$default$2 is defined twice
def f(b: String = "abc", a: Int = 5) = "second"
^
:6: error: method f$default$1 is defined twice
def f(b: String = "abc", a: Int = 5) = "second"
^

rytz
Joined: 2008-07-01,
User offline. Last seen 45 weeks 5 days ago.
Re: names / defaults


On Sat, May 30, 2009 at 15:02, Paul Phillips <paulp@improving.org> wrote:
On Sat, May 30, 2009 at 11:23:47AM +0200, Lukas Rytz wrote:
> Named and default arguments are now in trunk.

Cool, something new to play with.

One thing I noticed so far is the output below.  The first error message
is great, the two following it should not be issued IMO,

I agree, but I don't see how to do it. the first message is printed during type-checking
the method, the typer searches other overloaded alternatives with defaults.

At that point, the default getters for all these alternatives have already been
generated (namer), so the second set of messages is issued when type-checking
them.

How can I prevent reporting the second messages?

Lukas


 
both because
they are noisy and because they make no sense - the messages are written
as if I am trying to pass a and b of the wrong types to f, whereas
they're actually being used as formal parameters in the illegal overload.


scala> class a { def f(a: Int = 5, b: String = "abc") = "first" }
defined class a

scala> class b extends a { def f(b: String = "abc", a: Int = 5) = "second" }
<console>:5: error: multiple overloaded alternatives of method f define default arguments
      class b extends a { def f(b: String = "abc", a: Int = 5) = "second" }
                              ^
<console>:5: error: type mismatch;
 found   : Int(5)
 required: java.lang.String
      class b extends a { def f(b: String = "abc", a: Int = 5) = "second" }
                                                            ^
<console>:5: error: type mismatch;
 found   : java.lang.String("abc")
 required: Int
      class b extends a { def f(b: String = "abc", a: Int = 5) = "second" }
                                            ^

...and similarly, putting them in the same class issues too many errors.
Especially, synthetically generated method names make error messages
much harder to read (but it probably shouldn't get that far anyway.)


scala> object t1 {
    |   def f(a: Int = 5, b: String = "abc") = "first"
    |   def f(b: String = "abc", a: Int = 5) = "second"
    | }
<console>:5: error: multiple overloaded alternatives of method f define default arguments
        def f(a: Int = 5, b: String = "abc") = "first"
            ^
<console>:6: error: multiple overloaded alternatives of method f define default arguments
        def f(b: String = "abc", a: Int = 5) = "second"
            ^
<console>:6: error: method f$default$2 is defined twice
        def f(b: String = "abc", a: Int = 5) = "second"
                                 ^
<console>:6: error: method f$default$1 is defined twice
        def f(b: String = "abc", a: Int = 5) = "second"
              ^

--
Paul Phillips      | It's not enough to bash in heads - you've got to
Vivid              | bash in minds.
Empiricist         |     -- Capt Hammer
pal, i pill push   |----------* http://www.improving.org/paulp/ *----------

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: names / defaults

On Sat, May 30, 2009 at 05:19:53PM +0200, Lukas Rytz wrote:
> How can I prevent reporting the second messages?

I don't know the answer to this one yet, but on another subject, what
happens here gives me a bad feeling - especially because it silently
"works", just not how one might have expected:

object o {
def twice(x: => Unit) = { x ; x }

def go() = {
var x = 1
var y = 1
twice(x = x + 1)
twice(y = y + 1)
println(x + ", " + y)
}
}

This prints "1, 3" as we expect since I'm bringing it up. So one's
choice of parameter names is now potentially side effecting all the code
in the world. Maybe the parameter to twice was called "op" when I wrote
go() but he changed it to x later. I guess we won't be doing that
anymore if we know what's good for us.

To increment my local x (since it's not a field I can't access it via
this) I suppose I have to do something like:

def addone = x = x + 1
twice(addone)
println(x)

My gut reaction is that twice(x = x + 1) should not even compile if the
parameter is called x and I also have one in scope, and I should be
forced to disambiguate somehow, although I don't have any syntax in mind
for how one might do that.

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: names / defaults

On Sat, May 30, 2009 at 9:57 PM, Paul Phillips wrote:
> On Sat, May 30, 2009 at 05:19:53PM +0200, Lukas Rytz wrote:
>> How can I prevent reporting the second messages?
>
> I don't know the answer to this one yet, but on another subject, what
> happens here gives me a bad feeling - especially because it silently
> "works", just not how one might have expected:
>
> object o {
>  def twice(x: => Unit) = { x ; x }
>
>  def go() = {
>    var x = 1
>    var y = 1
>    twice(x = x + 1)
>    twice(y = y + 1)
>    println(x + ", " + y)
>  }
> }
>
> This prints "1, 3" as we expect since I'm bringing it up.  So one's
> choice of parameter names is now potentially side effecting all the code
> in the world.  Maybe the parameter to twice was called "op" when I wrote
> go() but he changed it to x later.  I guess we won't be doing that
> anymore if we know what's good for us.
>
> To increment my local x (since it's not a field I can't access it via
> this) I suppose I have to do something like:
>
>    def addone = x = x + 1
>    twice(addone)
>    println(x)
>
> My gut reaction is that twice(x = x + 1) should not even compile if the
> parameter is called x and I also have one in scope, and I should be
> forced to disambiguate somehow, although I don't have any syntax in mind
> for how one might do that.
>
I think that's a good argument. Can we make ambiguities between
assignment and named parameter a type error?

I think it's possible: If we find a named parameter, also
check it as an assignment, but wrapped in a silent block.
If the silent block returns a type error, all is OK. But if it
returns without a type error, issue an ambiguity error.

Cheers

rytz
Joined: 2008-07-01,
User offline. Last seen 45 weeks 5 days ago.
Re: names / defaults


On Sat, May 30, 2009 at 22:37, martin odersky <martin.odersky@epfl.ch> wrote:
On Sat, May 30, 2009 at 9:57 PM, Paul Phillips <paulp@improving.org> wrote:
> On Sat, May 30, 2009 at 05:19:53PM +0200, Lukas Rytz wrote:
>> How can I prevent reporting the second messages?
>
> I don't know the answer to this one yet, but on another subject, what
> happens here gives me a bad feeling - especially because it silently
> "works", just not how one might have expected:
>
> object o {
>  def twice(x: => Unit) = { x ; x }
>
>  def go() = {
>    var x = 1
>    var y = 1
>    twice(x = x + 1)
>    twice(y = y + 1)
>    println(x + ", " + y)
>  }
> }
>
> This prints "1, 3" as we expect since I'm bringing it up.  So one's
> choice of parameter names is now potentially side effecting all the code
> in the world.  Maybe the parameter to twice was called "op" when I wrote
> go() but he changed it to x later.  I guess we won't be doing that
> anymore if we know what's good for us.
>
> To increment my local x (since it's not a field I can't access it via
> this) I suppose I have to do something like:
>
>    def addone = x = x + 1
>    twice(addone)
>    println(x)
>
> My gut reaction is that twice(x = x + 1) should not even compile if the
> parameter is called x and I also have one in scope, and I should be
> forced to disambiguate somehow, although I don't have any syntax in mind
> for how one might do that.
>
I think that's a good argument. Can we make ambiguities between
assignment and named parameter a type error?

I think it's possible: If we find a named parameter, also
check it as an assignment, but wrapped in a silent block.
If the silent block returns a type error, all is OK. But if it
returns without a type error, issue an ambiguity error.

OK, we can do that. Depending on the environment, you are then allowed
to use named arguments or not; if a variable with the same name as one of the
parameters is in scope, bad luck.

Note that the problem only occurs for parameters of type Unit; otherwise, one
would not give an assignment expression as argument. Should the above check
be restricted to these cases?


We could also pick a different assignment operator than `=' e.g.
   foo(b <- getBValue()) // named argument
But I'd like to avoid that if possible, I think using `=' is better.

Lukas

PS: I'll be away from e-mail the next 48 hours / until Monday evening.
extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: names / defaults

On Sat, May 30, 2009 at 10:37:51PM +0200, martin odersky wrote:
> I think it's possible: If we find a named parameter, also check it as
> an assignment, but wrapped in a silent block. If the silent block
> returns a type error, all is OK. But if it returns without a type
> error, issue an ambiguity error.

This is a promising idea. No obvious syntax for how to disambiguate
though, especially in favor of the named argument interpretation. Your
suggestion above would further reduce ambiguities because "reassignment
to val" should resolve in favor of the named argument.

On Sat, May 30, 2009 at 11:10:16PM +0200, Lukas Rytz wrote:
> OK, we can do that. Depending on the environment, you are then allowed
> to use named arguments or not; if a variable with the same name as one
> of the parameters is in scope, bad luck.

I haven't read the spec thoroughly enough - named arguments are in all
cases only sugar? As long as that is true then it would be livable to
simply disallow them in this case, but then there still has to be a way
to apply the pre-named-arguments interpretation.

In other words, if the code I posted is fail with an ambiguity error
(good) and we say in that case you can't use named arguments (also good)
then we still need a way to indicate that we are unambiguously trying to
pass a line of code or perform an assignment and pass the Unit. The
approach I posted (writing a specialized function) is unthrilling, but
again presuming these conflicts will be very rare, livable.

> Note that the problem only occurs for parameters of type Unit;
> otherwise, one would not give an assignment expression as argument.
> Should the above check be restricted to these cases?

I see six non-generic types which could take x = foo: Unit, AnyVal, Any,
=>Unit, =>AnyVal, and =>Any. And then there's T and =>T if they're not
bounded by AnyRef.

That said, I like your idea to limit it to types that might actually be
ambiguous. We can compress the range of possible ambiguities down to
almost nothing, but knowing that those few would indeed be flagged makes
all the difference.

> We could also pick a different assignment operator than `=' e.g.
> foo(b <- getBValue()) // named argument
> But I'd like to avoid that if possible, I think using `=' is better.

I agree '=' is the way to go.

BTW just so people see that this is not about angels dancing on the head
of a pin, the current version of Exception (still in flux) has this:

def ultimately[T](body: => Unit): Catch[T] = noCatch andFinally body

which is used in the interpreter like this:

val wasPrinting = printResults
ultimately(printResults = wasPrinting) {
printResults = false
operation
}

...so as things stand right now if you tried to save/restore a variable
called "body" this way, you would be in for a surprise. In the absence
of the changes being discussed in this thread, I'd be tempted to name
the parameter to ultimately "asdljkf_#$%#$" just to be sure!

DRMacIver
Joined: 2008-09-02,
User offline. Last seen 42 years 45 weeks ago.
Re: names / defaults

2009/5/31 Paul Phillips :
> On Sat, May 30, 2009 at 10:37:51PM +0200, martin odersky wrote:
>> I think it's possible: If we find a named parameter, also check it as
>> an assignment, but wrapped in a silent block. If the silent block
>> returns a type error, all is OK. But if it returns without a type
>> error, issue an ambiguity error.
>
> This is a promising idea.  No obvious syntax for how to disambiguate
> though, especially in favor of the named argument interpretation.  Your
> suggestion above would further reduce ambiguities because "reassignment
> to val" should resolve in favor of the named argument.

I was going to say that there's an obvious disambiguation syntax which
already works, but then I actually tried it and it doesn't seem to
work, which I find a little troubling.

scala> def foo(x : => Unit) = x
foo: (x: => Unit)Unit

scala> var x = 0;
x: Int = 0

scala> foo(x = 1)

scala> x
res4: Int = 0

So the above behaves as expected.

What I expected to work though was wrapping x = 1 in additional
brackets to force it to parse as an expression rather than a
parameter:

scala> foo((x = 1))

scala> x
res6: Int = 0

scala> foo(((x = 1)))

scala> x
res9: Int = 0

Doesn't work, as you can see.

Braces don't work either:

scala> foo({ x = 1; })

scala> x
res13: Int = 0

The following example confuses me. I don't know what it's doing that
allows it to compile but doesn't cause it to do the right thing:

scala> foo(x = (x = 1))

scala> x
res27: Int = 0

Of course we can do the following, but that seems a bit silly.

scala> foo((()=> x = 1 )())

scala> x
res20: Int = 1

I find it particularly troubling that if you try assigning to a
variable that isn't a parameter name then it works:

scala> var y = 0;
y: Int = 0

scala> foo( y = 1)

scala> y
res15: Int = 1

It seems to me like writing an assignment in a parameter list should
*always* mean it to be passed as a named parameter, and that wrapping
it in brackets should cause it to be treated as an expression to be
passed as a parameter value. Does this seem reasonable?

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: names / defaults

Just to get some more data out there, here are a couple twists:

scala> def foo(x1: => Unit, x2: => Unit) = { x1 ; x2 }
foo: (x1: => Unit,x2: => Unit)Unit

scala> var x1 = 0
x1: Int = 0

scala> var x2 = 0
x2: Int = 0

scala> foo(x1 = { x2 = 5 }, x2 = 5)
:11: error: positional after named argument.
foo(x1 = { x2 = 5 }, x2 = 5)
^

scala> foo(x1 = { x2 = 5 ; () }, x2 = 5)

scala> x2
res32: Int = 5

Anders Bach Nielsen
Joined: 2008-12-17,
User offline. Last seen 42 years 45 weeks ago.
Re: names / defaults

Hey Lukas

I have one question, I can see that you committed a new starr together
with the named/default args commit. What is in that new starr? Any
reason why it was made?
The reason I ask is because I am currently creating a new starr that
supports only the new early defs syntax, and that means to remove that
old syntax usage from trunk!

/Anders

Lukas Rytz wrote:
> Named and default arguments are now in trunk. The main changes are:
>
> - MethodType now takes (params: List[Symbol]) as argument, no more
> List[Type]
> - To create parameter symbols for a synthetic MethodType, use
> methodSymbol.newSyntheticValueParamss(argtypess)
>
> - the expression "foo(x = _)" was previously parsed as
> foo(x$1 => x = x$1)
> but is now parsed as a named application:
> x$1 => foo(x = x$1)
>
> - a "copy" method is added to case classes
> - the tree copiers in the compiler are now called "treeCopy", no longer
> "copy"
>
> To learn how to use named / default arguments and how they are implemented,
> see http://www.scala-lang.org/sid/1
>
>
> Cheers: Lukas

rytz
Joined: 2008-07-01,
User offline. Last seen 45 weeks 5 days ago.
Re: names / defaults
Hi Anders,

In fact I did not commit a new starr togeter with the named/default arguments
(http://lampsvn.epfl.ch/trac/scala/changeset/17916).
But I did commit a new starr with the new annotations implementation
(http://lampsvn.epfl.ch/trac/scala/changeset/17925).

The reason is that (actually in both changes), the pickle format has changed.
The new starr was done to make sure it uses that new format. Also, we can
now start using named/default arguments in the compiler/library.

If you build a new starr just make sure you build it based on a recent
version of trunk.

Cheers: Lukas


On Mon, Jun 1, 2009 at 20:03, Anders Bach Nielsen <andersbach.nielsen@epfl.ch> wrote:
Hey Lukas

I have one question, I can see that you committed a new starr together
with the named/default args commit. What is in that new starr? Any
reason why it was made?
The reason I ask is because I am currently creating a new starr that
supports only the new early defs syntax, and that means to remove that
old syntax usage from trunk!

/Anders

Lukas Rytz wrote:
> Named and default arguments are now in trunk. The main changes are:
>
> - MethodType now takes (params: List[Symbol]) as argument, no more
> List[Type]
> - To create parameter symbols for a synthetic MethodType, use
>     methodSymbol.newSyntheticValueParamss(argtypess)
>
> - the expression "foo(x = _)" was previously parsed as
>        foo(x$1 => x = x$1)
>    but is now parsed as a named application:
>        x$1 => foo(x = x$1)
>
> - a "copy" method is added to case classes
> - the tree copiers in the compiler are now called "treeCopy", no longer
> "copy"
>
> To learn how to use named / default arguments and how they are implemented,
> see http://www.scala-lang.org/sid/1
>
>
> Cheers: Lukas

--
Anders Bach Nielsen            |   http://www.cs.au.dk/~abachn/
University of Aarhus           |   abachn@cs.au.dk
-
 Earth men are real men!

Anders Bach Nielsen
Joined: 2008-12-17,
User offline. Last seen 42 years 45 weeks ago.
Re: names / defaults

Hey Lukas

Sorry, I thought is was the named arguments commit, but yes, sorry it
was the annotations commit :) But never the less, you committed a new
starr :) Okay, I have just today merge the earlydefs branch with trunk
and I am running the test suite to see if there are any failing test
cases. If not, then I will build a new starr of this for my branch,
where the early defs syntax has changed!

This starr will then contain the named arguments and the annotations
commit.

/Anders

Lukas Rytz wrote:
> Hi Anders,
>
> In fact I did not commit a new starr togeter with the named/default
> arguments
> (http://lampsvn.epfl.ch/trac/scala/changeset/17916).
> But I did commit a new starr with the new annotations implementation
> (http://lampsvn.epfl.ch/trac/scala/changeset/17925).
>
> The reason is that (actually in both changes), the pickle format has
> changed.
> The new starr was done to make sure it uses that new format. Also, we can
> now start using named/default arguments in the compiler/library.
>
> If you build a new starr just make sure you build it based on a recent
> version of trunk.
>
> Cheers: Lukas
>

rytz
Joined: 2008-07-01,
User offline. Last seen 45 weeks 5 days ago.
Re: names / defaults
OK thanks guys for spotting these problems, I fixed them in trunk (r17964).

1) the "method f$default$2 is defined twice" messages are no longer printed.

2) Assignment or named argument: I implemented Martin's proposal,
   i.e. when typechecking the assignment succeeds, you get a "ambiguous
   reference" error. see (*)

3) Workaround syntax: wrapping the assignment into {} or () doesn't work
   because they are eliminated by the parser. If you want the assignment,
   not the named argument, the "best" I see is:
     foo({x = 1; ()})
     foo(x = {x = 1})

4) One bug pointed out by David: the righthand side of a named argument
   could be treated again as a named argument. This is now fixed.

foo(x = {x = 1}))
==> reduced to: foo(x = 1)
==> reduced to: foo(1)   // should not happen!!


Lukas




(*) Example

scala> def foo(x: Int) = 0
scala> def bar(x: Unit) = 0
scala> def baz[T](x: T) = 0
scala> var x = 0

scala> foo(x = 1) // not ambigous: foo expects an Int,
res0: Int = 0     // so this has to be a named argument

scala> x
res1: Int = 0

scala> bar(x = 1)
<console>:7: error: reference to x is ambiguous; it is both, a parameter
name of the method and the name of a variable currently in scope.
       bar(x = 1)
             ^

scala> baz(x = 1)
<console>:7: error: reference to x is ambiguous; it is both, a parameter
name of the method and the name of a variable currently in scope.
       baz(x = 1)
             ^

scala> baz({x = 1; ()})  // assignment to variable 'x'
res1: Int = 0

scala> x
res2: Int = 1



DRMacIver
Joined: 2008-09-02,
User offline. Last seen 42 years 45 weeks ago.
Re: names / defaults

2009/6/2 Lukas Rytz :
> OK thanks guys for spotting these problems, I fixed them in trunk (r17964).
>
> 1) the "method f$default$2 is defined twice" messages are no longer printed.
>
> 2) Assignment or named argument: I implemented Martin's proposal,
>    i.e. when typechecking the assignment succeeds, you get a "ambiguous
>    reference" error. see (*)
>
> 3) Workaround syntax: wrapping the assignment into {} or () doesn't work
>    because they are eliminated by the parser. If you want the assignment,
>    not the named argument, the "best" I see is:
>      foo({x = 1; ()})
>      foo(x = {x = 1})
>
> 4) One bug pointed out by David: the righthand side of a named argument
>    could be treated again as a named argument. This is now fixed.
>
> foo(x = {x = 1}))
> ==> reduced to: foo(x = 1)
> ==> reduced to: foo(1)   // should not happen!!
>
>
> Lukas
>
>
>
>
> (*) Example
>
> scala> def foo(x: Int) = 0
> scala> def bar(x: Unit) = 0
> scala> def baz[T](x: T) = 0
> scala> var x = 0
>
> scala> foo(x = 1) // not ambigous: foo expects an Int,
> res0: Int = 0     // so this has to be a named argument
>
> scala> x
> res1: Int = 0
>
> scala> bar(x = 1)
> :7: error: reference to x is ambiguous; it is both, a parameter
> name of the method and the name of a variable currently in scope.
>        bar(x = 1)
>              ^
>
> scala> baz(x = 1)
> :7: error: reference to x is ambiguous; it is both, a parameter
> name of the method and the name of a variable currently in scope.
>        baz(x = 1)
>              ^
>
> scala> baz({x = 1; ()})  // assignment to variable 'x'
> res1: Int = 0
>
> scala> x
> res2: Int = 1

Could you clarify the ambiguity rules? Is the intent that if there's
no chance that it could be meant as a named argument then it will be
treated as an assignment? i.e. bar(y = "kittens") will pass the
assignment y = "kittens" to bar?

If so, I remain very uncomfortable with this: It results in the
expression bar(y = "kittens") potentially meaning two wildly different
things depending on the parameter names of bar. This makes code
comprehension substantially harder in my opinion.

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: names / defaults

On Tue, Jun 2, 2009 at 4:42 PM, David MacIver wrote:
> 2009/6/2 Lukas Rytz :
>> OK thanks guys for spotting these problems, I fixed them in trunk (r17964).
>>
>> 1) the "method f$default$2 is defined twice" messages are no longer printed.
>>
>> 2) Assignment or named argument: I implemented Martin's proposal,
>>    i.e. when typechecking the assignment succeeds, you get a "ambiguous
>>    reference" error. see (*)
>>
>> 3) Workaround syntax: wrapping the assignment into {} or () doesn't work
>>    because they are eliminated by the parser. If you want the assignment,
>>    not the named argument, the "best" I see is:
>>      foo({x = 1; ()})
>>      foo(x = {x = 1})
>>
>> 4) One bug pointed out by David: the righthand side of a named argument
>>    could be treated again as a named argument. This is now fixed.
>>
>> foo(x = {x = 1}))
>> ==> reduced to: foo(x = 1)
>> ==> reduced to: foo(1)   // should not happen!!
>>
>>
>> Lukas
>>
>>
>>
>>
>> (*) Example
>>
>> scala> def foo(x: Int) = 0
>> scala> def bar(x: Unit) = 0
>> scala> def baz[T](x: T) = 0
>> scala> var x = 0
>>
>> scala> foo(x = 1) // not ambigous: foo expects an Int,
>> res0: Int = 0     // so this has to be a named argument
>>
>> scala> x
>> res1: Int = 0
>>
>> scala> bar(x = 1)
>> :7: error: reference to x is ambiguous; it is both, a parameter
>> name of the method and the name of a variable currently in scope.
>>        bar(x = 1)
>>              ^
>>
>> scala> baz(x = 1)
>> :7: error: reference to x is ambiguous; it is both, a parameter
>> name of the method and the name of a variable currently in scope.
>>        baz(x = 1)
>>              ^
>>
>> scala> baz({x = 1; ()})  // assignment to variable 'x'
>> res1: Int = 0
>>
>> scala> x
>> res2: Int = 1
>
> Could you clarify the ambiguity rules? Is the intent that if there's
> no chance that it could be meant as a named argument then it will be
> treated as an assignment? i.e. bar(y = "kittens") will pass the
> assignment y = "kittens" to bar?
>
> If so, I remain very uncomfortable with this: It results in the
> expression bar(y = "kittens") potentially meaning two wildly different
> things depending on the parameter names of bar. This makes code
> comprehension substantially harder in my opinion.
>
>
Normally, I'd agree with you, David. But I think that passing a
side-effecting expression of unit into a function is sufficiently
bizarre that in practice f(x = e) will always be read as a named parameter.

Cheers

James Iry
Joined: 2008-08-19,
User offline. Last seen 1 year 23 weeks ago.
Re: names / defaults
With call-by-name parameters and a general friendliness towards "create your own control flow constructs" I don't think it's that bizarre.

Is it too late to rename the assignment operator to "foo := 3"?    ;-) 

On Tue, Jun 2, 2009 at 7:46 AM, martin odersky <martin.odersky@epfl.ch> wrote:

>
Normally, I'd agree with you, David. But I think that passing a
side-effecting expression of unit into a function is sufficiently
bizarre that in practice f(x = e) will always be read as a named parameter.

Cheers

rytz
Joined: 2008-07-01,
User offline. Last seen 45 weeks 5 days ago.
Re: names / defaults


On Tue, Jun 2, 2009 at 16:46, martin odersky <martin.odersky@epfl.ch> wrote:
On Tue, Jun 2, 2009 at 4:42 PM, David MacIver <david.maciver@gmail.com> wrote:
> 2009/6/2 Lukas Rytz <lukas.rytz@epfl.ch>:
>> OK thanks guys for spotting these problems, I fixed them in trunk (r17964).
>>
>> 1) the "method f$default$2 is defined twice" messages are no longer printed.
>>
>> 2) Assignment or named argument: I implemented Martin's proposal,
>>    i.e. when typechecking the assignment succeeds, you get a "ambiguous
>>    reference" error. see (*)
>>
>> 3) Workaround syntax: wrapping the assignment into {} or () doesn't work
>>    because they are eliminated by the parser. If you want the assignment,
>>    not the named argument, the "best" I see is:
>>      foo({x = 1; ()})
>>      foo(x = {x = 1})
>>
>> 4) One bug pointed out by David: the righthand side of a named argument
>>    could be treated again as a named argument. This is now fixed.
>>
>> foo(x = {x = 1}))
>> ==> reduced to: foo(x = 1)
>> ==> reduced to: foo(1)   // should not happen!!
>>
>>
>> Lukas
>>
>>
>>
>>
>> (*) Example
>>
>> scala> def foo(x: Int) = 0
>> scala> def bar(x: Unit) = 0
>> scala> def baz[T](x: T) = 0
>> scala> var x = 0
>>
>> scala> foo(x = 1) // not ambigous: foo expects an Int,
>> res0: Int = 0     // so this has to be a named argument
>>
>> scala> x
>> res1: Int = 0
>>
>> scala> bar(x = 1)
>> <console>:7: error: reference to x is ambiguous; it is both, a parameter
>> name of the method and the name of a variable currently in scope.
>>        bar(x = 1)
>>              ^
>>
>> scala> baz(x = 1)
>> <console>:7: error: reference to x is ambiguous; it is both, a parameter
>> name of the method and the name of a variable currently in scope.
>>        baz(x = 1)
>>              ^
>>
>> scala> baz({x = 1; ()})  // assignment to variable 'x'
>> res1: Int = 0
>>
>> scala> x
>> res2: Int = 1
>
> Could you clarify the ambiguity rules? Is the intent that if there's
> no chance that it could be meant as a named argument then it will be
> treated as an assignment? i.e. bar(y = "kittens") will pass the
> assignment y = "kittens" to bar?

Yes
 

>
> If so, I remain very uncomfortable with this: It results in the
> expression bar(y = "kittens") potentially meaning two wildly different
> things depending on the parameter names of bar. This makes code
> comprehension substantially harder in my opinion.
>
>
Normally, I'd agree with you, David. But I think that passing a
side-effecting expression of unit into a function is sufficiently
bizarre that in practice f(x = e) will always be read as a named parameter.


Also, in cases where you actually pass an assignment as argument, the
method name will most probably remind you of it's argument type Unit:

  synchronized(x = 1)

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: names / defaults

On Tue, Jun 02, 2009 at 07:52:27AM -0700, James Iry wrote:
> With call-by-name parameters and a general friendliness towards
> "create your own control flow constructs" I don't think it's that
> bizarre.

Yes, and I would like to point out that there is a potential element of
self-fulfilling prophecy in that assessment. I am becoming quite
enamored of the potential available in the composition of by-name
arguments. To have any chance of realizing this potential, we should
resist introducing "gotchas" which increase the cognitive load, and this
is definitely a gotcha -- however improbable we may think it is, it's
certainly one more thing one has to consider.

Although the selective-ambiguity solution which we discussed and lukas
speedily implemented is an improvement, I had concluded that drmaciver's
suggestion is a lot better -- there is no ambiguity possible if

foo(x = bar)

always means a named argument, and

foo((x = bar))

always means an assignment. And in fact I attempted to implement that
this weekend, but ran into exactly the problem lukas mentioned, which is
that parentheses are eliminated by the parser and they don't leave
enough residue to discriminate between these cases.

I realize everyone is overworked and the temptation to accept the mostly
acceptable and already implemented solution is strong. But I really
think we would be better off improving the handling of parentheses in
the parser (which are stripped in a rather ad hoc manner, I think it
could use improvement anyway) than shipping it this way.

DRMacIver
Joined: 2008-09-02,
User offline. Last seen 42 years 45 weeks ago.
Re: names / defaults

2009/6/2 martin odersky :
>> If so, I remain very uncomfortable with this: It results in the
>> expression bar(y = "kittens") potentially meaning two wildly different
>> things depending on the parameter names of bar. This makes code
>> comprehension substantially harder in my opinion.
>>
>>
> Normally, I'd agree with you, David. But I think that passing a
> side-effecting expression of unit into a function is sufficiently
> bizarre that in practice f(x = e) will always be read as a named parameter.

I think that actually makes it worse. It means that when the
assumption is violated it becomes *very* surprising, to the point
where it may not even occur to you to look for it. If we consider it
that unlikely a use case it seems much better to reduce the ambiguity
and make it very obvious when we're using that.

Additional suggestion (I don't have a working copy of trunk here at
the moment so can't test if this is already the case), but I'd hope
that foo{ x = e } should not parse as a named argument, so for the
side effecting case where you often have something like

doStuff(args)(action : =>Unit)

and conventionally call it as

doStuff(args) { action goes here }

assignment works as expected.

rytz
Joined: 2008-07-01,
User offline. Last seen 45 weeks 5 days ago.
Re: names / defaults


On Tue, Jun 2, 2009 at 17:55, David MacIver <david.maciver@gmail.com> wrote:
2009/6/2 martin odersky <martin.odersky@epfl.ch>:
>> If so, I remain very uncomfortable with this: It results in the
>> expression bar(y = "kittens") potentially meaning two wildly different
>> things depending on the parameter names of bar. This makes code
>> comprehension substantially harder in my opinion.
>>
>>
> Normally, I'd agree with you, David. But I think that passing a
> side-effecting expression of unit into a function is sufficiently
> bizarre that in practice f(x = e) will always be read as a named parameter.

I think that actually makes it worse. It means that when the
assumption is violated it becomes *very* surprising, to the point
where it may not even occur to you to look for it. If we consider it
that unlikely a use case it seems much better to reduce the ambiguity
and make it very obvious when we're using that.

Additional suggestion (I don't have a working copy of trunk here at
the moment so can't test if this is already the case), but I'd hope
that foo{ x = e } should not parse as a named argument, so for the
side effecting case where you often have something like

doStuff(args)(action : =>Unit)

and conventionally call it as

doStuff(args) { action goes here }

assignment works as expected.


It does parse as a named argument, and I agree this is not ideal. All of this
is handled during parsing, and the only change I did to the parser was adding
support for default arguments.
In the typer, when named arguments are handled, I can't tell anymore whether
the argument was { arg }  or ( arg ) or ( { arg } ), they all look the same.

Lukas

David Hall
Joined: 2008-12-28,
User offline. Last seen 42 years 45 weeks ago.
Re: names / defaults

On Tue, Jun 2, 2009 at 12:28 PM, Lukas Rytz wrote:
>
>
> On Tue, Jun 2, 2009 at 17:55, David MacIver wrote:
>>
>> 2009/6/2 martin odersky :
>> >> If so, I remain very uncomfortable with this: It results in the
>> >> expression bar(y = "kittens") potentially meaning two wildly different
>> >> things depending on the parameter names of bar. This makes code
>> >> comprehension substantially harder in my opinion.
>> >>
>> >>
>> > Normally, I'd agree with you, David. But I think that passing a
>> > side-effecting expression of unit into a function is sufficiently
>> > bizarre that in practice f(x = e) will always be read as a named
>> > parameter.
>>
>> I think that actually makes it worse. It means that when the
>> assumption is violated it becomes *very* surprising, to the point
>> where it may not even occur to you to look for it. If we consider it
>> that unlikely a use case it seems much better to reduce the ambiguity
>> and make it very obvious when we're using that.
>>
>> Additional suggestion (I don't have a working copy of trunk here at
>> the moment so can't test if this is already the case), but I'd hope
>> that foo{ x = e } should not parse as a named argument, so for the
>> side effecting case where you often have something like
>>
>> doStuff(args)(action : =>Unit)
>>
>> and conventionally call it as
>>
>> doStuff(args) { action goes here }
>>
>> assignment works as expected.
>
>
> It does parse as a named argument, and I agree this is not ideal. All of
> this
> is handled during parsing, and the only change I did to the parser was
> adding
> support for default arguments.
> In the typer, when named arguments are handled, I can't tell anymore whether
> the argument was { arg }  or ( arg ) or ( { arg } ), they all look the same.
>

Still, this seems like a serious problem to me. Might I propose a way
around all of this? Why not make <- the named parameters operator.
It's not necessarily the best looking syntax, but it solves this
problem in a very straightforward way.

Ricky Clarkson
Joined: 2008-12-19,
User offline. Last seen 3 years 2 weeks ago.
Re: names / defaults

An excellent suggestion.

2009/6/2 David Hall :
> On Tue, Jun 2, 2009 at 12:28 PM, Lukas Rytz wrote:
>>
>>
>> On Tue, Jun 2, 2009 at 17:55, David MacIver wrote:
>>>
>>> 2009/6/2 martin odersky :
>>> >> If so, I remain very uncomfortable with this: It results in the
>>> >> expression bar(y = "kittens") potentially meaning two wildly different
>>> >> things depending on the parameter names of bar. This makes code
>>> >> comprehension substantially harder in my opinion.
>>> >>
>>> >>
>>> > Normally, I'd agree with you, David. But I think that passing a
>>> > side-effecting expression of unit into a function is sufficiently
>>> > bizarre that in practice f(x = e) will always be read as a named
>>> > parameter.
>>>
>>> I think that actually makes it worse. It means that when the
>>> assumption is violated it becomes *very* surprising, to the point
>>> where it may not even occur to you to look for it. If we consider it
>>> that unlikely a use case it seems much better to reduce the ambiguity
>>> and make it very obvious when we're using that.
>>>
>>> Additional suggestion (I don't have a working copy of trunk here at
>>> the moment so can't test if this is already the case), but I'd hope
>>> that foo{ x = e } should not parse as a named argument, so for the
>>> side effecting case where you often have something like
>>>
>>> doStuff(args)(action : =>Unit)
>>>
>>> and conventionally call it as
>>>
>>> doStuff(args) { action goes here }
>>>
>>> assignment works as expected.
>>
>>
>> It does parse as a named argument, and I agree this is not ideal. All of
>> this
>> is handled during parsing, and the only change I did to the parser was
>> adding
>> support for default arguments.
>> In the typer, when named arguments are handled, I can't tell anymore whether
>> the argument was { arg }  or ( arg ) or ( { arg } ), they all look the same.
>>
>
> Still, this seems like a serious problem to me. Might I propose a way
> around all of this? Why not make <- the named parameters operator.
> It's not necessarily the best looking syntax, but it solves this
> problem in a very straightforward way.
>

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: names / defaults

We do have a difficult decision here. As language insiders we
sometimes forget the expectations of non-experts. I believe for a
non-expert f(x = e) would be by far the most natural syntax. We have
to weigh this against possible complications. I still believe
detecting the ambiguity is good enough to deal with those
complications. But we might
think of some refinements. I also think that named arguments should be
permitted only in parentheses not in braces. That should be easy to
do. And an extra pair of parens should work for disambiguation, if
there is an ambiguity. Maybe even require it always, as Paul suggests?
i.e. it would be

synchronized { x = 1 } // OK
synchronized((x = 1)) // OK
synchronized(x = 1) // error: no parameter named x of synchronized

Either that, or require the ((...)) in case of ambiguities.

Cheers

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland