42

If I have the following case class with a private constructor and I can not access the apply-method in the companion object.

case class Meter private (m: Int)

val m = Meter(10) // constructor Meter in class Meter cannot be accessed...

Is there a way to use a case class with a private constructor but keep the generated apply-method in the companion public?

I am aware that there is no difference (in my example) between the two options:

val m1 = new Meter(10)
val m2 = Meter(10)

but I want to forbid the first option.

-- edit --

Surprisingly the following works (but is not really what i want):

val x = Meter
val m3 = x(10) // m3  : Meter = Meter(10)
Erik
  • 2,888
  • 2
  • 18
  • 35
  • What version of Scala are you using? I just tried it in my 2.10.0 REPL and `val m2 = Meter(10)` does not give any error] – Luigi Plinge Nov 17 '13 at 16:47
  • @LuigiPlinge I am using Scala 2.10.3 – Erik Nov 17 '13 at 17:30
  • Seems like it's the line `case class Meter private (m: Int)` that causes the error, when declared as top level object (http://scalafiddle.net/console/eb6fdc36b281b7d5eabf33396c2683a2) but it works when declared within another object or the REPL (http://scalafiddle.net/console/cdc0d6e63aa8e41c89689f54970bb35f) – Luigi Plinge Nov 17 '13 at 19:47

4 Answers4

52

Here's the technique to have a private constructor and a public apply method.

trait Meter {
  def m: Int
}

object Meter {   
  def apply(m: Int): Meter = { MeterImpl(m) }
  private case class MeterImpl(m: Int) extends Meter { println(m) }
}

object Application extends App {
  val m1 = new Meter(10) // Forbidden
  val m2 = Meter(10)
}

Background information private-and-protected-constructor-in-scala

Community
  • 1
  • 1
  • @senia, no it doesn't. Here's the compile error for new Meter(10){} ; trait Meter is a trait; does not take constructor arguments –  Nov 17 '13 at 13:44
  • 1
    I missed that. You should define `Meter` as `trait Meter { def m: Int }` to allow access to `m`. You could also make it `sealed`. – senia Nov 17 '13 at 13:46
  • 2
    I think this is a good workaround because you get the asked behavior and some of the benefits from case classes (e.g. implemented equals). But it's kind of heavy compared to the single case class. – Erik Nov 17 '13 at 13:51
  • 2
    In this case wouldn't `println(Meter(1))` result in `> MeterImpl(1)`? Not that this doesn't answer the question, but I'd like a solution where that doesn't happen. – Ritwik Bose Jun 05 '15 at 01:44
5

It seems the requested behavior (private constructor but public .apply) may be the way Scala 2.12 implements these.

I came to this from the opposing angle - would like a private case class constructor also block the .apply method. Reasons here: https://github.com/akauppi/case-class-gym

Interesting, how use cases differ.

akauppi
  • 17,018
  • 15
  • 95
  • 120
2

It is possible with some implicit tricks:

// first case 
case class Meter[T] private (m: T)(implicit ev: T =:= Int)
object Meter { 
  def apply(m: Int) = new Meter(m + 5) 
}

created some another constructor (and apply method signature) but guaranty that parameter can be only Int.

And after you have case class with case class features (with pattern matching, hashcode & equals) exclude default constructor:

scala> val m = Meter(10)
m: Metter[Int] = Meter(15)

scala> val m = new Meter(10)
<console>:9: error: constructor Meter in class Meter cannot be accessed in object $iw
       val m = new Meter(10)

OR with type tagging (naive implementation):

trait Private
case class Meter private (m: Integer with Private)
object Meter {
  def apply(m: Int) = new Meter((m + 5).asInstanceOf[Integer with Private])
}

It works as expected:

val x = new Meter(10)
<console>:11: error: constructor Meter in class Meter cannot be accessed in object $iw
              new Meter(10)
              ^

val x = Meter(10)
x: Meter = Meter(15)

Some possible issues with primitive types and type tags described here

Yuriy
  • 2,772
  • 15
  • 22
1

The solutions above are rather complicated. It is just:

case class Meter private (m: Int)
object Meter { 
 def apply(m: Int): Meter = new Meter(m)
}

Test

new Meter(10) // fails to compile
Meter(10) // compiles just fine
Julian Pieles
  • 3,880
  • 2
  • 23
  • 33