Skip to content

Chained call to apply after parens forgets to expand first apply #9348

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

Closed
johnynek opened this issue Jul 12, 2020 · 10 comments
Closed

Chained call to apply after parens forgets to expand first apply #9348

johnynek opened this issue Jul 12, 2020 · 10 comments

Comments

@johnynek
Copy link

Minimized code

In this PR: typelevel/cats#3515

part of the diff worked in scala 2, but not 3.

  def ap[A, B](f: () => A => B)(fa: () => A): () => B =
    () => f().apply(fa())

The error message on 0.24.0:

[error] -- [E050] Type Error: /home/oscarboykin/oss/cats/core/src/main/scala/cats/instances/function.scala:84:14 
[error] 84 |        () => f().apply(fa())                                                                       
[error]    |              ^                
[error]    |              value f does not take parameters
[error] one error found     
[error] (coreJVM / Compile / compileIncremental) Compilation failed 

changing the code to:

      def ap[A, B](f: () => A => B)(fa: () => A): () => B =    
        () => {                                       
          val fnAB = f()                    
          fnAB(fa())    
        }    

fixed the matter, but the original code was accepted by scala 2. There may be some language change in scala 3 that made this code illegal, but if that's the case I think a clearer error message would help greatly.

@travisbrown
Copy link
Contributor

You can minimize this a little further, and there are slightly better workarounds (either using explicit apply in both cases or neither):

scala> def foo(f: () => () => Int): Int = f()()    
def foo(f: () => () => Int): Int

scala> def foo(f: () => () => Int): Int = f.apply.apply
def foo(f: () => () => Int): Int

scala> def foo(f: () => () => Int): Int = f().apply
1 |def foo(f: () => () => Int): Int = f().apply
  |                                   ^
  |                                   value f does not take parameters

I was looking at some of the code related to nullary methods in Dotty recently, and I think the issue is just the isApplyProto check here. Removing it fixes this code and doesn't cause any tests to fail (including the example in #1639, which I think was the original motivation for the check).

@Sciss
Copy link
Contributor

Sciss commented Oct 1, 2020

Ran into the same thing (Dotty 0.27.0-RC1):

trait Ref[A] {
  def apply(): A
}

trait Test {
  val queue: Ref[Vector[Int]]
  
  def test(idx: Int): Int = 
    queue().apply(idx)   // !
}

value queue in trait Test does not take parameters

You have to spell out the first apply to make it work. It seems to have to do with repeated calls to apply methods. (Note that I want to spell out the second apply because actually I have a second implicit parameter list, which is not needed to reproduce the issue).

@bishabosha bishabosha changed the title code rejected by dotty, not scala 2, for unclear reason Chained call to apply after parens forgets to expand first apply Oct 1, 2020
@odersky
Copy link
Contributor

odersky commented Jan 3, 2021

I think should close this. Scala 3 will only insert one apply method per call, and I believe that's in balance a positive thing.

@odersky odersky closed this as completed Jan 3, 2021
@Sciss
Copy link
Contributor

Sciss commented Jan 3, 2021

Wait… but this still doesn't work (-M3):

queue().apply(idx)   // !

https://scastie.scala-lang.org/ARjsImPPTd68BjRglYancg

Here, Scala does insert exactly one apply method call, or not?

Also, you will want to allow multiple insertions for multi-dimensional array look-up, like x(1)(2) (this does work currently)

@odersky
Copy link
Contributor

odersky commented Jan 3, 2021

I believe it won't insert an apply over an existing apply. Multi-dimensional lookup is different since the inserted apply's are separated by arguments

@johnynek
Copy link
Author

johnynek commented Jan 3, 2021

Is this just an arbitrary breakage wrt scala 2 or is there a clear motivation why this is simpler?

Is the idea to it a tax on nesting use of apply since that is considered bad?

@odersky
Copy link
Contributor

odersky commented Jan 3, 2021

Nested use of applys can easily become infinite. There needs to be a simple rule to avoid that.

@Sciss
Copy link
Contributor

Sciss commented Jan 3, 2021

But there is no nesting in these examples? What would be a problematic case in your opinion?

@odersky
Copy link
Contributor

odersky commented Jan 3, 2021

The problem is finding a simple rule that will not let the compiler blow up in some (maybe far fetched) cases. The fact that some other cases are safe can make a difference only if there is a simple rule to separate safe from unsafe.

@johnynek
Copy link
Author

johnynek commented Jan 4, 2021

This was just a bug report about code scala 2 accepted. Travis suggested an edit which caused no tests to fail and fix the issue. I'm confused why this is even in the running for changes in scala for 3. Can you link to bug reports you have had with the scala 2 behavior?

I'm sorry if this sounds confrontational buts it's just frustrating that it seems like an arbitrary breakage relative to scala 2 is being thrown in on the hypothesis that it could cause problems despite scala 2 supporting this with no problems that I'm aware of.

Scala 2 has gotchas but honestly, in 10 years of use I don't recall this once.

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

No branches or pull requests

4 participants