Scala case class private constructor but public apply method
Asked Answered
C

4

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)
Cling answered 17/11, 2013 at 12:47 Comment(3)
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]Goethite
@LuigiPlinge I am using Scala 2.10.3Cling
Seems like it's the line case class Meter private (m: Int) that causes the error, when declared as top level object (scalafiddle.net/console/eb6fdc36b281b7d5eabf33396c2683a2) but it works when declared within another object or the REPL (scalafiddle.net/console/cdc0d6e63aa8e41c89689f54970bb35f)Goethite
J
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

Jugurtha answered 17/11, 2013 at 13:10 Comment(4)
@senia, no it doesn't. Here's the compile error for new Meter(10){} ; trait Meter is a trait; does not take constructor argumentsJugurtha
I missed that. You should define Meter as trait Meter { def m: Int } to allow access to m. You could also make it sealed.Regatta
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.Cling
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.Oklahoma
G
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.

Geyer answered 22/6, 2018 at 17:33 Comment(0)
Z
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

Zoba answered 17/11, 2013 at 13:55 Comment(0)
V
2

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
Vegetate answered 18/4, 2023 at 10:54 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.