6

I have a case class that looks about like this:

case class A(first: B*)(second: C*)

Both first and second are repeated, so I put the in separate parameter lists. However, I expect that second might be empty in a substantial number of cases, so being able to use the class like A(???, ???) without trailing empty parentheses would be nice. I tried the following:

case class A(first: B*)(second: C*) {
  def this(first: B*) = this(first: _*)()
}

Which gives me ambiguous reference to overloaded definition.

Is there a way to unambiguously write this constructor call? (and would I be able to call the overloaded constructor without cluttering up the syntax again?) My guess is no, with some arguments about how such syntactic sugar would break currying or some such, but I'd prefer to hear it from someone with more Scala knowledge than me ;)

Silly Freak
  • 4,061
  • 1
  • 36
  • 58

3 Answers3

3

The following may accomplish your goal:

case class Foo private(first: List[Int], second: List[Int])

object Foo {
    def apply(first: Int*) = new Foo(first.toList, List.empty[Int]) { 
        def apply(second: Int*) = new Foo(first.toList, second.toList)
    }
}

And then you can do:

Foo(1, 2, 3)
Foo(1, 2, 3)(4, 5, 6)

Edit by @SillyFreak: This variant doesn't give a "reflective access of structural type member" warning, so I think it should be a little better performance-wise:

case class Foo private (first: List[Int], second: List[Int])

object Foo {
  def apply(first: Int*) = new Foo(first.toList, List.empty[Int]) with NoSecond

  trait NoSecond {
    self: Foo =>
    def apply(second: Int*) = new Foo(first.toList, second.toList)
  }
}

Foo(1, 2, 3)
Foo(1, 2, 3)(4, 5, 6)
Silly Freak
  • 4,061
  • 1
  • 36
  • 58
Ben Reich
  • 16,222
  • 2
  • 38
  • 59
  • 2
    Wow, now that's pretty slick! I didn't know you could instantiate a `case class` with a refinement. I mistakenly believed `case class` implied `final`, but upon looking it up again, I can see that this is not the case. *And* it appears `unapply` still functions in a reasonable way. Well done! – acjay Feb 21 '15 at 13:10
  • 1
    Great answer! If you don't mind, I'd add a variant that doesn't use reflective calls in an edit. Just remove the edit if you think that's inappropriate of me – Silly Freak Feb 22 '15 at 13:28
2

Edit Ben Reich has broken through the barrier and proved that this is, in fact possible. Here's a slight improvement over what he's got, that doesn't rely on the reflective calls feature:

case class Foo private(first: List[Int], second: List[Int]) {
  def apply(newSecond: Int*) = new Foo(first.toList, newSecond.toList)
}
object Foo {
  def apply(first: Int*) = new Foo(first.toList, List.empty[Int])
}

The only possible disadvantage versus his example is that you could keep calling a Foo object multiple times, whereas in his example, you can only do so for a Foo constructed with only first provided.


In some sense, I don't think the two options should clash. This is because you can't directly call a multiple parameter list method with only one of its parameter lists. Both parameter lists are an integral part of the method call, and technically, you have to use the post-fix _ operator to partially apply the first parameter list and get a function back that takes the second parameter list (taking a sequence, instead of varargs, you might note). This happens automagically in some cases, but not always. So the compiler should be able to tell between the two options simply by always assuming your executing a complete method call, unless you explicitly use _.

But overloading is a little iffy in Scala, often due to implementation details of the translation to JVM representation. Or maybe it's just because there's no syntactic difference between calling a method vs. a function, and the analysis that would permit this simply doesn't exist.

However, this doesn't work when you try to call the overloaded method. Here are several variations:

Overloaded apply method

scala> :paste
// Entering paste mode (ctrl-D to finish)

case class A(first: Int*)(second: Int*)
object A { def apply(first: Int*) = new A(first: _*)() }

// Exiting paste mode, now interpreting.

defined class A
defined object A

scala> A(1,2,3)
<console>:12: error: ambiguous reference to overloaded definition,
both method apply in object A of type (first: Int*)(second: Int*)A
and  method apply in object A of type (first: Int*)A
match argument types (Int,Int,Int)
              A(1,2,3)
              ^

Overloaded constructor

(original question's example)

Split constructor and apply

scala> :paste
// Entering paste mode (ctrl-D to finish)

class A(first: Int*)(second: Int*)
object A { def apply(first: Int*) = new A(first: _*)() }


// Exiting paste mode, now interpreting.

defined class A
defined object A

scala> A(5, 6, 7)
res5: A = A@47a36ea0

scala> A(5, 6, 7)(4, 5)
<console>:12: error: A does not take parameters
              A(5, 6, 7)(4, 5)
                        ^

scala> new A(5, 6, 7)(4, 5)
res7: A = A@62a75ec

scala> new A(5, 6, 7)
<console>:10: error: missing arguments for constructor A in class A
              new A(5, 6, 7)
              ^

So whether you try to get this overloaded behavior with apply or with the constructor, you're going to have the same issue with ambiguity. As you can see, making it a regular class (to not define the default apply) and splitting the methods works, but I'm pretty sure it doesn't achieve the elegance you're looking for.

acjay
  • 34,571
  • 6
  • 57
  • 100
  • Regarding your edit, I'd probably try to extract a trait here somewhere to get rid of that possibility, haven't thought it through yet though. I'd really dislike if these multiple calls were possible. Thanks for the overview of options and their implications! – Silly Freak Feb 22 '15 at 13:21
1

Case classes already have a generated implementation of apply (in a generated companion object), which is why you can do val a = A()() instead of val a = new A()().

All you need to do is write your own implementation of apply that does what you want:

object A {
  def apply(first: B*): A = new A(first: _ *)()
}

This is a useful general technique for writing alternative "constructors" which are really factory methods in the companion object.

Richard Close
  • 1,895
  • 11
  • 17
  • Have you tried this solution? I'm pretty sure it doesn't work. If you're in the REPL, make sure to define everything at once with `:paste` to ensure it's not just shadowing. – acjay Feb 20 '15 at 21:18
  • I've just tried it (not in REPL), and it works half the way. the key is the `new`: there's just one constructor, but several `apply` methods, so no ambiguity. It seems I was on to something with `and would I be able to call the overloaded constructor without cluttering up the syntax again?`, because actually instantiating the case class with `A(???)` or `A(???)(???)` is ambiguous now. Still +1, because it shows an important point: apply and constructors are very different when it comes to ambiguity (and most other stuff, really) – Silly Freak Feb 20 '15 at 21:23