1

If I want to modify one single parameter in a constructor.

In the Scala case class, the apply method will be overridden twice. Unless apply applies ( no pun ) to auxiliary constructor. Related to

How one can modify one single input from a constructor ?

Criteria :

  • The class must hold immutable data. All data must be accessible.

  • Note it doesn't have to be case class.

  • Additionally , no need to use the apply method.

  • no extra unused parameters. In the example below, _fistName is still accessible but unused.

case class Person( lastName: String, _fistName: String, ... ) { lazy val fistName = _fistName.toLowerCase }

0

3 Answers 3

3

Here are two simple approaches.

Using class:

class Person(first: String, last: String) {
  val firstName = first.toLowerCase
  val lastName = last.toLowerCase()
}

val person = new Person("Adam", "Smith")
println(person.firstName + " " + person.lastName) // adam smith

Using trait + object's apply():

trait Person {
  val firstName: String
  val lastName: String
}

object Person {
  def apply(first: String, last: String) = new Person {
    override val firstName: String = first.toLowerCase
    override val lastName: String = last.toLowerCase
  }
}

val person = Person("Adam", "Smith")
println(person.firstName + " " + person.lastName) // adam smith

Note that classes must be instantiated with new, while traits (created with apply()) don't.

Why no case classes? Well, they are designed to serve as ADTs (abstract data types). Basically, they are thought of as containers for some data without any logic. That's why they get apply() out-of-the-box. If you want to override it, then means your class doesn't have the semantics of a case class.

I can see that @prayag took the effort of trying to help you force it into a case class, but seriously, if it doesn't have to be one (and you said so in the question), then don't make it one.

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

7 Comments

I like the way to use class. It's classy. If you have a lot of parameters to pass to the constructor. Is there a way to change only one variable instead of copying the rest ? I like the case class conciseness
Yes, just add val in front of them in constructor. Case classes get that automatically. E.g. class Person(first: String, val lastName: String) { val firstName = first.toLowerCase }
ok, I get that. But I want to avoid to repeat the obvious : class Person(first: String, last: String, x: Int, y: Int, ... ) val lastName = last val lastName = last, val x = x , val y = y ... I guess there is no shortcut
You don't need val y = y. Code in my previous comment works. Having val in front of lastName means it's publicly accessible.
I edited the comment above. Sorry I cannot format as code in the comment
|
1

The reference you had posted seems to have lot of answers as well.

Two simple ways I could think of

  1. make it abstract case class and define companion object which would mutate the value you want

  2. define the member of case class as var and mutate it.

eg. (using scalatest)

 class CaseClassSpecs extends FunSpec {

   describe("case class modify") {

     it("APPROACH 1 : modifies abstract case class member") {

      object Item {
        def apply(itemId: String, itemName: String) :Item = new Item(itemId.toLowerCase, itemName) {}
      }

      abstract case class Item private (val itemId: String, itemName: String)

      val item1 = Item("SKU-ONE", "Shirts")

      assert(item1.itemId == "sku-one")
      assert(item1.itemName == "Shirts")
    }

     it("APPROACH 2 : modifies case class member which is var") {

      case class Item (var itemId: String, itemName: String) {
        itemId = itemId.toLowerCase()
      }

      val item1 = Item("SKU-ONE", "Shirts")

      assert(item1.itemId == "sku-one")
      assert(item1.itemName == "Shirts")
    }
   }
  }

2 Comments

I don't know who downvoted your answer and my question. But I upvoted your answer. None of the links I provided gave me the answer.
'val' modifier is redundant for parameter of case class primary constructor.
0

Class parameters are not necessarily class members. You can have class parameters that do not become class members.

Method 1

class Foo(bar0: String) {
  val bar = bar0.toLowerCase()
}

@main
def main(): Unit = {
  println(Foo("AsDfGh").bar)
}

prints:

asdfgh

and the decompiled code is:

public class Foo {
    private final String bar;

    public Foo(final String bar0) {
    this.bar = bar0.toLowerCase();
    }

    public String bar() {
    return this.bar;
    }
}

You see, bar0 is a "temporary" value, it does not become a field because it is not referenced.

So if you want to change a value, just do not use the original value in the methods.

Method 2

For case classes, it does not seem to work in 2022, but here is another trick:

case class Point (x: Double, y: Double)
class PolarPoint(r: Double, alpha: Double) extends Point(r*Math.cos(alpha), r*Math.sin(alpha))

Here r and alpha do not become members of PolarPoint.

If you don't need two types, you can make the 1st constructor protected:

case class Foo protected(x:Int)
class FooImpl(x0:Int) extends Foo(myFunc(x0))

You will reference objects as Foos but create FooImpls.

Method 3

Your class can have multiple parameter lists and implicits:

class Qux(x:String)(implicit val y:String = x.toLowerCase())

is converted to:

public class Qux {
    private final String y;

    public static String $lessinit$greater$default$2(String var0) {
    return Qux$.MODULE$.$lessinit$greater$default$2(var0);
    }

    public Qux(final String x, final String y) {
    this.y = y;
    }

    public String y() {
    return this.y;
    }
}

You see that here only y becomes a field.

Comments

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.