Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Case class and Linearization of traits

Suppose that I want to write a case class Stepper as follows:

case class Stepper(step: Int) {def apply(x: Int) = x + step}

It comes with a nice toStringimplementation:

scala> Stepper(42).toString
res0: String = Stepper(42)

but it's not really a function:

scala> Some(2) map Stepper(2)
<console>:10: error: type mismatch;
 found   : Stepper
 required: Int => ?
              Some(2) map Stepper(2)

A workaround is to implement the Function trait...

case class Stepper(step: Int) extends (Int => Int) {def apply(x: Int) = x + step}

But then, I can't have for free a nice toString implementation anymore:

scala> Stepper(42).toString
res2: java.lang.String = <function1>

Then, the question is: can I have the best of these two worlds? Is there a solution where I have the nice toString implementation for free AND an implementation of trait Function. In other words, is there a way to apply the linearization in such a way that case class syntaxic sugar is applied at last?

like image 669
Nicolas Avatar asked Mar 14 '12 15:03

Nicolas


People also ask

What is class object and trait in Scala?

Unlike class inheritance, in which each class must inherit from just one superclass, a class can mix in any number of traits. Traits are used to define object types by specifying the signature of the supported methods. Scala also allows traits to be partially implemented but traits may not have constructor parameters.

Can a case extend a trait class?

The answer is simple: Case Class can extend another Class, trait or Abstract Class.

What is the difference between class and case class in Scala?

A class can extend another class, whereas a case class can not extend another case class (because it would not be possible to correctly implement their equality).

What is linearization in Scala?

Scala Linearization is a deterministic process which comes into play when an object of a class is created which is defined using inheritance of different traits and classes.


2 Answers

The question is not really to do with linearisation. In case-classes toString is a method automatically generated by the compiler if and only if Any.toString is not overridden in the end-type.

However, the answer is partly to do with linearisation - we need to override Function1.toString with the method that would have been generated by compiler if not for the version introduced by Function1 :

trait ProperName extends Product {
  override lazy val toString = scala.runtime.ScalaRunTime._toString(this)
}

// now just mix in ProperName and... magic!
case class Stepper(step: Int) extends (Int => Int) with ProperName {
  def apply(x:Int) = x+step
}

Then

println(Some(2) map Stepper(2))
println(Stepper(2))

Will produce

Some(4)
Stepper(2)

Update

Here is a version of ProperName trait that doesn't rely on the undocumented API method:

trait ProperName extends Product {
  override lazy val toString  = {
    val caseFields = {
       val arity = productArity
       def fields(from: Int): List[Any] =
         if (from == arity) List()
         else productElement(from) :: fields(from + 1)
       fields(0) 
    }
    caseFields.mkString(productPrefix + "(", ",", ")")
  }
}

Alternative toString implementation is derived from the source code for the original _toString method scala.runtime.ScalaRunTime._toString.

Please note that this alternative implementation is still based on the assumption that a case class always extends Product trait. Although the latter holds true as of Scala 2.9.0 and is a fact that is known to and relied upon by some members of Scala community it's not formally documented as part of Scala Language Spec.

like image 164
Vlad Gudim Avatar answered Sep 19 '22 18:09

Vlad Gudim


EDIT: What about overriding toString?

case class Stepper(step: Int) extends (Int => Int) {
  def apply(x: Int) = x + step
  override def toString = "Stepper(" + step + ")"
}
like image 37
Tal Pressman Avatar answered Sep 22 '22 18:09

Tal Pressman