Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala - simple design by contract

Tags:

scala

I'm learning Scala as a personal project as I'm fed up with the verbosity of Java. I like a lot of what I see, but wonder if there's a way to efficiently implement some simple contracts on methods. I'm not (necessarily) after full DbC, but is there a way to: -

  1. indicate that a parameter or a class field is REQUIRED, i.e. CANNOT be null. The Option thing seems to indicate cleanly if an OPTIONAL value is present, but I want to specify class invariants (x is required) and also to succinctly specify that a parameter is required. I know I can do "if's" throwing some kind of exception, but I want a language feature for this VERY common use-case. I like my interfaces tight, I dislike defensive programming.

  2. Is it possible to define succinct and efficient (runtime performance) ranged types, such as "NonNegativeInt" - I want to say that a parameter is >= 0. Or within a range. PASCAL had these types and I found them excellent for communicating intent. That is one of the big drawbacks of C, C++, Java, etc. When I say succinct I mean I want to declare a variable of this type as easily as a normal int, not having to new each and every instance on the heap.

like image 997
David Kerr Avatar asked Mar 04 '13 15:03

David Kerr


2 Answers

For point (1), Option should indeed be enough. This is because while scala supports null values, it does so mainly for compatibility with Java. Scala code should not contain null, values, and where it does it should be constrained to very localized places, and converted to an option as soon as possible (good scala code will never let null values propagate). So in idiomatic scala, if a field or parameter is not of type Option this really means that it is required.

Now, there is also the (experimental and never fully supported as far as I can tell) NotNull trait. See How does the NotNull trait work in 2.8 and does anyone actually use it?

For point (2) scala 2.10 introduces value classes. With them, you could define your very own class that wraps Int without runtime overhead, and implement its operators as you see fit. The only places where you would have a runtime check would be when converting from a normal Int to your NonNegativeInt (throw an exception if the int is negative). Note that this check would be performed everytime you create a new NonNegativeInt, which also means everytime you perform an operation, so there would be a non-null runtime impact. But Pascal was in the very same situation (range checks are performed at runtime in Pascal) so I guess that you're OK with this.

UPDATE: Here is an example implementation of NonNegativeInt (here renamed to UInt):

object UInt {
  def apply( i: Int ): UInt = {
    require( i >= 0 )
    new UInt( i )
  }
}
class UInt private ( val i: Int ) extends AnyVal {
  override def toString = i.toString
  def +( other: UInt ) = UInt( i + other.i)
  def -( other: UInt ) = UInt( i - other.i)
  def *( other: UInt ) = UInt( i * other.i)
  def /( other: UInt ) = UInt( i / other.i)
  def <( other: UInt ) = i < other.i
  // ... and so on
}

and some example usage in the REPL:

scala> UInt(123)
res40: UInt = 123

scala> UInt(123) * UInt(2)
res41: UInt = 246

scala> UInt(5) - UInt(8)
java.lang.IllegalArgumentException: requirement failed
        at scala.Predef$.require(Predef.scala:221)
        at UInt$.apply(<console>:15)
        ...
like image 170
Régis Jean-Gilles Avatar answered Oct 08 '22 20:10

Régis Jean-Gilles


What is this null of which you speak?

Seriously, bar null at the borders of your system, where it comes into contact with code you did not write. At that boundary you make sure all nullable values are converted to Option.

Likewise, don't use exceptions. As with null, bar them at the gate. Turn them into Either or use ScalaZ Validation.

As for dependent types (where the type interacts with or depends on specific values or subsets of values such as the natural numbers) it's more work. However, Spire has a Natural type. It might not be exactly what you want since it's arbitrary precision but it does impose the non-negative aspect of the natural numbers.

Addendum

Conversion from a nullable value to Option is trivially accommodated by the Scala Standard Library itself in the form of the Option factroy. To wit:

scala>     val s1 = "Stringy goodness"
s1: String = Stringy goodness

scala>     val s2: String = null
s2: String = null

scala>     val os1 = Option(s1)
os1: Option[String] = Some(Stringy goodness)

scala>     val os2 = Option(s2)
os2: Option[String] = None
like image 35
Randall Schulz Avatar answered Oct 08 '22 19:10

Randall Schulz