Monday, November 23, 2009

Protocols and template conditions

I spent most of the weekend working on protocols and template conditions, and while they aren't working yet, they are very close.

Template conditions allow you specify a restriction on what types a template parameter can bind to. For example, say we wanted to be able to define a template that could only be used with subclasses of Exception:

    class ExceptionFilter[%T <: Exception] { ... }

The '<:' operator is equivalent to the Python function "issubclass". %T is a type variable (the percent sign introduces a new type variable.)

Tart allows you to have multiple definitions of the same template, as long as it is unambiguous which one to use:

    class LogFormatter[%T <: Exception] { ... }
    class LogFormatter[String] { ... }

Note that the second declaration of LogFormatter did not declare a type variable. In this case, the type argument 'String' is a type literal, not a variable - in other words, the template pattern will only match if the type argument is type 'String' exactly.

The template conditions can also be given outside of the template parameter list, using the 'where' clause:

    class LogFormatter[%T]
      where T <: Exception {
      ...
    }

There can be an arbitrary number of 'where' clauses on a template. (Note: Where clauses are not implemented, but they are not hard to do now that I have the basic condition architecture in place.)

One particularly useful template condition is whether a class has a particular method or not. Rather than defining as "hasmethod" operator, Tart allows you declare a pseudo-interface called a "protocol". For example, if we wanted to test whether a class has a "toString()" method, we would start by defining a protocol containing the toString() method:

    protocol HasToString {
      def toString() -> String;
    }

Syntactically, protocols are like interfaces, however they are psuedo-types, not real types - you can't declare a variable whose type is a protocol. You can inherit from a protocol, but all that does is cause the compiler to emit a warning if any of the protocol's methods are not implemented. It does not change the layout or the behavior of the class in any way.

Another difference with protocols is that any class that meets the requirements of the protocol is considered a subtype of the protocol - regardless of whether the class explicitly declares the protocol as a base type or not.

Here's a concrete example: Suppose we want to make a "universal stringifier" that will convert any value to a string:

    def str(obj:Object) -> String { return obj.toString(); }
    def str[%T <: HasToString](v:T) -> String { return v.toString(); }
    def str[%T](v:T) { return "???"; }

The first overload handles all of the types which are subclasses of Object. Since we know "Object" has a toString() method, and since that method is dynamically dispatched (i.e. the equivalent of C++ "virtual"), the toString() call will get routed to the right place.

The next line handles all types that have a 'toString' method, whether or not they explicitly inherit from "HasToString". This includes things like integers and floats, which have a toString() method (in Tart, you can say things like "true.toString()"). Since this is a template, it will create a copy of the function for each different type of T. (That's why we handled 'Object' as a separate case, to limit the number of such copies generated.)

The third overload handles classes which don't have a 'toString' method. (If you are wondering what kinds of types don't have a default toString() method, the answer includes structs, unions, tuples, native C data types, and so on.) In the example, it just returns the string "???" but in fact it could be much smarter - at minimum printing the string name of type T.

The overload resolver will attempt to match the most specific template whose constraints are met. Thus, the first method is preferred over the second since the second requires binding a pattern variable, and the second is preferred over the third because it has a condition.

-- 
-- Talin

No comments:

Post a Comment