11

Is there a way in Scala to modify a parameter passed to a single-argument case class constructor / apply() method before it becomes a val? E.g.

case class AbsVal private(aVal: Double)

object AbsVal {
    def apply(aVal: Double): AbsVal = AbsVal(Math.abs(aVal)) // doesn't compile
}

This fails of course with ambiguous reference to overloaded definition. I thought maybe I could trick it with named parameters (and different parameter names for the constructor vs apply()), but that doesn't work either.

Of course instead of apply() I could just have the private constructor and a factory method, but it's annoying to have to litter the code with AbsVal.make(x) instead of just AbsVal(x).

5
  • 1
    I actually prefer AbsVal.make(x) here. Commented Jun 16, 2014 at 0:17
  • 2
    Shouldn't that be new AbsVal(Math.abs(val))? Otherwise it's AbsVal.apply calling itself in an infinite loop. Commented Jun 16, 2014 at 0:30
  • @wingedsubmariner Even with new, it still doesn't compile (same errors). Commented Jun 16, 2014 at 0:49
  • Thanks for calling out the typo, @som-snytt. Fixed. No, that's not what I mean and if you'd read the question and the comments carefully you'd know that, because ambiguous reference to overloaded definition is not the error you get when you try to use a reserved word as a parameter name. (And no, the actual code I was compiling was not trying to use a reserved word as a parameter name.) Commented Jun 16, 2014 at 15:01
  • 1
    Sorry, I wasn't actually trying to be mean. I cut/pasted and went whoops. Commented Jun 16, 2014 at 17:49

3 Answers 3

7
abstract case class AbsVal private(value: Double)

object AbsVal {
  def apply(value: Double): AbsVal = new AbsVal(Math.abs(value)) {}
}

Abstract case classes don't have an apply method automatically generated in their companion object. This lets us create our own. We have to create another class to inherit from the case class (anonymous, here), but the toString method generated for the case class will still display it as an AbsVal, so this should be invisible as well. A copy method is not automatically generated for abstract case classes, but for a class with a single parameter like this it wouldn't be useful anyway (and it can always be defined manually, see LimbSoup's answer).

Case class inheritance is usually a bad idea, but because of the private constructor the only subclass that will exist will be the anonymous one we define, so in this instance it is safe.

Sign up to request clarification or add additional context in comments.

4 Comments

That's kind of clever, even if Nikita's argument from semantics is persuasive.
Now I lean toward the semantics. This makes "constructor pattern" more deceptive: C(-42) match { case C(42) => } Extractor semantics are arbitrary, but this uses an accessor to examine the private field. Note that you can't specify unapply.
@som-snytt If that is an issue, then implement an appropriate unapply method as well.
That gives double definition, as unapply is not suppressed. Maybe that's just a bug or oversight, of course.
4

it's annoying to have to litter the code with AbsVal.make(x) instead of just AbsVal(x)

This is an extremely subjective point. Throughout different languages it is a common wisdom to prefer descriptive names to overloaded definitions, since they tend to introduce ambiguity. Yours is a perfect example of such a case.

Hence, AbsVal.make(x) is the correct way to go in your situation. Although I'd rather name it something like AbsVal.abs(x).

Comments

2

There doesn't seem to be a way to override apply on a case class. There are some syntactic errors in your code, but even changing it to override def apply(value: Double): AbsVal = new AbsVal(Math.abs(value)) will fail. I think this behavior is intentional, because when you define AbsVal(-1), you expect the value not to change (for any case class).

Nonetheless, the same feel can be achieved through a class with a private constructor. Obviously a bit more work to get the same functionality as a case class, but your AbsVal.make is gone..

class AbsVal private(val value: Double) {

    def copy(value: Double = this.value): AbsVal = AbsVal(value)

    def equals(that: AbsVal): Boolean = this.value.equals(that.value)

    override def toString: String = "AbsVal(" + this.value.toString + ")"

}

object AbsVal {

    def apply(value: Double):AbsVal = new AbsVal(Math.abs(value))

    def unapply(a: AbsVal): Option[Double] = Some(a.value)

}

1 Comment

You can still do some basic pattern matching at the very least, defining the unapply function. Try it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.