On Mon, Nov 17, 2014 at 1:12 PM, Marvin Humphrey <[email protected]> wrote:
> * Clownfish abstract classes are mapped to Go interfaces.
> * Clownfish concrete classes are mapped to Go struct pointers.
> * Adapting Clownfish's subtyping for Go is hard, because Go does not
> support inheritance. This is especially a problem with functionality
> which can only be unlocked by overriding methods on concrete classes
> like QueryParser, IndexManager and Schema.
To illustrate the challenge posed by Go's lack of support for inheritance...
If we wrap Query in a Go "struct", then it would not be valid to use a
*TermQuery Go struct pointer in a context where a *Query Go struct pointer is
called for -- e.g. passing a *TermQuery to a method which expects a *Query.
However, if we make Query a Go "interface", then it *would* be valid to use a
*TermQuery in a Query context.
For abstract classes like Query or Analyzer, making them Go interfaces works
fine. However, there are unresolved difficulties for concrete classes which
we expect people to subclass.
One such class is IndexManager. In order to control how segments get recycled
in Lucy, you need to subclass IndexManager and override Recycle() (as
described in Lucy::Docs::Cookbook::FastUpdates). However, as things are now,
that won't be possible from Lucy's Go bindings:
http://play.golang.org/p/4GLVD1Gg6N
prog.go:31: cannot use manager (type *FastAddManager) as type
*IndexManager in argument to NewIndexer [process exited with non-zero
status]
Change IndexManager to an interface and the code at least compiles:
http://play.golang.org/p/ues1hRQp8y
https://paste.apache.org/iwCi (diff)
A more elegant solution, though, is to refactor IndexManager to avoid
inheritance and rely only on interface-based polymorphism and composition.
For example, IndexManager could call Recycle() on an object which
implements an "IndexDeletionPolicy" interface. Once IndexManager has-a
IndexDeletionPolicy, you change IndexManager's behavior by supplying an
IndexDeletionPolicy object which does what you want -- eliminating the need to
subclass IndexManager.
The compositional approach would also be perfectly natural and idiomatic for
other languages.
# Python:
manager = lucy.IndexManager.new()
manager.set_deletion_policy(MyDeletionPolicy.new())
indexer = lucy.Indexer.new(index="path/to/index", manager=manager)
# Java
IndexManager manager = new IndexManager();
manager.setDeletionPolicy(new MyDeletionPolicy());
Indexer indexer = new Indexer("path/to/index", manager);
# Perl
my $manager = Lucy::Index::IndexManager->new;
$manager->set_deletion_policy(MyDeletionPolicy->new);
my $indexer = Lucy::Index::Indexer->new(
index => 'path/to/index',
manager => $manager,
);
One thing I've come to appreciate while working on the Go bindings is that the
composition model is more universal -- besides its popularity among elite
hackers, it's more compatible with more object models than inheritance. It's
made me contemplate whether composition might be a more suitable model on
which to base a project like Clownfish.
Marvin Humphrey