0

There is a case class that looks like this:

case class User(
  id: Long,
  name: String,
  email: String)

I want to use Scala macro to generate a function like below:

def makeUser(
    id: Long = 1L,
    name: String = "some name",
    email: String = "some email"): User = {
  User(
    id = id,
    name = name,
    email = email)
}

It's verbose, and Scala macro can solve this verbosity (I hope). I don't care much about the default values; they can be random values.

I wonder if anyone can give me a code example. Thank you.

Edit: I'd like to clarify more about what I want. The function will only be used in unit tests, so I'd like to avoid littering my case classes with default values. (Thanks @Tyler for pointing it out).

Edit2: Also, I'd like to learn more about Scala's Macro. Therefore, if there's a macro example that achieves this, it would be a good lesson for me.

Tanin
  • 1,853
  • 1
  • 15
  • 20

3 Answers3

3

You don't need a macro, just add a new function to your companion object (or make another constructor):

case class User(id: Long, name: String, email: String)
object User {
  def defaultUser(): User = {
    User(1L, "name", "email")
  }
}

Then use it:

val user = User.defaultUser()

Edit:

As @jwvh pointed out you can put default parameters right into your case class:

case class User(id: Long = 1L, name: String = "tyler", email: String = "tyler@email.com")
val a = User()
val b = User(email = "new@email.com")

But, as I mentioned in my comment, since it looks like you are creating a dummy instance of this object (maybe for testing?), I prefer to have it separated like in my original answer.

Tyler
  • 17,669
  • 10
  • 51
  • 89
  • 1
    Defaults can be even simpler: `case class User(id: Long=9L, name: String="bob", email: String="bob@me.com")` Then usage is simply: `User()` – jwvh Nov 25 '17 at 05:24
  • Correct, I just figure whenever I see something with a default `id` that it is going to be inserted (and ignored) by a database and like to have an explicit function for that, especially since OP says they can be anything. A case class with default parameters might be a better answer for OP though depending on what he wants. – Tyler Nov 25 '17 at 05:52
  • I would like to avoid putting the defaults directly onto my case class because these convenient values will only be used in unit tests. (I've modified my question to include this requirement. Thanks for pointing it out.) – Tanin Nov 25 '17 at 06:38
1

I would use the scalacheck-shapeless library but since you were looking into practising some code here is an example of using a typeclass which would allow you to seperate the class definition and its default value.

trait Default[T] {
  def get: T
}

object Default {
  def apply[T](implicit ev: Default[T]): Default[T] = ev
  implicit val user: Default[User] = new Default[User] {
    def get: User = User(1,"name","email")
  }
}

object Example {
  val user = Default[User]().get
}

EDIT: For the question in the comment. I wouldn't use the following, I'm putting it down purely for demontstrative purposes. At this point I would use scalacheck-shapeless. However, this is getting close to what I assume is scalacheck's implementation. Scalacheck will throw some randomness around too I assume. The following has a lot of initial boilerplate but it will work for any case class that only contains Int and Strings (though you could extend that).

import shapeless._
trait Default[T] {
  def get: T
}

object Default {
  def apply[T](implicit ev: Default[T]): Default[T] = ev
  private def make[T](t: T): Default[T] = new Default[T] {
    def get: T = t
  }
  implicit val int: Default[Int] = make(1)
  implicit val string: Default[String] = make("some string")
  implicit hnil: Default[HNil] = make(HNil)
  implicit hlist[H, T <: HList](implicit hEv: Default[H], tEv: Default[T]): Default[H :: T] = new Default[H :: T] {
    def make: H :: T = hEv.get :: tEv.ger
  }
  implicit gen[CC, R](implicit gen: Generic.Aux[CC, R], rEv: Default[R]): Default[CC] = {
    gen.from(rEv.get)
  }
}
A Spoty Spot
  • 746
  • 6
  • 19
  • Thank you. I'll look into it. One quesition: specifying `User(1,"name","email")` is what I want to avoid. Is there an example that avoids it? – Tanin Nov 27 '17 at 04:55
0

Eventually, I've decided to use Scala/Java reflection for generating test data from case classes.

The main advantages are (1) compile faster (because it doesn't use Macros) and (2) the API is nicer than scalacheck-shapeless.

Here's how to do it: https://give.engineering/2018/08/24/instantiate-case-class-with-arbitrary-value.html

Tanin
  • 1,853
  • 1
  • 15
  • 20