I think Tassilo's ideas about extenders deserve more discussion. But let me
continue the discussion with Armando for now.
Thank you for offering the Eclipse API example, which is very helpful. I
agree that Java's OO paradigm creates the need for abstract classes.
However, I would like to look further at what is going on in the Eclipse
example and consider if protocols in Clojure are the same or different.
To clarify the example: Eclipse allows the user to add the user's own
methods for certain kinds of event-handling. Because the language is Java,
these methods must be attached to a class. In order for Eclipse to specify
which methods it expects, Eclipse provides an interface that the user's
class must implement. For convenience, Eclipse also provides default
implementations of the required methods; these are provided in an abstract
class. When the user extends this abstract class, the user can elect to
override none, some, or all of the default methods.
I have a few observations.
1) In the example, it is certainly true that the user may implement none or
just some of the methods of the interface. However, when the user finally
passes an object instance to Eclipse, *all* of the methods of the interface
are in fact implemented by that instance. This is because the default
implementations in the abstract class will fill any holes the user leaves.
Therefore, I respectfully disagree that this is case of "partial
implementation." It may appear so to the user, but not to the language.
2) It is true that a Java abstract class can partially implement an
interface. However, an abstract class cannot be instantiated. It must be
extended first. When extending an abstract class, the user must implement
any methods that the abstract class has not implemented. No concrete class
"partially implements" an interface.
To me, the analogy to Java therefore supports the idea that the extends?
relationship ought to require a datatype/record to implement all of its
protocols' methods. A datatype/record is a piece of data, not a collection
of default methods; it is therefore like an object instance, not an
abstract class. Object instances must implement all of their interfaces'
methods.
As I said in my last post, it would seem strange for a language to provide
protocols (rather than just fast, un-grouped multimethods) if a piece of
data that "satisfies" a protocol does not necessarily implement even one of
the protocol's methods.
3) Armando's worry is valid that requiring implementation of protocol
methods would result in "proliferation of interfaces and protocols that
would prevent reuse; something like
ListWithoutModificationOrBulkOperations."
However, to me the beauty of protocols is that they can be highly granular.
They are not tied to a rigid inheritance paradigm. They support direct
implementation composition. If, per Armando's example, a protocol designer
felt the need to have a BasicList protocol, a ModifiableList protocol, and
a BulkOperations protocol, to be combined as necessary, that would be just
fine.
4) In /The Joy of Clojure/, a few of the examples involve extending a type
to implement just one of a protocol's multiple methods (pp. 193-195). The
authors consider this a valid use case. To leave the /JoC/ examples roughly
intact while giving protocols a bit more bite, let me throw out two ideas
for discussion.
Idea 1
======
Types may be extended to a strict subset of a protocol's methods, but
satisfies? and extends? will return FALSE, not true, in this case. That is:
=> (defprotocol Fixo
(fixo-push [fixo value])
(fixo-pop [fixo]))
Fixo
=> (extend-type clojure.lang.IPersistentVector
Fixo
(fixo-push [vector value]
(conj vector value)))
[succeeds]
=> (extends? Fixo clojure.lang.IPersistentVector)
FALSE, not true
Idea 2
======
Types may be extended to new protocols only if *all* of the protocol's
methods are implemented. However, types may be always extended tonew,
ungrouped *methods*. That is:
=> (defprotocol Fixo
(fixo-push [fixo value])
(fixo-pop [fixo]))
Fixo
=> (extend-type clojure.lang.IPersistentVector
Fixo
(fixo-push [vector value]
(conj vector value)))
[should FAIL - protocol name not allowed here unless all methods are
implemented]
=> (extend-type clojure.lang.IPersistentVector
nil
(fixo-push [vector value]
(conj vector value)))
[should SUCCEED - fixo-push is like a fast multimethod, without a named
protocol]
=> (extends? Fixo clojure.lang.IPersistentVector)
FALSE, not true
I invite discussion and criticism.
All the best, Garth
On Thursday, March 8, 2012 11:24:42 AM UTC-5, Armando Blancas wrote:
>
> (Don't know why I can only respond to the first message.)
>
> I come across partial implementation all the time, and with proxy, too. In
> Eclipse this is so common that this is typical:
>
> "This adapter class provides default implementations for the methods
> described by the SelectionListener interface.
> Classes that wish to deal with SelectionEvents can extend this class and
> override only the methods which they are interested in."
>
> You may have seen the idea of optional operation in java.util.List and how
> it's handled in places like AbstractList with default implementations.
> Clojure itself has partial implementations of List in PersistentList and
> Collection in PersistentQueue. I suppose the alternative would be a
> proliferation of interfaces and protocols that would prevent reuse;
> something like ListWithoutModificationOrBulkOperations could have a full
> implementation but little use.
>
> I can see the value of your view of protocol as spec, but maybe for
> domain-realted concepts and less for plumbing.
>
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en