Arc Forumnew | comments | leaders | submitlogin
Simple multimethods: lib/multi.arc
2 points by rntz 5886 days ago | 8 comments
I've implemented simple multimethods for Arc in lib/multi.arc on anarki. They are modeled, not after CLOS's type-based multimethods, but after Clojure's much simpler model (see http://clojure.org/multimethods). Basically, each multimethod is associated with a dispatcher function, which is applied to the arguments of the function and returns a key into a table of methods. (Although one could conceivably incorporate the types of all the arguments into this key and have type-based dispatch on all arguments, only exact type matches would work, so it wouldn't be very useful.)

So, borrowing examples from the Clojure site, you can do the following:

  (multi area [_ 'shape])
  (method area 'rect (r) (* r!height r!width))
  (method area 'circle (c) (* 3.14159 (* c!radius c!radius)))

  (area (obj shape 'rect height 2 width 3))
  => 6

  (area (obj shape 'circle radius 1))
  => 3.14159
Since dispatching on the value of a certain key in the first argument is probably the common use-case, I've also included a macro 'multi-keyed for just that purpose. The first line of the above could be rewritten:

  (multi-keyed area 'shape)
You can also supply a default method to 'multi and 'multi-keyed, which is called if the key which the dispatcher returns is not in the method table.


3 points by almkglor 5886 days ago | link

Arc-f actually has clojure-style multimethods hidden somewhere (I invite you all to search for them!). However, there are very deep reasons why I did not encourage their use.

The most important is that the dispatcher function is a limiting factor. To be specific, the dispatcher function means that extending the function is limited to what the dispatcher expects.

If the dispatcher function expects exactly one argument, you cannot create a method with an optional argument. For instance, consider a shape rectangular-prism

  (= shape
     (obj shape 'rectangular-prism
          height 2
          width 3
          depth 4))

  (method area 'rectangular-prism (p (o face 'front))
    (case face
      front  (* p!width p!height)
      back   (* p!width p!height)
      left   (* p!height p!depth)
      right  (* p!height p!depth)
      top    (* p!width p!depth)
      bottom (* p!width p!depth)))
Unfortunately, because the dispatcher for 'area expects exactly one argument, you cannot extend 'area with a method that supports optional arguments.

I'll be putting up a more extensive rationale for why I decided not to use Clojure-style multimethods in Arc-F (despite already having implemented them), and would very much rather prefer to bash my head trying to implement CLOS-style multimethods.

-----

4 points by rntz 5886 days ago | link

It's true that if the dispatcher expects exactly one argument, you can't add optional arguments. The solution is very simple: don't make dispatchers which expect exactly n arguments. Make all your dispatchers take rest parameters which they ignore. In fact, 'multi-keyed already does exactly that, so if you used (multi-keyed area 'shape), your example would work exactly as expected. It's not a hard fix.

Regarding reasons for not implementing Clojure-style multimethods in Arc-F, how about this one: because you can implement them in plain old arc! Don't put stuff in Arc-F if it can be done in Arc or Anarki already. Interoperability is good - we don't need to fork the community as well as the language.

-----

1 point by almkglor 5885 days ago | link

> It's true that if the dispatcher expects...

It's not just the number of arguments. One advantage of CLOS-style multimethods is this:

  (def bam (a b)
    (err "collision type unknown!"))
  (defm bam ((t a ship) b)
    (destroy ship)
    (destroy b))
  (defm bam ((t a ship) (t b missile))
    (destroy ship)
    (destroy missile)
    (add-points missile!source))
Because of the computation of keys, you can't exactly implement the above using clojure-style multimethods without tricks like method bouncing.

Like I said: already implemented clojure-style multimethods. And tried it. So yes: I'll continue bashing my head implementing CLOS-style multimethods, because while clojure-style multimethods are cute, they're not good enough for all cases. Arguably neither are CLOS-style multimethods, but at least we have an alternative choice.

Edit:

Also, there's a good reason for implementing this in the scheme-side: efficiency. Your implementation of clojure-multimethods allocates a cons cell for each argument to the multimethod. The scheme-side implementation does not, because on the scheme-side I have access to the ar-funcall* functions.

Efficiency of course is not a concern, except when it is.

As an aside, there are several bits of Arc-F that look like they're implemented in Arc, but are actually implemented in the scheme-side. There's an Arc implementation of them in arc.arc, which is labeled as "IDEAL", while the actual binding to the scheme side is labeled as "REAL". For example, the basic compose function is actually implemented in the scheme-side, but there's a reference Arc implementation in arc.arc marked "IDEAL". The only reason they're on the scheme-side is due to efficiency. Ideally, they would be in Arc: realistically, they are better implemented in the base system.

-----

1 point by rntz 5885 days ago | link

This is true, and indeed I mentioned it in the OP; you can't do type-based dispatch with clojure-style multimethods except on exact matches. You also can't get method chaining. But clojure-style has the advantage of being damn simple, and easily permits dispatch based on non-type-based conditions. CLOS style has the advantage of being more flexible about dispatch and integrating well with OO methodologies.

I'm not trying to convince you that CLOS multimethods are bad or not to implement them; a full implementation for Arc or Arc-F would be _very cool_. CLOS is without a doubt my favorite thing about Common Lisp. But Clojure-style multimethods are not "cute" or useless. They're just not a universal panacea. Very little is.

-----

1 point by almkglor 5885 days ago | link

> "cute"

For me, cute means something really really nice, not necessarily useless. Like cute mature women, for example. Or better: cute girls, with guns. LOL.

Method chaining may require us to rethink PG's type system, at least if we want to handle a drop-down to a more generic type (which arguably is the more interesting bit about chaining). It's reasonably easy to drop from the "argument 2 matched type T" to "argument 2 matched no types", but how about when we want to drop from "argument 2 matched derived type D" to "argument 2 matched base type B"?

Waa.

We would have to have an operator which determines if D is derived from some random type B, and forcibly standardizing on it. This is going to make my head explode.

-----

1 point by almkglor 5885 days ago | link

               ,(or default-method
                  `(err "no matching method found for multimethod:" ',name)))
Is this deliberate? It seems to me that the optional default-method argument is a default expression that will be evaluated, but without access to the arguments to the function.

-----

1 point by rntz 5885 days ago | link

I'm not sure exactly what you're getting at. Admittedly, what I did there is somewhat hackish (although my original method was worse). The default-method argument is just that: a default method which will be called (with the arguments to the multimethod) if no other method is found. But the expression that makes it up is only evaluated if necessary (lazily, in an 'or expression). So if it produces an error, then when that expression is evaluated, that is, if and only if we have failed to find a matching method, it produces an error; and since, in the macro itself, I know the name of the multimethod, I can substitute this into the default for 'default-method, and have nicer error messages.

-----

1 point by almkglor 5885 days ago | link

Ah sorry, misread the code, forgot the 'apply bit around the form.

-----