Tanin
Tanin

Reputation: 1923

Generate a function to instantiate a case class with default values using Scala's macro

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.

Upvotes: 0

Views: 2131

Answers (3)

Tanin
Tanin

Reputation: 1923

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

Upvotes: 0

A Spoty Spot
A Spoty Spot

Reputation: 746

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)
  }
}

Upvotes: 1

Tyler
Tyler

Reputation: 18157

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 = "[email protected]")
val a = User()
val b = User(email = "[email protected]")

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.

Upvotes: 3

Related Questions