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: -
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.
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.
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)
...
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With