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

Scala Wart: Classes cannot have only implicit parameter lists #2576

Closed
lihaoyi opened this issue May 29, 2017 · 6 comments · Fixed by #14840
Closed

Scala Wart: Classes cannot have only implicit parameter lists #2576

lihaoyi opened this issue May 29, 2017 · 6 comments · Fixed by #14840
Assignees
Milestone

Comments

@lihaoyi
Copy link
Contributor

lihaoyi commented May 29, 2017

Opening this issue, as suggested by Martin, to provide a place to discuss the individual warts brought up in the blog post Warts of the Scala Programming Language and the possibility of mitigating/fixing them in Dotty (and perhaps later in Scala 2.x). These are based on Scala 2.x behavior, which I understand Dotty follows closely, apologies in advance if it has already been fixed


This doesn't work:

@ class Foo(i: Int)
defined class Foo

@ new Foo(1)
res50: Foo = $sess.cmd49$Foo@7230510

@ class Bar(implicit i: Int)
defined class Bar

@ new Bar(1)
cmd52.sc:1: no arguments allowed for nullary constructor Bar: ()(implicit i: Int)$sess.cmd51.Bar
val res52 = new Bar(1)
                    ^
Compilation Failed

But this does:

@ new Bar()(1)
res52: Bar = $sess.cmd51$Bar@467de021

This one straddles the line between "Wart" and "Bug", but definitely should
be fixed so that a class defined with one argument list doesn't magically
sprout two.

@lihaoyi lihaoyi changed the title Classes cannot have only implicit parameter lists Scala Wart: Classes cannot have only implicit parameter lists May 29, 2017
@odersky odersky marked this as a duplicate of #2794 Jul 16, 2017
@odersky
Copy link
Contributor

odersky commented Jan 24, 2018

I think this will be affected by the explicitly proposal.

@DhashS
Copy link

DhashS commented Mar 10, 2018

This bug hit me pretty hard recently, I was looking to curry the type signature of a function with three types. One is supplied by the user, and the other two derived from function arguments. I used code that looks like

  private[Mixer] final class _Mixer[T <: FluidT](implicit ev: ClassTag[T]) {
    def apply[A <: FluidT, B <: FluidT](fst: Fluid[A], snd: Fluid[B])(
        mxe: MixerT[A, B, T]): Fluid[T] =
      Mixer[A, B, T](fst, snd)
  }

 private lazy val _mixer = new Mixer[Nothing]

  def as[T <: FluidT] = _mixer.asInstanceOf[Fluid[T]]

(see http://caryrobbins.com/dev/scala-type-curry/)

Problem is, that the asInstanceOf call doesn't regenerate the ClassTag.

No big deal, I thought, I'll just insert some classtag implicit evidence, and do the new call in as, but now I have to call .apply for it to resolve to the inner method apply, instead of the implicit parameter list of as. I'd much rather the implicits go away at compile time here and allow me to call into the inner method apply.

The only reason I'm doing this hack is so I can have curried type parameter lists.

@Blaisorblade
Copy link
Contributor

The only reason I'm doing this hack is so I can have curried type parameter lists.

That's also something we'll hopefully also support directly, if there isn't an issue feel free to create one.

@joan38
Copy link
Contributor

joan38 commented Aug 27, 2018

Hi,

I get hit by this one:

trait Encoder[T] {
  def apply(o: T): String
}

object Encoder {
  implicit val intEncoder: Encoder[Int] = _.toString
}

trait Creatable[Resource: Encoder] {
  def create(resource: Resource): Unit = ???   
}

case class PodsOperations() extends Creatable[Int]

The last line won't compile.
The workaround:

case class PodsOperations() extends Creatable[Int]()

Thanks

@odersky
Copy link
Contributor

odersky commented Apr 11, 2019

It turns out this is can alternatively be seen as the problem of old-style implicits taking regular arguments. If we move to given parameters and arguments, all test cases work as expected. E.g. the following compiles:

class TC
implied tc for TC

class Foo given TC

object Test {
  new Foo
  new Foo given tc
  new Foo()
  new Foo() given tc
  Foo()
  Foo() given tc
}

Classes without a leading regular parameter list still assume () as first parameter list. I tried for a while to avoid this but in the end this created more problems than it solved. In particular, with creator applications, it's quite reasonable to demand that Foo() works even if class Foo is parameterless. Otherwise we'd either have to go back to new Foo for this case, or make sure everyone creates their classes with at least a () parameter list. Either choice is unpalatable.

But if creator applications take a () it's consistent to expect that corresponding constructors do so as well.

@odersky odersky closed this as completed Apr 11, 2019
@smarter
Copy link
Member

smarter commented Aug 24, 2020

I'm reopening this issue because the syntax has evolved quite a bit since it was closed and the situation is now "wart-y" again in my opinion:

given Int = 1

class Foo(using x: Int)

new Foo // compiles
new Foo() // compiles
new Foo(using 1) // compiles
new Foo()(using 1) // compiles

The fact that there's some sort of optional empty parameter list is fairly weird, but it gets weirder when combined with the fact that we can now have a regular parameter list after a using parameter list:

class Bar(using x: Int)(y: String) // For a more realistic usecase, see https://github.com/lampepfl/dotty-feature-requests/issues/133

new Bar("") // ERROR: too many arguments for constructor Bar: ()(using x: Int)(y: String): Bar
new Bar()("") // compiles
new Bar(using 1)("") // compiles
new Bar()(using 1)("") // compiles

Here in particular, the fact that one cannot write new Bar("") (or Bar("") using creator application syntax) seems like a real wart to me.

@smarter smarter reopened this Aug 24, 2020
odersky added a commit to dotty-staging/dotty that referenced this issue Apr 4, 2022
Fixes scala#2576

As the discussion in scala#2576 shows, we still have some problems with the implicitly
inserted empty parameter lists for class constructors. We do need that empty list
to support syntax like `C()` and `new C()`. But it gets in the way if a class has
using clauses. Example from the issue:
```scala
class Bar(using x: Int)(y: String)
given Int = ???
def test = new Bar("")
```
Here, an implicitly inserted `()` in front makes the last line fail. We'd need
`new Bar()("")`.

If a class has only using clauses as parameters we now insert a `()` at the end
instead of at the start. That makes the example compile.

For old-style implicit parameters we don't have a choice. We still need the `()` at
the start since otherwise we'd change the meaning of calls with explicit arguments
for the implicit parameters.
michelou pushed a commit to michelou/dotty that referenced this issue Apr 25, 2022
Fixes scala#2576

As the discussion in scala#2576 shows, we still have some problems with the implicitly
inserted empty parameter lists for class constructors. We do need that empty list
to support syntax like `C()` and `new C()`. But it gets in the way if a class has
using clauses. Example from the issue:
```scala
class Bar(using x: Int)(y: String)
given Int = ???
def test = new Bar("")
```
Here, an implicitly inserted `()` in front makes the last line fail. We'd need
`new Bar()("")`.

If a class has only using clauses as parameters we now insert a `()` at the end
instead of at the start. That makes the example compile.

For old-style implicit parameters we don't have a choice. We still need the `()` at
the start since otherwise we'd change the meaning of calls with explicit arguments
for the implicit parameters.
@Kordyjan Kordyjan added this to the 3.2.0 milestone Aug 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants