Let me propose a concrete example. An evolutionary computation framework uses a random number generator (RNG) to provide the entropy needed to get coverage of a solution space. If you simply create a scala.util.Random and take numbers from it, you will, sooner or later, run into the problem that your unit tests will fail. Not because the logic is incorrect but because, even if the order of running test cases is fixed (it usually is not so guaranteed), you will introduce new logic that pulls one more, or one fewer, numbers from the RNG and your expected results will be incorrect. Here's the kind of thing I have in mind (greatly simplified):
val random = new scala.util.Random(0L)
val someInt: Int = ??? val x1 = random.nextInt(100) val x2 = random.nextInt(100) // ... val y = random.nextInt(100) assert(y==someInt)
class RNG[+A](f: Long=>A)(seed: Long) { private val random = new scala.util.Random(seed) private lazy val state = random.nextLong def next = new RNG(f)(state) def value = f(state) } val r = new RNG[Int](x => (x.toInt + 100) % 100)(0L) val someInt: Int = ??? val r1 = r.next val r2 = r.next // ... val rN = r2.next val y = rN.value assert(y==someInt)
There are many variations on this general theme, of course. But the important point is that we carry around our current RNG from one step to the next, rather than simply taking its value.
Here's another example which helps us strip arguments (from a variable number of args) with the appropriate types. The get method returns a Try of a tuple of a T and the remaining Args:
case class Args(args: List[Any]) extends (() => Try[(Any,Args)]) { def apply(): Try[(Any, Args)] = args match { case Nil => Failure(new Exception("Args is empty: this typically means you didn't provide sufficient arguments")) case h :: t => Success(h, Args(t)) } def get[T](clazz: Class[T]): Try[(T, Args)] = { apply match { case Success((r,a)) => if (clazz.isInstance(r) || clazz.isAssignableFrom(r.getClass)) Success(r.asInstanceOf[T], a) else throw new Exception(s"args head is not of type: $clazz but is of type ${r.getClass}") case f @ Failure(t) => f.asInstanceOf[Try[(T, Args)]] } } def isEmpty: Boolean = args.isEmpty }
And here is how you would extract the arguments (in a FlatSpec with Matchers):
def mainMethod(args: List[Any]): Unit = {
val a1 = Args(args) val (x: String, a2) = a1.get(classOf[String]).get val (y, a3) = a2.get(classOf[java.lang.Boolean]).get a3.isEmpty shouldBe true x shouldBe "hello" y shouldBe true } mainMethod(List("hello", true))
Note how we should end up with an empty list after stripping off all the arguments. And note also that if we want to add a type annotation (for example for x, as shown above) then we can do that and be quite sure that the get method is working properly. In this example, we didn't try to avoid the exceptions that may be thrown by the get methods on Try objects. We could of course do a nicer job with nested match statements.
On a completely different subject, I asked and answered my own question on StackOverflow today, regarding an issue with serializing/deserializing case class instances via Json.
This comment has been removed by the author.
ReplyDeletethank you for giving such valuable information.one of the recommanded site.we appriciate more details
ReplyDeleteHadoop,spark, and scala training
Scala training in hyderabafd
Hadoop training in hyderabad