case class with private constructor/apply and factory method?

1,242 views
Skip to first unread message

Markus Jais

unread,
Nov 10, 2012, 8:43:48 AM11/10/12
to scala...@googlegroups.com
Hello,
at work when I write Java classes for things like a customer id I always make the classes immutable and sometimes
I construct them with factory methods like "fromLong(long id"), etc.
This makes the code easier to read, avoids all threading problems with that class, etc.

In Scala I came up with this:

class CustomerId private (id: Long)
object CustomerId {
  // add validation logic, etc
  def fromLong(id: Long) = new CustomerId(id)
}

I would like to use a case class so I can get equals/hashcode for free but a case class still allows me to do this:

val c = CustomerId(42) which I do not want. It might be ok and I could override the apply() method and call fromLong but 
it there a way to make sure that an instance of my case class can only(!) be generated with the factory Method?

Cheers,

Markus

Sonnenschein

unread,
Nov 10, 2012, 9:05:57 AM11/10/12
to scala...@googlegroups.com, Markus Jais
Hi Markus,

I don't claim that it's best practice but what about the following pattern:

case class C(...) {
  require(C.isValid)
}
object C {
  def isValid(...): Boolean = ...
  def from(...): C = ... // formal par list equals to that of the class
  def apply(...): C = ... // formal par list doesn't equal to that of the class
}

Peter

Sonnenschein

unread,
Nov 10, 2012, 9:57:32 AM11/10/12
to scala...@googlegroups.com, Markus Jais
I actually meant:

case class C(...) {
  require(C.isValid(...))

}
object C {
  def isValid(...): Boolean = ...

  def from (...): Option[C] = ... // formal par list equals to that of the class
  def apply(...): Option[C] = ... // formal par list doesn't equal to that of the class
}

Som Snytt

unread,
Nov 10, 2012, 7:39:56 PM11/10/12
to Markus Jais, scala...@googlegroups.com
On Sat, Nov 10, 2012 at 5:43 AM, Markus Jais <marku...@yahoo.de> wrote:
it there a way to make sure that an instance of my case class can only(!) be generated with the factory Method?


How about this trick to get a nice error message if they try to use the apply?

package noapply


class CustomerId private (id: Long)

object CustomerId {
  @annotation.implicitNotFound("Use fromLong to obtain a CustomerId")
  private[this] trait Dummy

  // add validation logic, etc
  def fromLong(id: Long) = new CustomerId(id)
  def apply(id: Long)(implicit d: Dummy) = ???
}

object Test extends App {
  val a = CustomerId fromLong 7L
  val b = CustomerId(8L)
}

Sonnenschein

unread,
Nov 11, 2012, 3:28:59 AM11/11/12
to scala...@googlegroups.com, Markus Jais
The idea of the implicit trick is interesting, indeed.
But he wanted a case class. After prepending case and calling CustomerId(8L) you get "error: ambiguous reference to overloaded definition" and not the expected implicitNotFound...

Markus Jais

unread,
Nov 11, 2012, 6:04:33 AM11/11/12
to scala...@googlegroups.com
Hi Peter,

thanks. I tried that but I still can call:

val myc3 = C("a") // if the argument to the class is a String and the apply method has a different signature

Is there a way to actually make the constructor call private to that only the "from" method can be called?

I am just curios, writing a regular class with equals/hashcode is no problem but it might be nice to have a case class here.

Cheers,

Markus





Von: Sonnenschein <peter...@arcor.de>
An: scala...@googlegroups.com
CC: Markus Jais <marku...@yahoo.de>; marku...@yahoo.de
Gesendet: 15:57 Samstag, 10.November 2012
Betreff: [scala-user] Re: case class with private constructor/apply and factory method?

Sonnenschein

unread,
Nov 11, 2012, 8:06:23 AM11/11/12
to scala...@googlegroups.com, Markus Jais
Hi Markus,

I too would try to stick with case classes.

My above pattern allows calling the generated apply directly and also indirectly by from. Passing an invalid value to the generated apply results in an exception while calling from returns an Option. So, in this schema, the user has the freedom to decide on the instantiation method depending on whether he is sure the validation won't fail.

If you want to revoke this choice from the user and ensure that your check is always run prior to the instantiation, you can work around declaring your case class abstract and provide its implementation within its companion object:

abstract case class C private(...)
object C {
  class InnerC private[C](override val ...) extends C(...)

  def isValid(...): Boolean = ...

  def apply(...): Option[C] = if (isValid(...)) Some(new InnerC(...)) else None

  def from (...): Option[C] = ...

}


Can you go along with that? Sorry, I cannot suggest an easier approach, maybe somebody else can.

Peter

Artie Peshimam

unread,
Nov 11, 2012, 8:45:28 AM11/11/12
to Sonnenschein, scala-user, Markus Jais
It seems to me that the limitation is intentional as case classes must generate apply/unapply to support pattern matching. If you restrict the users ability to use either, you dont really have a case class any more. I think your desire here is valid. Perhaps we can see if it's possible to add an annotation or something to generate equals/hashcode (with restrictions on inheritance) 

Som Snytt

unread,
Nov 11, 2012, 2:33:07 PM11/11/12
to Markus Jais, scala...@googlegroups.com

$ scalac noapply.scala
noapply.scala:22: error: constructor CustomerId in class CustomerId cannot be accessed in object Test
  val b = CustomerId(8L)

Case class apply is rewritten to the constructor.  If the constructor is private, you have the behavior you're looking for, right?

If you want to suspend generation of apply altogether, you can do that:

abstract case class NoApply(id: Long)

object NoApply {
  private class Applied(id: Long) extends NoApply(id)
  def fromLong(id: Long): NoApply = new Applied(id)
}

val f = NoApply fromLong 9L
//val g = NoApply(11L)
f match {
  case NoApply(i) => println("id "+ i)
  case _ => println("?")

Markus Jais

unread,
Nov 11, 2012, 2:33:29 PM11/11/12
to Artie Peshimam, Sonnenschein, scala-user
Hi all,

thanks for your comments. I like Peter's implementation and this might be what I need for my particular class. 
For others I may go through the (very small) trouble of using a "normal" class and write the hashcode/equals, etc methods myself.

Cheers,

Markus


Von: Artie Peshimam <apes...@gmail.com>
An: Sonnenschein <peter...@arcor.de>
CC: scala-user <scala...@googlegroups.com>; Markus Jais <marku...@yahoo.de>
Gesendet: 14:45 Sonntag, 11.November 2012
Betreff: Re: [scala-user] Re: case class with private constructor/apply and factory method?

Som Snytt

unread,
Nov 11, 2012, 2:34:24 PM11/11/12
to Sonnenschein, scala...@googlegroups.com, Markus Jais
Sorry for the brain failure.

virtualeyes

unread,
Aug 22, 2014, 6:20:15 PM8/22/14
to scala...@googlegroups.com, marku...@yahoo.de
Just what the doctor ordered, thanks.
Reply all
Reply to author
Forward
0 new messages