简体   繁体   中英

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 toString implementation:

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?

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 .

EDIT: What about overriding toString?

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

You can use an implicit conversion to have Stepper treated like a function only when necessary:

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

implicit def s2f(s: Stepper) = new Function[Int, Int] {
  def apply(x: Int) = s.apply(x)
}

Now you get the case class's toString when you call Stepper(42).toString , but Some(2) map Stepper(2) also works as desired.

(Note that I've been more verbose than necessary above to keep the mechanics clear. You can also write implicit def s2f(s: Stepper) = s.apply _ or any number of other more concise formulations).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM