The Expression Problem and Tables
joelburget.com
joelburget.com
http://clojure.org/multimethods
(defmulti sound class)
(defmulti eat class)
(defmulti attack class)
(defmethod sound ::cow [c] "Moo!")
(defmethod sound ::dog [c] "Roof!")
(defmethod sound ::cat [c] "Miau!")
(defmethod attack ::dog [c] (bite c))
(defmethod attack ::cat [c] (scratch c))
(defmethod eat ::cow [c] ...)
(defmethod eat ::dog [c] ...)
(defmethod eat ::cat [c] ...)
...
(derive ::mad-cow ::cow)
(defmethod sound ::mad-cow [c] "MooOOOooooOOOOoooo!")
(defmethod attack ::mad-cow [c] (stampede c))
The layout problem remains, and I would like to be able to do sql- or prolog-like queries on code, but IMHO it's IDE and tooling problem more than missing language feature.Also in clojure you can add arbitrary http://clojure.org/metadata to each value, so marking your function as deprecated/in-progress/whatever is easy (but it seems you can't easily add metadata to separate implementations of multimethod right now).
...
(defmulti "Attacks."
{:deprecated "Use eat instead.",
:author "me"}
attack class)
...
Also - to confirm that people want to specify rules in tables - I've been working at 2 different companies where we had code generators with .xlsx files as input and .java as output.I'm not sure if Clojure supports that, but in CL you can also have :before, :after and :around methods that will be called before, after and instead of an actual method, respectively. This gives you another interesting boost to extensibility.
EDIT:
RE querying - using Metaobject Protocol (MOP) you can do all sorts of queries you like. For instance, to compute "animals that eat" you could use generic-function-methods on a generic eat to get a list of all method metaobjects (objects associated with methods), from which you can determine for which classes an eat method is defined.
CLOS + MOP is quite possibly the most powerful object system ever made.
In languages like Smalltalk or Lisp, the Expression Problem has easy solutions - but they are not statically typed.
With that said, you can add gradual typing to Clojure with Core.Typed and still solve the expression problem using multimethods without violating type safety.
I say this not because it will prove impossible or useless, but because you will learn things in the process that will help you refine the idea. Refinement into "what I want is not really possible at scale" is certainly one possible outcome, but there are many other possibilities.
(Certainly one problem you'll encounter is that real code is horrifyingly non-"normal", in the statistical distribution sense. A program of 100 classes is very likely to have 90 of them part of one hierarchy, with the remaining 10 each doing isolated things. Understanding the table representation of such things may have the MEGO ("my eyes glaze over") problem, but then, perhaps you can find something for that.)
I think that this problem is basically a simple structural relation on sum types in disguise. That's how I interpret the "Data Types a la Carte" paper anyway.
So if you have:
class Foo a where
foo :: a -> String
And say: instance Foo A+B where
foo A = "a"
foo B = "b"
And: instance Foo C+D where
foo C = "c"
foo D = "d"
Then if you extend your data type to A+B+C+D, then a simple instance generator should be enough to tie it all together: instance (Foo v, Foo w) => Foo v+w where
foo (inl v') = foo v'
foo (inr w') = foo w'
I have a (currently internal to my company) Haskell-like language with structural sum/product/variant/record types where we can do this kind of thing. Otherwise "Data Types a la Carte" has the solution in the typed setting of vanilla Haskell (even if it's a little wordy). type animal = Cat | Dog
type action = Sound | Eat | Attack
let cat_sound () = printf "meow"
let cat_attack () = printf "scratch"
let dog_sound () = printf "woof"
let act animal action = match animal,action with
| Cat, Sound -> cat_sound ()
| Cat, Attack -> cat_attack ()
| Dog, Sound -> dog_sound ()
Compiling this yields: Warning 8: this pattern-matching is not exhaustive.
Here is an example of a value that is not matched:
(Dog, (Eat|Attack))
just as we'd like.C++ will catch a missing combination at compile time as well.
It is, however, an extremely simple way of implementing the table-style dispatch outlined in the post, with a reasonable layout and compile-time completeness checks.
It's not open to extension outside the compilation unit, but in exchange it is dead simple and you still only ever need to change exactly two places in the code if you want to extend along either axis. Sometimes you just don't need polymorphism when a switch statement will do.
[0] https://news.ycombinator.com/item?id=9409779 [1] (PDF) http://www.math.nagoya-u.ac.jp/~garrigue/papers/variant-reus...
I think what you're looking for is a good IDE and Scala or Java
> animals which eat
type eater interface { eat() }
somEater, isEater := value.(eater)
Or something, my Go isn't very good.