Hi.

On Thu, 08 Aug 2013 10:03:36 +0200, Luc Maisonobe wrote:
Le 08/08/2013 01:13, Gilles a écrit :
On Wed, 7 Aug 2013 18:04:39 -0400, Evan Ward wrote:
Hi,

As a grateful user of the library, I would prefer the Fluent API and immutability. I have implemented this pattern in some of my own projects
and it makes the API very easy to use.

Most of the time, when the interface is small, inheritance isn't really necessary and I just implement the withXxx methods directly. A one line 'return new ...' isn't hard to maintain with IDE support for renames and
add/remove parameters.

When inheritance is part of the API you can parametrize the base class
and add an abstract create method, like so:

abstract class Parent<T extends Parent> implements Interface{
    protected abstract T create(...);
    public T withXxx(x){
        return create(x, ...);
    }
}
class Child extends Parent<Child> {
    protected T create(...){
        return new Child(...);
    }
}

Did I understand correctly that the base class "Parent" is assumed to have complete knowledge of all its subclass "Child": the signature of
"create" must contain the arguments for fields declared in "Child"?

I don't think so.

In this way, it does not; what I inferred from the partial code above was
that there would only be _one_ "create" method. But:
1. All "create" methods must be overridden at the next level of the class
   hierarchy (this is the duplication I was referring to in the first
   post).
2. When a parameter is added somewhere in the hierarchy, all the classes
   below must be touched too.

And, we must note, that the duplication still does not ensure "real"
immutability unless all the passed arguments are themselves immutable.
But:
1. We do not have control over them; e.g. in the case of the optimizers
the "ConvergenceChecker" interface could possibly be implemented by a non-thread-safe class (I gave one example of such a thing a few weeks
   ago when a user wanted to track the optimizer's search path)
2. Some users _complained_ (among other things :-) that we should not
   force immutability of some input (model function and Jacobian IIRC)
   because in some use-cases, it would be unnecessarily costly.

Consequently, if (when creating a new instance) we assign a reference
passed to the fluent method, we cannot warrant thread-safety anymore;
which in turn poses the question of whether this false sense of security warrants the increased code duplication and complexity (compare your code
below with the same but without the constructors and "create" methods:
even a toy example is already cut by more than half).

Then if we really want to aim at thread-safety, I think that the approach of mandating a "copy()" interface to all participating classes, would be a serious contender to just making all fields "final". Let's also recall that immutability is not a goal but a means (the goal being thread-safety).

I still think that the three "tools" mentioned in the subject line do not
play along very well, unfortunately.
I was, and still am, a proponent of immutability but IMO this discussion
indicates that it should not be enforced at all cost. In particular, in
small and non-layered objects, it is easy to ensure thread-safety (through "final") but the objects that most benefit from a fluent API are big (with
many configuration combinations), and their primary usage does not
necessarily benefit from transparent instantiation.

Given all these considerations, in the first steps for moving some CM codes to fluent APIs, I would aim for simplicity and _less_ code (if just to be
able to make adjustments more quickly).
Then, from "simple" code, we can more easily experiment (and compare, with "diff") the merits and drawbacks of various routes towards thread-safety.

OK?


Gilles

The following code displays:
original: 3 1.0
changed : 2 1.0

This means that despite withI() was defined only in the base class and despite this base class doesn't know anything about the Derived class, the withI call was properly executed and the modified object was really
a Derived instance.

The drawback is that the user had to do the cast from BAse to Derived
when calling withI in the statement:

  Derived changed  = (Derived) original.withI(2);


I tried a few tricks with the templates to prevent this, but did not
succeed. I'm not sure it is a big problem, though.


interface IChanger<T extends IChanger<T>> {
    public T withI(int i);
}

interface DChanger<T extends DChanger<T>> {
    public T withD(double d);
}

class Base implements IChanger<Base> {
    int i;
    public Base(int i)         { this.i = i; }
    public Base create (int i) { return new Base(i); }
    public Base withI(int i)   { return create(i); }
    public int getI()          { return i; }
}

class Derived extends Base implements DChanger<Derived> {
    double d;
    public Derived(int i, double d)        { super(i); this.d = d; }
public Derived create(int i, double d) { return new Derived(i, d); } public Derived create(int i) { return new Derived(i, d); }
    public Derived withD(double d)         { return create(i, d); }
    public double getD()                   { return d; }
}

Derived original = new Derived(3, 1.0);
Derived changed  = (Derived) original.withI(2);
System.out.println("original: " + original.getI() + " " + original.getD()); System.out.println("changed : " + changed.getI() + " " + changed.getD());



best regards,
Luc
>
> [...]


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@commons.apache.org
For additional commands, e-mail: dev-h...@commons.apache.org

Reply via email to