7

I seem to run into a lot of problems learning to stay with immutable principals in Scala, one of which is the concept of Cloning (or rather deriving) from the object in question.

One of these problems is the concept of mixed in traits - example

trait helper //modifies some kind behavior.. assume we want to to continue down the line

class A (val x:int) {

 def add(y:int) = new A(x + y)
}

Example extends App {

 val a =new A(5) with helper
 val b = a.add(10)  // mixed trait disappears

}

Now this is just a really simple version of a more complex problem I had already built today around various classes, factory methods to hide class A, etc. If we're only dealing with one trait, I know I could simply test for it and send it forward as needed. But what the heck do I do if upward of 3 or more traits could exist? I would have to test for all combinations which is unrealistic.

How do you instantiate (clone) an existing object with various traits and/or modify some aspect of it while adhering to functional design principals?

Many thanks, - Tim

LaloInDublin
  • 5,379
  • 4
  • 22
  • 26
  • It turns out to be not at all straightforward! This is one major reason why I try to avoid the cake pattern--re-making the same cake is really tricky, especially if you build the cake on the fly. It's generally easier if the thing that modifies behavior is data (e.g. a `val` in the class, probably in the constructor). – Rex Kerr Jun 24 '13 at 00:59
  • I agree and sadly why I keep avoid the trait decorating pattern as well... it looks great in a book, but the academic books never address how to clone the object later on as I've outlined here. – LaloInDublin Jun 24 '13 at 03:13
  • You basically have to create a builder for the object, which adds another three or four lines of boilerplate (when I do it) every time you want to mix in the trait. Sometimes it's still worth it. Often it's not. – Rex Kerr Jun 24 '13 at 03:32

1 Answers1

3

Collections uses implicit builders which know how to produce your target type from what you're starting with. Those two types are not always the same thing.

There are many related posts about typesafe builders that control what can be produced, e.g., http://nullary.blogspot.com/2011/10/builder-pattern-revisited-in-scala.html

A related question is what if you have a collection of mixed types: Polymorphic updates in an immutable class hierarchy

On the value axis, tracking values instead of encoding types What is the Scala equivalent to a Java builder pattern?

Updated: something similar just came up during play time. Using REs in patterns is described on the ML and here.

package object interpat {
  implicit class MySContext (val sc : StringContext) {
    object mys {
      def apply (args : Any*) : String = sc.s (args : _*)
      def unapplySeq (s : String) : Option[Seq[String]] = {
        val regexp = sc.parts.mkString ("(.+)").r
        regexp.unapplySeq (s)
      }
    }
  }
  implicit class SBContext (val sc : StringContext) {
    def sb(args: Any*): SB = new SB(sc.s (args : _*))
  }
  implicit class toHasPattern(sb: SB) {
    def /(pp: String) = new SB(sb.s) with HasPattern {
      val p = pp
    }
  }
  implicit class toHasRepl(hasp: SB with HasPattern) {
    def /(rr: String) = new SB(hasp.s) with HasPattern with HasRepl with Updateable {
      val p = hasp.p
      val r = rr
    }
  }
  // disallow it
  implicit class noway(hasr: SB with HasPattern with HasRepl) {
    def /(rr: String) = ???
  }
  implicit class noway2(hasr: SB with HasPattern with HasRepl) {
    def /(rr: String) = ???
  }
}

With usages and alternative of controlling the implicits with type parameters.

package interpat {
  import scala.util.Try
  object I { def unapply(x: String): Option[Int] = Try(x.toInt).toOption }

  trait XX {
    type HasIt
    type Yes <: HasIt
    type No <: HasIt
  }
  object XX extends XX {
    implicit class XContext (val sc : StringContext) {
      def x(args: Any*) = new X[No, No] {
        val s = sc.s(args : _*)
      }
    }
    implicit class xPat(x: X[No, No]) {
      def /(pp: String) = new X[Yes, No] with HasPattern {
        val s = x.s
        val p = pp
      }
    }
    implicit class xRep(x: X[Yes, No] with HasPattern) {
      def /(rr: String) = new X[Yes, Yes] with HasPattern with HasRepl {
        val s = x.s
        val p = x.p
        val r = rr
        override def toString = s replaceAll (p, r )
      }
    }
    implicit class xable(xx: X[Yes, Yes]) {
      def x = xx.toString
    }
  }
  import XX._

  trait X[HasP <: HasIt, HasR <: HasIt] {
    def s: String
  }

  trait HasPattern {
    def p: String
  }

  trait HasRepl {
    def r: String
  }

  trait Updateable { this: HasPattern with HasRepl =>
    def update(p: String, r: String)
    override def toString = {
      update(p, r)
      super.toString
    }
  }

  class SB(val s: String) {
    final val sb = new StringBuilder(s)
    def update(p: String, r: String): Unit =
      sb.replace(0, sb.length, sb.toString.replaceAll(p, r))
    override def toString = sb.toString
  }

  object Test extends App {
    val msg = "The sky is blue" match {
      case mys"The $thing is $colour" => mys"A $colour thing is $thing"
      case _ => "no match"
    }
    println (msg)
    val mys"The $thing is $colour" = "The sky is blue"
    Console println s"$thing / $colour"
    val mys"The ${I(number)} is blue" = "The 3 is blue"
    Console println s"$number"
    //sb"The sky is blue".update("blue","red")
    // no way to get a value out!
    sb"The sky is blue"("blue") = "red"
    val ugh = sb"The sky is blue"
    ugh("blue") = "red"
    Console println ugh
    val sx = sb"The sky is $colour" / "blue" / "red"
    Console println sx
    //sb"The sky is $colour" / "blue" / "red" / "yellow"
    Console println sx
    Console println x"The sky is $colour" / "blue" / "red"
    Console println (x"The sky is $colour" / "blue" / "red").x
    //Console println x"The sky is $colour" / "blue" / "red" / "yellow"
  }
}
Community
  • 1
  • 1
som-snytt
  • 39,429
  • 2
  • 47
  • 129