7

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.

David Kerr
  • 1,376
  • 2
  • 15
  • 20
  • Actually C and C++ has non negative int: lookup "unsigned int" in google. – om-nom-nom Mar 04 '13 at 15:53
  • 2
    FYI: http://www.pm.inf.ethz.ch/education/theses/student_docs/Rokas_Matulis/Rokas_Matulis_MA_report The developed compiler plugin works for 2.10.0, but we haven't released it in any way (yet) because it - being a MSc's thesis - isn't really stable. – Malte Schwerhoff Mar 04 '13 at 16:16
  • @om-nom-nom. Yes I agree. It's been too long since I did C/C++ but it's very easy to circumvent if I recall. Anyway, my "real" intent (sorry) was to ask about useful numeric types not just NonNegativeInt. An example would be `DegreesCentigrade`, or anything else **domain-specific** - increased type-safety that I liked in Pascal. – David Kerr Mar 04 '13 at 17:52
  • @mhs: Nice. When this becomes part of Scala, or moves out of the lab and into mainstream use (maintained, etc) then I'd be very interested in using it. The more I see of Scala the more impressed I am: being able to mixin DbC is way cool! Keep it up! – David Kerr Mar 04 '13 at 17:55

3 Answers3

8

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)
        ...
Community
  • 1
  • 1
Régis Jean-Gilles
  • 32,541
  • 5
  • 83
  • 97
  • OK, so the non-null contract is implicit in idiomatic Scala: I can accept that - use Option for Optional. I'd still prefer (until I become more comfortable with the idiom I suppose), being able to be explicit about the non-nullness. Value classes also look like the ticket. Thanks. Hence accepting your answer – David Kerr Mar 04 '13 at 17:47
4

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
Randall Schulz
  • 26,420
  • 4
  • 61
  • 81
  • Thanks. I'm wanting to **document** intent on my interfaces, but I completely agree about banishing null's to the system-boundary. Nice! No more defensive null-checks. But this isn't enforced. C++ references enforce (near-enough) non-nullness I'm still new to Scala, so I'm hazy on lots of this stuff. Too many years putting up with Java :) – David Kerr Mar 04 '13 at 17:37
  • How is it not enforced? `Option` values are either `None` or `Some(value)`. And the type itself serves to document the possible alternatives. Lastly, if `null`s come from some non-Scala code, then they do and no amount of documenting to the contrary will change that. So I'm unclear what your objection is. – Randall Schulz Mar 04 '13 at 17:46
  • Excuse me. I meant if I weren't to follow idiomatic Scala. I'm looking for a new equilibrium, i.e. in Java I'm constantly checking against null and hate it. I need to "let go" of that baggage! – David Kerr Mar 04 '13 at 17:58
  • 4
    He's correct that Option types don't enforce non-nullity. In addition to None and Some(value), option types can also be null or (worse) Some(null). Yes, it's horrific style, but the compiler and runtime certainly do nothing to prevent it. – Dave Griffith Mar 04 '13 at 21:29
  • @DaveGriffith: If you do as I illustrated in the Addendum, you won't get `Some(null)`. – Randall Schulz Mar 04 '13 at 22:44
  • 2
    IMHO, the `final case class Some(x)` constructor (in Option.scala) is missing a vital ingredient: `require(x != null)`. If this were to be added, it would become impossible to accidentally create `Some(null)`. – Rick-777 Mar 05 '13 at 12:08
  • @Rick-777: as I am coming to understand it, `Some(null)` is actually legitimate: for example a Map can have a null value against a key. Of course, I would like some kind of mechanism that doesn't allow this, as per your comment. – David Kerr Mar 05 '13 at 14:41
  • This doesn't really address what I want, to wit writing a method signature that makes the RUNTIME enforce non-null (something like a C++ reference '&'). I agree about banishing null to the system-boundary (don't allow garbage in), but some other less-scrupulous developer might not do the necessary garbage filtering - hence allowing nulls into the system. I want to protect against that on my method signature. Something like Required[T] or the C++ reference '&'. – David Kerr Mar 05 '13 at 14:46
  • 1
    C++'s reference-forming operator does not really guarantee non-nullness of the result, does it? You can force the matter: `int *i = null; int &ir = &(*i)` (My C++ is pretty rusty, but I think that's right...). In any event, I don't think you're going to get what you're looking for in Scala unless you're willing to write a compiler plug-in or conceivably some macros. – Randall Schulz Mar 05 '13 at 15:19
  • Agreed C++ can be circumvented as you say or by the const_class thing, but IMO if you do nasty casting you get your just desserts!. But, ok I accept there's no way of specifying what I'd like the runtime to enforce. Shame :( – David Kerr Mar 05 '13 at 15:26
  • @DecaniBass the point of Option is that you never need `Some(null)`, even in a Map. You'd put `None` against a key if you explicitly need to include it with no value. – Rick-777 Mar 05 '13 at 16:18
  • @Rick-777: I understand that :) and I avoid putting nulls in maps. My desire is for a mechanism to make runtime check a "required" parameter. Yes, there's a performance cost, but the source clarity and confidence it brings me (when reviewing a class) is what I want. – David Kerr Mar 05 '13 at 16:45
2

The Scala standard library comes built-in with exactly these kinds of assertion mechanisms: the assert, assume, required, and ensuring methods. The latter two especially allow you to write preconditions and postconditions in a Design-By-Contract style. Simple example of natural number division:

def divide(x: Int, y: Int): Int = {
  require(x > y, s"$x > $y")
  require(y > 0, s"$y > 0")

  x / y
} ensuring (_ * y == x)

The require calls throw an IllegalArgumentException if the requirements are not met, and show the interpolated string as the exception's message. The ensuring call throws an exception if the given condition doesn't hold.

More details at: https://madusudanan.com/blog/scala-tutorials-part-29-design-by-contract/

There's also a tool that does formal verification on a subset of Scala written in this style: https://github.com/epfl-lara/stainless

Yawar
  • 11,272
  • 4
  • 48
  • 80