On Mar 28, 6:30 pm, David Nolen <dnolen.li...@gmail.com> wrote:
> Having thought a little about multiple inheritance when implementing Spinoza
> I also ran into this problem. However at the time I wasn't hindered by
> multifn dispatch as much as the fact that parents cannot be ordered (because
> calling parents on a tag returns a set) as pointed out by Mark. I understand
> Rich's point that hierarchies probably shouldn't be directional, but for the
> sake of practicality why not allow the programmer to specify that a tag (or
> even the whole hierarchy) should in fact have its relationships be ordered?
> In general prefer-method seemed to me (I could be wrong) to be a last
> attempt solution- when there is just no other way to resolve an ambiguity,
> and this ambiguity is not of the hierarchy, but of multimethod dispatch. As
> pointed out by Konrad & Rich, you can work around the limitation of
> hierarchies right now with a lot of prefer-methods. But as Mikel points
> out, with current state of prefer-method you have to hunt through code to
> find out where these prefers were made and for what reason and they assume
> that you've already written your methods (this for me is the deal breaker).
>
> In anycase my general feeling (in agreement with Mark) is that the problem
> isn't with multifns but rather with how hierarchies are constructed.
>
> So the achilles heel of hierarchies seems to be that when you call parents
> (and related fns) on a tag you get a set - it has no order. For single
> inheritance hierarchies this is fine, but for multiple inheritance I think
> this becomes a problem a very fast. Working on Spinoza, I realized I would
> have to keep my own ordered list of parents in order to support left-right
> precedence, basically keeping an ordered copy of the whole hierarchy. Again
> prefer-method is only useful for fixing method-level ambiguities. There's
> no general way to say, type A always precedes type B without writing quite a
> bit of explicit code (or hiding the problem away with macros).
>
> Here's something that might kick of a dicussion to a proper solution (I am
> sure there are some gaps in my logic), why not allow something like the
> following?
>
> ;; allow control of how tags get introduced into the hierarchy
> (def h (make-hierarchy :parents-fn fn1 :ancestors-fn fn2 :descendants-fn
> fn3))
>
> ;; add a new parent insertion fn to a hierarchy
> (add-parents-fn h fn4) ;; -> returns a new hierarchy with the parents fn
> set, all parents sets are converted into vectors
>
> ;; add a new parent insertion fn to a hierarchy for a specific key, only the
> parents of ::my-tag are converted into a vector
> (add-parents-fn h fn4 ::my-tag)
>
> ;; a hierarchy fn might look something like the following
> (defn my-parents-fn [parents new-key]
> (conj parents new-key))
>
> If my thinking is correct this is pretty flexible. This doesn't break the
> meaning of hierarchy for any existing code. It doesn't change the signature
> of multimethods. Yet now a user can provide fns that sorts a hierarchy
> anyway they please, and it doesn't even have to be the entire hierarchy.
> This code will probably live in one spot where anyone could look to
> understand how the hierarchy is being constructed. All high-level
> precedence ambiguities can now be resolved by looking at the hierarchy.
> prefer-method does only what it was intended to do- resolve method level
> ambiguity.
You can build a working generic functions implementation for any
domain that obeys a protocol consisting of four functions:
1. fn [v & [context]] -> t
A type-returning function that returns a usable type-value for any
input in its intended domain.
2. fn [t1 t2 & [context]] -> Boolean
A type-ordering function that returns true if t1 is to
be considered "more specific" than t2. This function
must yield values that permit a stable, monotonic
sort of types in the domain.
3. fn [t & [context]] -> s
Where s is a sequence of type-values. A supertypes function that,
given a type, returns a sequence of all types that are to be
considered supertypes of it.
4. fn [inits & [context]] -> t
A type-creating function that returns new type-values that
function (1) can produce when applied to domain values,
that (2) can order, and that (3) can take as input to yield
a canonical list of supertypes.
The optional context argument is necessary because some domains are
restricted, and operate only with respect to some specific context. An
example is a domain defined in terms of one or more ad hoc
hierarchies.
If you can implement this protocol for a domain, then you can have
generic functions that are defined for that domain, and you can have
deterministic dispatch and next-method for values in that domain.
There's no obvious way to implement (2) for types defined using
Clojure's ad hoc hierarchies, because (parents t) returns a set, which
is unordered. That doesn't mean there's no way at all to implement
(2); it means that any working implementation of (2) imposes an
ordering on (parents t). Since more than one such ordering is
possible, more than one implementation of (2) is possible, and
different ones constitute different domains.
You can even write an implementation of (2) that asks the user how to
order unordered type values when the question needs to be resolved
before processing can continue. That's the solution used by the
existing MultiFn dispatch mechanism, and with this protocol, you can
have it if you want it.
You can define any number of generic function domains that operate
side-by-side. It would be prudent in that context to be clear about
the domain you're defining, and maybe implement the protocol's
functions in a way that clearly identifies inputs that aren't in the
domain (if any). For example, if your type-returning function relies
on metadata, then if it encounters a value that doesn't support the
IMeta interface, it should raise an exception that complains that it's
being applied to a value not in its domain.
You also need to be careful that your type-creating function creates
types that the other functions can operate on correctly.
Strictly speaking, function (4) doesn't have anything to do with
generic functions; you only need functions (1), (2), and (3) to
implement them. Function (4) is a convenience: a constructor for
values in the domain defined by (1), (2), and (3). For some domains,
(4) might just be identity applied over some set of existing values.
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"Clojure" group.
To post to this group, send email to clojure@googlegroups.com
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
-~----------~----~----~----~------~----~------~--~---