Raymond Chenon
Raymond Chenon

Reputation: 12662

Modify one value in a Scala class constructor in a concise way

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 :

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

Upvotes: 1

Views: 3199

Answers (3)

18446744073709551615
18446744073709551615

Reputation: 16832

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.

Upvotes: 0

slouc
slouc

Reputation: 9698

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.

Upvotes: 3

prayagupadhyay
prayagupadhyay

Reputation: 31192

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

Upvotes: 1

Related Questions