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 }

Community
  • 1
  • 1
Raymond Chenon
  • 11,482
  • 15
  • 77
  • 110

3 Answers3

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.

slouc
  • 9,508
  • 3
  • 16
  • 41
  • 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 – Raymond Chenon Feb 19 '17 at 22:46
  • 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 }` – slouc Feb 19 '17 at 22:56
  • 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 – Raymond Chenon Feb 19 '17 at 23:03
  • You don't need `val y = y`. Code in my previous comment works. Having `val` in front of `lastName` means it's publicly accessible. – slouc Feb 19 '17 at 23:05
  • I edited the comment above. Sorry I cannot format as code in the comment – Raymond Chenon Feb 19 '17 at 23:09
  • I don't know where is the misunderstanding. Just write `val x: Int, val y: Int` etc. in the class parameters list, so that it becomes `class Person(first: String, val x: Int, val y: Int) { val firstName = first.toLowerCase }`. What is the problem? Btw formatting help is here in the lower right, code is in backticks. – slouc Feb 19 '17 at 23:11
  • Hey @Raymond Chenon did you manage to solve it? Did you read my last comment? Because what you say in your edit is simply not true. You don't need to copy paste anything. If you declare class variables with `val`, they are automatically considered public fields. There is no need for c/p, unless you want to rename all `c`s to `a`s. – slouc Feb 28 '17 at 09:51
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")
    }
   }
  }
prayagupa
  • 30,204
  • 14
  • 155
  • 192
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.

18446744073709551615
  • 16,368
  • 4
  • 94
  • 127