Apocalisp

The end of programming as you know it

Archive for August, 2009

A Scala Puzzler

Posted by Rúnar on August 28, 2009

I’ve been toying around with Scala’s type system a little bit, and I’ve come to the conclusion that it’s completely brilliant. And I’m very encouraged by how rapidly it has improved.

There is, for example, limited support for type-level computation. But not so much that typing becomes undecidable.

In Scala, types can have type members, and those members can take type parameters. Type members can be bounded, and so can their parameters.


trait Lambda { type a[A <: Lambda] <: Lambda }

This feature gives you quite a lot of expressive power in the type system. Type members are accessed by use of the # symbol. You can nest types, so Scala effectively has closures for types. So you can do crazy stuff like this:

  trait S extends Lambda { type a[X <: Lambda] = Lambda { type a[Y <: Lambda] = Lambda { type a[Z] = X#a[Z]#a[Y#a[Z]] }} }

OK, since we basically have the power of the lambda calculus in the type system, let’s see if we can do Church encoding of datatypes. It turns out that we can, to an extent. Here are booleans:

  trait Boolean { type cond[t, f] }
  trait TFalse extends Boolean { type cond[t, f] = f }
  trait TTrue extends Boolean { type cond[t, f] = t }

Let’s test that out.

scala> val x: TTrue#cond[Int, String] = 10
x: TTrue#cond[Int,String] = 10

scala> val x: TTrue#cond[Int, String] = "10"
:6: error: type mismatch;
 found   : java.lang.String("10")
 required: TTrue#cond[Int,String]
 val x: TTrue#cond[Int, String] = "10"

It works. We can assign an Int to the value if the condition is TTrue, and a String if it’s TFalse, but not vice versa.

How about church numerals? Here are those:

trait Num { type a[F[_], X] }
trait Zero extends Num { type a[F[_], X] = X }
trait One extends Num { type a[F[_], X] = F[X] }
trait Two extends Num { type a[F[_], X] = F[F[X]] }
trait Three extends Num { type a[F[_], X] = F[F[F[X]]] }

The way this works is that the numerals take some type constructor F and a unit type X. For example, to get lists of strings three levels deep, Three#a[List, String] is equivalent to the type List[List[List[String]]]

scala> val x: Three#a[List, String] = List(List(List("Three")), List(List("Levels"), List("Deep")))
x: Three#a[List,String] = List(List(List(Three)), List(List(Levels), List(Deep)))

We can even do some arithmetic. Here are three type-level functions. Respectively, they add two numbers together, increment a number by one, and multiply one number by another:

trait PApp[F[P[_], _], A[_]] {
  type a[X] = F[A, X]
}

trait Plus[M <: Num, N <: Num] extends Num { type a[F[_], X] = M#a[F, N#a[F, X]] }
trait Succ[N <: Num] extends Num { type a[F[_], X] = F[N#a[F, X]] }
trait Mult[M <: Num, N <: Num] extends Num { type a[F[_], X] = PApp[N#a, PApp[M#a, F]#a]#a[X] }

Pretty sweet. For example, a List of Strings 20 levels deep would be of the type Mult[Succ[Three], Plus[Two, Three]]]#a[List, String]

Now for the puzzler.

Exercise: Write a type-level function Exp[M, N] that yields the Church numeral M lifted to the Nth power. Can it be done? If not, why not?

Posted in Programming, Scala | 1 Comment »

Some Notes in Hostility Toward Subtyping

Posted by Rúnar on August 27, 2009

What follows is a session of thinking out loud, so it’s not to be taken too seriously.

The more I think about it, the more I convince myself that the idea of subtyping and class hierarchies is a mistake. “Inheritance” is a good way to lock down a design so that it becomes rigid and brittle. If you remember your (now scorched, I trust) GoF book, its main cargo was “composition over inheritance”. So why do we want this mechanism at all? Because we want type substitution. We say that if X is a subtype of Y, then X can be substituted wherever Y is expected. Informally, we want to say that an X is a Y. But what does this mean? It means that the set of all values in X is a subset of all the values in Y. So if you have a value x in X, then x is also in Y. In other words, X implies Y, or (X => Y). Hang on. This is just the type signature for a function.

When we observe this, we realise that we can substitute functions for subtyping. Moreover, we can make those functions implicit (as in Scala) and then type substitution will work just as if we were using subtypes (hopefully without loss of type inference). As a bonus, our implicit type substitutions have scope, while subtyping is a global declaration.

For example, take a look at the function type (A => B), recalling that this conceptually the same as saying “A implies B”, or even “A is a subtype of B”. Using subtyping, we could have types Foo and Bar, and say “Bar extends Foo”. But instead of subtyping, in Scala we could have an implicit function:

implicit def fooBar(b: Bar): Foo = b.foo

The easiest way to implement this is to have Bar wrap a value of type Foo by accepting it in its constructor. Then the Bar.foo method simply returns this value. But it can really be implemented any way we want. For example, if both Foo and Bar take a parameter of type Int, then we can extract it from one when constructing the other.

There’s a slight problem with implicit functions like this. Somebody else may have defined a function with the same type already, with a totally different intent. To remedy that, we can create a type for a specific kind of conversion to Foo; one that implies the relation we want to express. Something like…

trait IsAFoo[A] {
  def apply(a: A): Foo
}

This is now unambiguous. This kind of construct is a typeclass. IsAFoo classifies all types that imply type Foo. We can supply an implicit instance of this typeclass for Bar:

implicit val bar_is_a_foo = new IsAFoo[Bar] {
  def apply(b: Bar) = b.foo
}

And wherever we want to accept something that “is a” Foo, we accept an implicit parameter as evidence that it is indeed a Foo.

implicit def doSomethingWithFoo[A](implicit foo: IsAFoo[A])(a: A) = foo(a).methodOnFoo

We can call this method with a value of type Bar, because of the existence of the implicit instance bar_is_a_foo. In fact, Scala has an even nicer syntax for this, using “view bounds”. I leave it to you to check that out. What I want to impress on you is how flexible typeclasses are. We’re not constrained to using this mechanism to substitute for subtyping. We can use it to do the converse, i.e. supertyping an existing type. Or we can have the conversion go both ways to express isomorphisms between types. The pattern here is that we want to be able to state any kind of relation between types.

If Scala had the ability to state functional dependencies, the typeclass mechanism truly could obviate subtyping, with the added bonus that we could state any kind of type-level relation that we want, rather than just type order. I could talk about the downside of this, but… I could go on forever.

Variance of Functors

So, speaking of type order, there’s a tie-in here with variance. This is one of the things that trips people up when thinking about class hierarchies. I know it trips me up. But variance is much easier to reason about if we think of the subtype/supertype relation as just a function.

To compare, here’s what Wikipedia has to say about variance in class hierarchies:

If class B is a subtype of class A, then all member functions of B must return the same or narrower set of types as A; the return type is said to be covariant. On the other hand, the member functions of B must take the same or broader set of arguments compared with the member functions of A; the argument type is said to be contravariant. The problem for instances of B is how to be perfectly substitutable for instances of A. The only way to guarantee type safety and substitutability is to be equally or more liberal than A on inputs, and to be equally or more strict than A on outputs.

OK, but why is it the case that this is the only way to guarantee substitutability and type-safety? To understand that, it helps to throw away the notion of “subtype”, and simply think only in terms of functions, instead of the mixed notions of “subtype” and “member function”. Try translating that snippet from Wikipedia so that it’s worded in terms of functions. It gets pretty convoluted.

Proceeding from the premise that functions and subtype relations are interchangeable, we can derive a definition of co- and contravariance simply from the definition of substitutability. We start with this:

If B is a subtype of A, then every subtype of B is also a subtype of A, and every supertype of A is also a supertype of B.

Now, if we take (A <: B) to mean “A is a subtype of B”, and (A => B) to mean “function from A to B”, or equivalently “A implies B”, we can write this property as:

(A <: B) <==> ((B <: C) => (A <: C)) and ((C <: A) => (B <: C))

Remember that you can substitute functions for subtyping and vice versa, so any (=>) sign above can be replaced with the (<:) sign, or the other way around, and the meaning stays intact. So let’s restate this property purely in terms of functions: If there exists a function from B to A, then for every function from C to B there exists a function from C to A, and for every function from A to C there exists a function from B to C. (B => A) <==> ((C => B) => (C => A)) and ((A => C) => (B => C))

Moving (B => A) to the left of the <==> sign, we can infer two properties of (B => A):

(B => A) => (C => B) => (C => A)
and
(B => A) => (A => C) => (B => C)

Recalling that function application is logical implication, both of these properties evaluate to true, for all A, B, and C. Let’s use Wolfram Alpha to confirm this for us.
See here and here.

Let’s now say that C is a fixed type. Remembering what we did above with typeclasses, this gives rise to two such typeclasses, representing (C => A) and (A => C), respectively, for all A:

trait FromC[A] {
  def fromC(c: C): A
}
trait ToC[A] {
  def toC(a: A): C
}

To restate the properties above:

(B => A) => FromC[B] => FromC[A]
(B => A) => ToC[A] => ToC[B]

In other words, mapping a function (B => A) over FromC[B] results in FromC[A]. So the implication is preserved across the mapping. Mapping a function (B => A) over ToC[A] results in ToC[B]. So the implication is reversed across the mapping. This means that FromC is a covariant functor and ToC is a contravariant functor, by the definition of co- and contravariance. So now we have anchored the notion of substitutability to the variance of functors. For reference, here’s my off-the-cuff definition of co- and contravariance, along with some preliminaries:

Definition of Higher-Order Function
A higher-order function (HOF) is a function that takes another function as its argument.

Definition of Functor
For our purposes, a functor is a unary type constructor T[_]. There are some additional properties, but we’ll gloss over them for now.

Definition of covariance
A functor T[_] is covariant if and only if there exists a HOF with the following type, for all A and B:
(A => B) => T[A] => T[B].

Definition of contravariance
A functor T[_] is contravariant if and only if there exists a HOF with the following type, for all A and B:
(B => A) => T[A] => T[B].

Note that these last two are exactly the properties above that we got for FromC and ToC, which we derived directly from the definition of subtype and supertype.

Conclusion

Algebraic data types can be simulated with subtyping in a language that lacks them. Conversely, in a language that has algebraic data types and a mechanism for classifying types, subtyping is obviated. The benefits of subtyping, namely type substitution, can be expressed more naturally with a mechanism for classifying types and functional dependencies between them.

Posted in Programming, Scala | 2 Comments »

Structural Pattern Matching in Java

Posted by Rúnar on August 21, 2009

One of the great features of modern programming languages is structural pattern matching on algebraic data types. Once you’ve used this feature, you don’t ever want to program without it. You will find this in languages like Haskell and Scala.

In Scala, algebraic types are provided by case classes. For example:

sealed trait Tree
case object Empty extends Tree
case class Leaf(n: Int) extends Tree
case class Node(l: Tree, r: Tree) extends Tree

To define operations over this algebraic data type, we use pattern matching on its structure:

def depth(t: Tree): Int = t match {
  case Empty => 0
  case Leaf(n) => 1
  case Node(l, r) => 1 + max(depth(l), depth(r))
}

When I go back to a programming language like, say, Java, I find myself wanting this feature. Unfortunately, algebraic data types aren’t provided in Java. However, a great many hacks have been invented over the years to emulate it, knowingly or not.

The Ugly: Interpreter and Visitor

What I have used most throughout my career to emulate pattern matching in languages that lack it are a couple of hoary old hacks. These venerable and well respected practises are a pair of design patterns from the GoF book: Interpreter and Visitor.

The Interpreter pattern really does describe an algebraic structure, and it provides a method of reducing (interpreting) the structure. However, there are a couple of problems with it. The interpretation is coupled to the structure, with a “context” passed from term to term, and each term must know how to mutate the context appropriately. That’s minus one point for tight coupling, and minus one for relying on mutation.

The Visitor pattern addresses the former of these concerns. Given an algebraic structure, we can define an interface with one “visit” method per type of term, and have each term accept a visitor object that implements this interface, passing it along to the subterms. This decouples the interpretation from the structure, but still relies on mutation. Minus one point for mutation, and minus one for the fact that Visitor is incredibly crufty. For example, to get the depth of our tree structure above, we have to implement a TreeDepthVisitor. A good IDE that generates boilerplate for you is definitely recommended if you take this approach.

On the plus side, both of these patterns provide some enforcement of the exhaustiveness of the pattern match. For example, if you add a new term type, the Interpreter pattern will enforce that you implement the interpretation method. For Visitor, as long as you remember to add a visitation method for the new term type to the visitor interface, you will be forced to update your implementations accordingly.

The Bad: Instanceof

An obvious approach that’s often sneered at is runtime type discovery. A quick and dirty way to match on types is to simply check for the type at runtime and cast:

public static int depth(Tree t) {
  if (t instanceof Empty)
    return 0;
  if (t instanceof Leaf)
    return 1;
  if (t instanceof Node)
    return 1 + max(depth(((Node) t).left), depth(((Node) t).right));
  throw new RuntimeException("Inexhaustive pattern match on Tree.");
}

There are some obvious problems with this approach. For one thing, it bypasses the type system, so you lose any static guarantees that it’s correct. And there’s no enforcement of the exhaustiveness of the matching. But on the plus side, it’s both fast and terse.

The Good: Functional Style

There are at least two approaches that we can take to approximate pattern matching in Java more closely than the above methods. Both involve utilising parametric polymorphism and functional style. Let’s consider them in order of increasing preference, i.e. less preferred method first.

Safe and Terse – Disjoint Union Types

The first approach is based on the insight that algebraic data types represent a disjoint union of types. Now, if you’ve done any amount of programming in Java with generics, you will have come across (or invented) the simple pair type, which is a conjunction of two types:

public abstract class P2<A, B> {
  public A _1();
  public B _2();
}

A value of this type can only be created if you have both a value of type A and a value of type B. So (conceptually, at least) it has a single constructor that takes two values. The disjunction of two types is a similar idea, except that a value of type Either<A, B> can be constructed with either a value of type A or a value of type B:

public final class Either<A, B> {
  ...
  public static <A, B> Either<A, B> left(A a) { ... }
  public static <A, B> Either<A, B> right(B a) { ... }
  ...
}

Encoded as a disjoint union type, then, our Tree data type above is: Either<Empty, Either<Leaf, Node>>

Let’s see that in context. Here’s the code.

public abstract class Tree {
  // Constructor private so the type is sealed.
  private Tree() {}

  public abstract Either<Empty, Either<Leaf, Node>> toEither();

  public static final class Empty extends Tree {
    public <T> T toEither() {
      return left(this);
    }

    public Empty() {}
  }

  public static final class Leaf extends Tree {
    public final int n;

    public Either<Empty, Either<Leaf, Node>> toEither() {
      return right(Either.<Leaf, Node>left(this));
    }

    public Leaf(int n) { this.n = n; }
  }

  public static final class Node extends Tree {
    public final Tree left;
    public final Tree right;    

    public Either<Empty, Either<Leaf, Node>> toEither() {
      return right(Either.<Leaf, Node>right(this));
    }

    public Node(Tree left, Tree right) {
      this.left = left; this.right = right;
    }
  }
}

The neat thing is that Either<A, B> can be made to return both Iterable<A> and Iterable<B> in methods right() and left(), respectively. One of them will be empty and the other will have exactly one element. So our pattern matching function will look like this:

public int depth(Tree t) {
  Either<Empty, Either<Leaf, Node>> eln = t.toEither();
  for (Empty e: eln.left())
    return 0;
  for (Either<Leaf, Node> ln: t.toEither().right()) {
    for (leaf: ln.left())
      return 1;
    for (node: ln.right())
      return 1 + max(depth(node.left), depth(node.right));
  }
  throw new RuntimeException("Inexhaustive pattern match on Tree.");
}

That’s terse and readable, as well as type-safe. The only issue with this is that the exhaustiveness of the patterns is not enforced, so we’re still only discovering that error at runtime. So it’s not all that much of an improvement over the instanceof approach.

Safe and Exhaustive: Church Encoding

Alonzo Church was a pretty cool guy. Having invented the lambda calculus, he discovered that you could encode data in it. We’ve all heard that every data type can be defined in terms of the kinds of operations that it supports. Well, what Church discovered is much more profound than that. A data type IS a function. In other words, an algebraic data type is not just a structure together with an algebra that collapses the structure. The algebra IS the structure.

Consider the boolean type. It is a disjoint union of True and False. What kinds of operations does this support? Well, you might want to do one thing if it’s True, and another if it’s False. Just like with our Tree, where we wanted to do one thing if it’s a Leaf, and another thing if it’s a Node, etc.

But it turns out that the boolean type IS the condition function. Consider the Church encoding of booleans:

true  = λa.λb.a
false = λa.λb.b

So a boolean is actually a binary function. Given two terms, a boolean will yield the former term if it’s true, and the latter term if it’s false. What does this mean for our Tree type? It too is a function:

empty = λa.λb.λc.a
leaf  = λa.λb.λc.λx.b x
node  = λa.λb.λc.λl.λr.c l r

You can see that this gives you pattern matching for free. The Tree type is a function that takes three arguments:

  1. A value to yield if the tree is empty.
  2. A unary function to apply to an integer if it’s a leaf.
  3. A binary function to apply to the left and right subtrees if it’s a node.

The type of such a function looks like this (Scala notation):

T => (Int => T) => (Tree => Tree => T) => T

Or equivalently:

(Empty => T) => (Leaf => T) => (Node => T) => T

Translated to Java, we need this method on Tree:

public abstract <T> T match(F<Empty, T> a, F<Leaf, T> b, F<Node, T> c);

The F interface is a first-class function from Functional Java. If you haven’t seen that before, here it is:

public interface F<A, B> { public B f(A a); }

Now our Tree code looks like this:

public abstract class Tree {
  // Constructor private so the type is sealed.
  private Tree() {}

  public abstract <T> T match(F<Empty, T> a, F<Leaf, T> b, F<Node, T> c);

  public static final class Empty extends Tree {
    public <T> T match(F<Empty, T> a, F<Leaf, T> b, F<Node, T> c) {
      return a.f(this);
    }

    public Empty() {}
  }

  public static final class Leaf extends Tree {
    public final int n;

    public <T> T match(F<Empty, T> a, F<Leaf, T> b, F<Node, T> c) {
      return b.f(this);
    }

    public Leaf(int n) { this.n = n; }
  }

  public static final class Node extends Tree {
    public final Tree left;
    public final Tree right;    

    public <T> T match(F<Empty, T> a, F<Leaf, T> b, F<Node, T> c) {
      return c.f(this);
    }

    public Node(Tree left, Tree right) {
      this.left = left; this.right = right;
    }
  }
}

And we can do our pattern matching on the calling side:

public int depth(Tree t) {
  return t.match(constant(0), constant(1), new F<Node, Integer>(){
    public Integer f(Node n) {
      return 1 + max(depth(n.left), depth(n.right));
    }
  });
}

This is almost as terse as the Scala code, and very easy to understand. Everything is checked by the type system, and we are guaranteed that our patterns are exhaustive. This is an ideal solution.

By the way, if you’re wondering what constant(0) means, it’s a method that returns a function F<A, Integer> that always returns 0, ignoring the argument.

Conclusion

With some slightly clever use of generics and a little help from our friends Church and Curry, we can indeed emulate structural pattern matching over algebraic data types in Java, to the point where it’s almost as nice as a built-in language feature.

So throw away your Visitors and set fire to your GoF book.

Posted in Programming, Scala, java | 26 Comments »