Case classes with default parameters that depend on other parameters

Hi there,

I am completely new to scala. I am fascinated by the concept of case classes. To my understanding, it allows me to write POJOs in a single line. I could basically rewrite the following Java code:

class Person
{
    private String firstname;
    private String lastname;
    private String email;

    public Person(String firstname, String lastname, String email)
    {
        this.firstname = firstname;
        this.lastname = lastname;
        this.email = email;
    }

    // getters and setters
}

into something beautiful:

case class Person(val firstname: String, val lastname: String, val email: String);

However, let’s assume another constructor that automatically sets the value of email:

public Person(String firstname, String lastname)
{
    this(firstname, lastname, String.format("%s@%s.com", firstname, lastname);
}

What is the scala way of extending the case class by the given constructor. I know that I could just stick to Java code in scala but I don’t want to violate the beauty of scala :wink:
Any ideas how to solve that?

thanks, Simon

Something like this:

case class Person(val first: String, val last: String, val email: String)

object Person {
  def apply(first: String, last: String): Person = this(first, last, s"${first}@${last}.com")
}

object Test {
  def main(args: Array[String]): Unit = {
    val p = Person("Fist", "Last")
    println(p)
  }
}

Looks interesting. But: are you creating two classes with the same name here? First, a “case class” and second, a (singleton) “object” class? I like the way you use apply here, but is there a way to somehow put both constructors in a “single class”?

Object there is not another class, but a companion object. As explained here, “An analog to a companion object in Java is having a class with static methods. In Scala you would move the static methods to a Companion object.”

Also, here is a very nice explaination of what is going on really, when you create a case class.

Thank you very much. This is amazing! Compared to Java, this saves a lot of lines. But let’s just assume a Person marries changing it’s last name. I Java, I would do this:

class Person
{
    private String firstname;
    private String lastname;
    private String email;

    public Person(String firstname, String lastname, String email)
    {
        this.firstname = firstname;
        this.lastname = lastname;
        this.email = email;
    }

    public Person(String firstname, String lastname)
    {
        this(firstname, lastname, this.generateMail(firstname, lastname));   
    }

    public void setLastname(String lastname)
    {
        this.lastname = lastname;
        this.mail = this.generateMail(this.firstname, lastname);
    }

    private String generateMail(String firstname, String lastname)
    {
        return String.format("%s@%s.com", firstname, lastname);
    }
}

How can I do the same thing in Scala now?

Yes, you can ©

case class Person(first: String, var last: String, email: String)

Note var before name: now you can modifiy it (by default arguments are immutable val-s)

scala> val p = Person("Name", "Last Name", "[email protected]")
p: misc.Person = Person(Name,Last Name,[email protected])

scala> p.first = "Other Name"
<console>:13: error: reassignment to val
       p.first = "Other Name"
               ^

scala> p.last = "Other Name"
p.last: String = Other Name

scala> p
res3: misc.Person = Person(Name,Other Name,[email protected])

But, IMHO, it’s better to keep everything immutable and just create new object with changed values, i.e.:

val marriedPerson = Person(p.first, "New Last Name", p.email)

After all marriage makes a new person anyway. :slight_smile:

Small note: you can also use the copy method which makes the more compact and clear.

Thank you mtavkhelidze but as far as I can see your solution does not automatically change the mail address when the last name changes (I know, this is far from a real world example but I am more interested in the technical solution :stuck_out_tongue_winking_eye:)

:joy: maybe you are right. But what does that mean for the old person when a garbage collector is hanging around in jvm :stuck_out_tongue: I guess marriage might change someone into somebody else rather than completely replacing a person by another one :thinking:

But it copies the object which might not be the best solution in every scenario …

Hello,

What to do if a person marries? Depends on your use case.

Often, all you need is dealing with snapshots of people. For example, some one gives you a file of people to process. Presumably, each entry is a snapshot at a certain time. You can use case classes with immutable fields.

Even if you periodically receive an updated file to process, you still only deal with snapshots.

It becomes a different matter if your app actually receives messages that people have changed. In that case, you probably assign a key to each person to keep track, and then an immutable case class with the key represents a person, and the data regarding that person would, for example, be stored in a database.

If you receive people data from multiple sources, you probably also want to assign a key to each person, because there may be different versions of a name. E.g. “Donald Trump”, “Barack Obama”, “George W Bush” or “Bill Clinton” are not the actual full names.

Best, Oliver

That’s true – but in practice, it’s correct in many (I’d say most) situations where you are doing idiomatic Scala.

The biggest mental jump from Java to Scala isn’t syntactic or anything like that, it’s switching to a mostly-immutable mindset. Scala doesn’t insist on that, but I’d say that most serious Scala programmers favor immutability by default, unless they have reasons to do otherwise. This makes a lot of things easier (especially anything having to do with threading), and the language gently prods you in that direction with elements like the built-in copy() method on case classes.

As for your email case, as an example of thinking in this mode, I would personally define that as something like:

case class Person(first: String, last: String, emailIn: Option[String] = None) {
  lazy val email = emailIn match {
    case Some(e) => e
    case None => s"$first@$last.com"
  }
}

A bunch of things to note here:

  • Instead of making email a constructor parameter, we specify an optional emailIn parameter. The actual email is a lazy val (that is, a constant field that gets computed the first time you use it), based on the parameters.
  • match is a built-in feature of Scala, that lets you test and decompose a value at one shot. It’s much more powerful than traditional switch statements, and replaces many ifs with something more concise. In this case, the Some(e) means “if there is a value in emailIn, put it in e”, and the None means “if there is no value in emailIn”.
  • Instead of using String.format, I’m using Scala’s built-in string interpolation. If you start a String with s, you can stick variable names into it using $ and they will be interpolated. It’s much cleaner and easier to read.
  • The copy() method only copies the constructor parameters; it won’t copy the lazy val inside. So it you say:
val mary = Person("Mary", "Smith")
mary.email  // will get "[email protected]"

val mary2 = mary.copy(last = "Jones")
mary2.email  // will get "[email protected]"

The email field of mary2 gets calculated the first time you use it, so it will pick up the constructor parameters of mary2; it doesn’t care about the ones in mary.

It’s a rather different way of thinking about your code, but once you get used to it, it’s quite delightful…

1 Like

And looking at what I just wrote, I should note – the lazy is actually unnecessary here. You could just as easily make it a plain val, and that’s probably more sensible in most cases where the field is cheap to compute and you’re reasonably likely to use it.

Either you

  1. view a Person instance as a representation of a real person who can change names and still be the same person (have the same identity), or
  2. you view a Person instance as a snapshot of a person at a certain point in time.

I would say (1) is the OOP way and (2) the FP way. For (1) you do not want to use a case class, but a plain class with vars without an overridden equals. For (2) you do want an immutable case class.

Yeah. After reading your response I assume thats a truth one has to experience :stuck_out_tongue_winking_eye:

Thanks for your answer, btw. Your approach seems elegant to me, esp. lazy property assignment looks awesome. Still need to get my head around the Some(e) part, but the rest seems nice and understandable.

Well, I guess I am not used to the FP way, but as jducoeur mentioned: considering threading, immutable objects definitely simplify the whole concept enormously :wink:

Thank you all for your answers. I get the feeling that I will regret learning scala cause I would hate switching back to other languages after being used to it :stuck_out_tongue:

Happy to help!

In practice, Option is probably the most important part to learn when coming from Java, IMO.

The key thing to keep in mind is that, while null is technically legal in Scala, it is strongly discouraged. Not everyone is as heavily into immutability (some performance-focused libraries wind up necessarily cheating on this), but I don’t know anybody who thinks that null should ever be used except when you have no choice – generally, when you’re interoperating with Java or JavaScript.

Instead, Scala uses Option[T] – a formal way of saying “there might or might not be a value of type T here”. If there is, it’s a Some; if not, it’s None.

It requires a little more thought and verbiage, but the benefit is huge: you can mostly stop worrying about null pointer exceptions. Whereas in Java every value could be null, so you have to always be worrying about that, in well-behaved Scala code you can usually assume that that is not the case. Instead, if this might be empty, you declare it as Option, and the compiler enforces that you think about that where possible. (For example, in my code above, if I had left off the case None => line, the compiler would complain about the fact that I’m not dealing with that possible case.) It makes your code much more reliable.

So any time you would be tempted to allow a null in Java, that generally means that the type should be an Option in Scala.

As for the way that case Some(e) => pulls the Some apart, that’s a deeper topic. For now, just trust that, when you match on an Option, it allows you to pull out the value like this.

(match is actually extremely powerful – it is named that way because it allows very deep pattern-matching, and case classes are named that way because they automatically gain these pattern-matching abilities. It’s one of my favorite features of the language.)

Heh – not a rare reaction…

Wow. Thanks for the comprehensive explanation. I assume I can use multiple optional parameters in one function? If so, do i need to pass parameters in a key-value pair manner?

Yep, as many optional parameters as you like - they just have to be at the end of the parameter list.

You don’t need to use the key-value style if you are providing the parameters in order; you can use it if you want to give them out of order, or if there are gaps. I use that style mostly when there are a large number of optional parameters - I’ll just specify the ones that want a non-default value, in the key-value style.

You can use named argument syntax for any argument.

You have to use named argument syntax for any argument after any argument that you pass as named, and after any optional argument that you skip (for its default value), until the end of the argument list.

Optional parameters do not need to be at the end, but if they aren’t, and you skip one for its default value, then you will be required to use named arguments syntax for any following arguments.