Scala case class private constructor but public apply method
Solution 1
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
Solution 2
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.
Solution 3
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
Related videos on Youtube
Erik
I have studied computer science at the university of Hamburg and I'm now a full-stack developer and web performance engineer at Baqend. www.baqend.com
Updated on July 09, 2022Comments
-
Erik almost 2 years
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)
-
Luigi Plinge over 10 yearsWhat 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] -
Erik over 10 years@LuigiPlinge I am using Scala 2.10.3
-
Luigi Plinge over 10 yearsSeems 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)
-
-
Farmor over 10 years@senia, no it doesn't. Here's the compile error for new Meter(10){} ; trait Meter is a trait; does not take constructor arguments
-
senia over 10 yearsI missed that. You should define
Meter
astrait Meter { def m: Int }
to allow access tom
. You could also make itsealed
. -
Erik over 10 yearsI 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.
-
Ritwik Bose almost 9 yearsIn 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.