Look over there
So if you're one of the screaming throngs following my ramblings, look over there for further posts; I imagine I'll let this blog sort of wither on the vine. And don't trip over that other throng on your way out.
(define <user> (structure () username password)) (define <admin> (structure (<user>) roles)) (define <trial> (structure () start-date)) (define <trial-user> (structure (<user> <trial>)))
If you're used to Java or C# or C++, you might be tempted to think
that <trial-user> inherits from <user> and
<trial>. It doesn't. In Categories, types don't have
inheritance. The definition of <trial-user> includes
<user> and <trial>, which means that it has all the fields
that are defined in those types. We haven't defined any inheritance
yet, though. Inheritance is possible, as we'll see shortly, but it
belongs to domains, not types.
(define print-user (function -flat-))
This function belongs to the -flat- domain. -flat- is a very simple
domain that supports polymorphic functions, but not inheritance. That
means that if you define a method on <number>, then it will run
only if you pass an argument of type <number>. Passing an
argument of type <integer> won't select that method, because
<integer> is not <number>. -flat- does not support
inheritance. The disadvantage is that you can't automatically inherit
behavior in -flat-. The advantage is that it's simple and fast--and
you don't automatically inherit behavior. Hey, sometimes you don't
want to inherit.
(add-method! print-user
(flat:method ((u <user>))
(newline)
(display "User: ")
(display (get-key u 'username))
(newline)))
(add-method! print-user
(flat:method ((u <admin>))
(newline)
(display "Administrator: ")
(display (get-key u 'username))
(newline)
(display " Roles: ")
(display (get-key u 'roles))
(newline)))
(add-method! print-user
(flat:method ((u <trial-user>))
(newline)
(display "Trial user: ")
(display (get-key u 'username))
(newline)
(display " Start date: ")
(display (get-key u 'start-date))
(newline)))
The macro flat:method is a convenience provided by the -flat- domain
that creates a method suitable for use with -flat- functions. Now the
function print-user has three methods that work on three different
types. Let's try them out.
(define $user (make <user> 'username "fred" 'password "dino")) (define $admin (make <admin> 'username "joe" 'password "f23Zb!" 'roles '(gm dev))) (define $trial (make <trial-user> 'username "noob" 'password "noob" 'start-date "2009-11-11"))
(print-user $user) User: fred (print-user $admin) Administrator: joe Roles: (gm dev) (print-user $trial) Trial user: noob Start date: 2009-11-11
Categories dispatches on more than one argument. Let's define a
two-argument function and see how that works:
(define times (function -flat-))
(add-method! times
(flat:method ((n <integer>)(x <integer>))
(* n x)))
(add-method! times
(flat:method ((x <integer>)(y <integer>)(z <integer>))
(* x y z)))
(add-method! times
(flat:method ((n <integer>)(s <string>))
(apply string-append (vector->list (make-vector n s)))))
(add-method! times
(flat:method ((n <integer>)(s <symbol>))
(make-vector n s)))
(times 2 3) ; => 6
(times 2 3 4) ; => 24
(times 2 "Foo") ; => "FooFoo"
(times 2 'bob) ; => #(bob bob)
Notice that (times 2 3) calls a different method from (times 2 "Foo")
or (times 2 'bob). The difference is the second argument: in the first
example it's an integer; in the second, it's a string; in the third,
it's a symbol.
Okay, what if you want inheritance? Well, you use a different
domain. The domain I just added, -c3-, supports multiple
inheritance. We can write the same examples for -c3- with just a few
changes. First of all, let's tell -c3- about the new types we created:
(c3-dom:derive-type! -c3- <user> (list <anything>)) (c3-dom:derive-type! -c3- <admin> (list <user>)) (c3-dom:derive-type! -c3- <trial> (list <anything>)) (c3-dom:derive-type! -c3- <trial-user> (list <user> <trial>))
The type <anything> is the root of the -c3- type graph;
everything inherits from it one way or another. c3:derive-type! is a
convenience provided by -c3- for telling the domain about new
types. All you have to do is tell -c3- what supertypes you want a new
type to have; it figures out the rest.
Notice that in -c3-, <trial-user> inherits from <trial>
and <user>, just as you would expect. There's an important
subtlety here, though. The definition of the <trial-user> type
gave it the same fields as <trial> and <user>. The
c3-dom:derive-type! function made it inherit from those two types. The
important point is that it doesn't have to inherit from the
same types that it includes. Sometimes you want to say that a new type
is a subtype of some other type, but you don't want to inherit all
fields of the parent types. Representation and taxonomy are two
different things, and Categories doesn't force you to combine them,
though you can if you want.
Okay, now how do we write a polymorphic function in -c3-? The same way
we did it in -flat-, except that we make a -c3- function instead of a
-flat- function, like so:
(define print-user (function -c3-))
This definition replaces the old one, so print-user is no longer a
-flat- function. You can have both -c3- functions and -flat-
functions; they just can't have the same names.
(add-method! print-user
(c3:method ((u <user>))
(newline)
(display "User: ")
(display (get-key u 'username))
(newline)))
(add-method! print-user
(c3:method ((u <admin>))
(next-method u)
(display "Admin roles: ")
(display (get-key u 'roles))
(newline)))
(add-method! print-user
(c3:method ((u <trial-user>))
(next-method u)
(display "[Trial] Start date: ")
(display (get-key u 'start-date))
(newline)))
This time we use c3:method instead of flat:method. c3:method is a
convenience that, you guessed it, creates a method suitable for use
with -c3-. (Actually, at this point there is no significant difference
between the two kinds of method, but the important point is that there
could be. A user-defined domain is free to represent type
relationships and method signatures any way it wants).
Notice that the -c3- methods are a little bit shorter. That's because
they take advantage of inheritance to reuse the code defined for
supertypes. The methods for <admin> and <trial-user> call
next-method to reuse the code defined for <user>.
(print-user $user) ; => User: fred (print-user $admin) ; => User: barney Admin roles: (gm editor) (print-user $trial) User: noob [Trial] Start date: 2009-11-11
Notice that I didn't redefine any of the types, and I didn't recreate
any of the instances. Changing domains affects only the taxonomy. The
types and their instances don't have to change at all. you can reuse
the same types and values in several different domains at the same
time.
The two-argument functions work similarly, but again we can leverage
the fact that -c3- supports inheritance:
(define times (function -c3-))
(add-method! times
(c3:method ((n <number>)(x <number>))
(* n x)))
(add-method! times
(c3:method ((x <number>)(y <number>)(z <number>))
(* x y z)))
(add-method! times
(c3:method ((n <number>)(s <string>))
(apply string-append (vector->list (make-vector n s)))))
(add-method! times
(c3:method ((n <number>)(s <symbol>))
(make-vector n s)))
(times 2 3) ; => 6
(times 2 3.0 4.0) ; => 24.0
(times 2 "Foo") ; => "FooFoo"
(times 2 'bob) ; => #(bob bob)
We don't have to define methods specifically for <integer> and
<float> in -c3-; the <number> method works on values of those
types just fine, because -c3- defines <float> and
<integer> as subtypes of <number>. Otherwise, the behavior
of this function is the same as in the -flat- domain.
(define-method add ((x <integer>)(y <integer>))
(+ x y))
(define-method add ((x <integer>)(y <integer>)) :: -c3-
(+ x y))
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 |