Friday, December 10, 2010

Klingon Scala

English is a "subject-verb-object" language.  This denotes the ordinary order of words in an English sentence.  For example, "He loves her," reveals his feelings, but not hers.  When we write code in an object oriented language, we tend to choose words that reflect this practice.

In Scala, given a Set ns of Integers, we can use the Set's contains method to ask whether a given number is in the Set.

  val ns = Set(8, 15, 17)
  println(ns contains 42)  //false
  println(ns contains 17)  //true

In a sense, contains is a binary operator that carries ordered pairs to Booleans.  We emphasize that, unlike other binary operators such as `+`, this one is not commutative.  A snippet like "42 contains ns" would mean something else entirely, and doesn't even compile.

A DSL with ∈

Klingon (Tlingan) is an "object-verb-subject" language.  Translating word-by-word from such a language, "Her loves He", or more properly, "She is-loved-by him" again tells us about his feelings, but not hers.  Sometimes when writing code, it would be easier on the reader to shuffle the order of our operands.

  println(42 `∈` ns)

This of course does not compile because there is no such `∈` method of integers. However, when the gain in readability is worth the effort, Scala offers a way to write such expressive code.

  class MyElement[X](x :X) {
    def `∈`(xs :Set[X]) = xs contains x
  }
  implicit toMyElement[X](x :X) = new MyElement(x)

This approach contrasts a bit with monkey patching found in other languages.  On one hand, the Scala approach tends to be a bit more verbose, since a new class is defined.  On the other hand, Scala allows careful control over the modification's scope.  Instead of globally altering the integer type as monkey patching would do, Scala affects the code only where the implicit function is imported.

Friday, December 3, 2010

Scala Duck Typing, Almost

There are a couple of different approaches to type systems, and I'm not talking about the whole static vs. dynamic thing. The nominative approach requires subtypes to extend base types explicitly. The structural approach allows types to be equivalent if they merely have the same methods. Scala supports both.

Nominative Example

trait Printable { def print :Unit }

class Nominative extends Printable {
def print { println("Nominative") }
}

If we define a function that accepts Printable instances, then it will happily accept Nominative instances, too.

def nominative(p :Printable) = {
p.print
}

nominative(new Nominative)

Structural Example

Because our Structural class below does not explicitly extend Printable, the compiler does not let us pass its instances into the nominative function, even though it has a suitable print method. And sometimes, that's exactly the kind of type safety we want.

class Structural {
def print { println("Structural") }
}

nominative(new Structural) // does NOT compile

But, other times it isn't. Scala is powerful enough to support structural types, whose definitions look like traits but without names. We can use the type keyword to give our structural type an alias.

type CanPrint = { def print :Unit }

def structural(p :CanPrint) = {
p.print
}

structural(new Nominative)
structural(new Structural) // compiles!

A nice feature here is that our Structural class was defined before CanPrint, so structural typing is useful when we must adapt old code to a new purpose.

An Interesting Idiom

Finally, let's consider an interesting non-legacy case. Suppose we want structural typing, and we also have full control over our class definitions. It sure would be nice to be able to have the compiler check that our signatures match up.

Unfortunately (or perhaps fortunately, since it's not completely clear what it should mean), the following does not compile.

class DoesNotCompile extends CanPrint {

So instead, let's use the Predef.identity generic function to ensure that our class does indeed have the correct structure.

class AnotherStructural {
identity[CanPrint](this)

def print :Unit = {
println("AnotherStructural")
}
}

If we had misspelled or forgotten the print method, our class would not have compiled.