Scala's answer to the "null" that everybody loves to hate is to use is the Option[T] types. The Option thing is described here in section "Option, Some, and None: Avoiding nulls".
Despite all the waffle, Option types are simple. A variable called v of class Option[Int] can have a value of either None or Some(123) where 123 is an example of the underlying value you actually care about. The value None is a real value, rather than an invalid reference as null is in Java or C#. If I want to test is whether v is None I can say v==None or v.isDefined() or !v.isEmpty(). If I want its value 123, I say v.getOrElse(0) where 0 is a default if its None. Easy. Or I can use a patten match. Easy too.
A scenario I hit was doing various bits of arithmetic with stock prices. Now a stock price may not exists on a given date. But I may still want to write the code to try to do some calculation with the price. In Java I probably use a lot of null checks or set the price to a special value early on or something. C# gives you Nullable types which lets you say
double? price1 = null; double? ratio1 = 0.7; double? result = ratio1 * price1; // or some other complex expression Console.Write("{0,7:N2}", result);In C#, if either price1 or ratio1 is null then result is null. The operators == and != do the right thing. Comparison using < and > requires a little care.
So how do I do this in Scala? Well, if the Option type is what we are supposed to use then lets do it.
val price1:Option[Double] = None val ratio1:Option[Double] = Some(0.7) val result:Option[Double] = price1 * ratio1 printf("%7.2f", result getOrElse -99999.99)but line 3 does not compile because * is not a member of Option[Double].
So a bit of googling and I find this clever-but-obscure trick:
val result = for (ratio1_alias <- ratio1; price1_alias <- price1) yield ratio1_alias * price1_aliasIt seems that you can iterate over an Option[..]: Its a sequence of either zero (for the None case) or one element (for the Some(123.0) case). The expression returns None if anything is None or Some(ratio1_alias * price1_alias) if not. Tricksy stuff. It works but boy does it suck from a readability point of view. Oh and all the vals need to be aliased. Can I have my C# nullable types back please? So after a question on the Scala-User mailing list "Rex Kerr-2" suggested using the pimp-my-library techique to add functionality to the Option[Double] class, by "upgrading" it to a RichOptionDouble to which I can add arithmetic operators. So I ended up with the non-generic (but simple and, better still, working) code:
package org.scala_tools.option.math class RichOptionDouble(od:Option[Double]) extends Ordered[Option[Double]] { def +(o:Option[Double]) = if (o==None) o else Some(numeric.plus(od.get,o.get)) def unary_-():Option[Double] = Some(-od.get) def -(o:Option[Double]) = if (o==None) o else Some(od.get-o.get) def *(o:Option[Double]) = if (o==None) o else Some(od.get*o.get) def /(o:Option[Double]) = if (o==None) o else Some(od.get/o.get) def compare(y: Option[Double]): Int = { if (od.isEmpty) return if (y.isEmpty) 0 else 1 if (y.isEmpty) return -1 // This is what Scala RichDouble does return java.lang.Double.compare(od.getOrElse(0d), y.getOrElse(0d)) } } object RichOptionDoubleNone extends RichOptionDouble(None) { override def +(o:Option[Double]) = None override def unary_-() = None override def -(o:Option[Double]) = None override def *(o:Option[Double]) = None override def /(o:Option[Double]) = None } trait Implicits { implicit def optiondouble2richoptiondouble(od:Option[Double]) = { if (od==None) RichOptionDoubleNone else new RichOptionDouble(od) } implicit def double2optiondouble(d:Double) = Some(d) implicit def int2optiondouble(i:Int) = Some(i.toDouble) implicit def double2richoptiondouble(d:Double) = new RichOptionDouble(Some(d)) implicit def int2richoptiondouble(i:Int) = new RichOptionDouble(Some(i.toDouble)) } // Modelled on technique in Scala-Time object Imports extends ImplicitsSo now I can say:
import org.scala_tools.option.math.Imports._ object Bar { def main(args : Array[String]) : Unit = { val price1 = None val price2 = Some(123.45) val ratio1 = Some(0.7) val result1 = price1 * ratio1 val result2 = price2 * ratio1 / 2 + 7.0 printf("%7.2f", result1 getOrElse -99999.99) printf("%7.2f", result2 getOrElse -99999.99) printf(" %s\n", result1 > result2) } }Which prints
-99999.99 86.41 true
Not ground breaking stuff. But still nice to get there in the end. It feels something like this (probably in generic form) should be in the core libraries,
No comments:
Post a Comment