ceylon / ceylon-spec

DEPRECATED
Apache License 2.0
108 stars 34 forks source link

Supports syntax for method chaining #943

Open loganmzz opened 10 years ago

loganmzz commented 10 years ago

Fluent interfaces use a lot the method chaining. But when using inheritance its become hardy due to the type system. You must explicitly refines all the methods to restrict the return type to the current child class.

A very good features will be to implement a "this" return type. Such the compilor/type system could assume methods return the current declared type.

I suggest using "this" keyword such as "void" is used when nothing is supposed to be returned. These methods should use only "empty return" (again, such as "void" ones).

Here is a class declaration example:

class MethodChainingSample() {
   shared this first(String? str) { 
     if (str.empty) return;
     print(str);
  }
}
gavinking commented 10 years ago

I don't dislike the concept, but note that so-called "fluent" interfaces aren't idiomatic Ceylon. We use named argument invocations instead.

loganmzz commented 10 years ago

You suggest transform:

Query query = new QueryBuilder().select("x", "y").from("Coordinates").where("...");

to:

Query query = queryBuilder { select={"x","y"}; from={"Coordinates"}; where={"..."}; };

?

I'm not sure this construct can handle the cases where:

Because this an abstract discussion, I don't know if my phrases apply. Here is "stupid" sample:

new StupidApi().setName("Foo").printName().setName("Bar").printName();

Moreover, I have some doubt about the ease to write (and re-read/maintain) some kind of APIs using named arguments. As I have already said I haven't experienced this. As Java developer I think a lot like Java-way more than Ceylon-way. It's easier to learn than unlearn ;) I suppose "named-argument" construct is more usual in languages such as JavaScript ?

FroMage commented 10 years ago

Dart's .. operator is much better than some syntax which forces you to design APIs as fluent. That operator applies the right-hand side to the previous left-hand expression.

loganmzz commented 10 years ago

It's like a VB's with operator ? However fluent APIs can also return different kind of "handler" during the chain. Especially to structurally restrict access to some methods. Lombok generated-builders are good example. A SQL query API can also uses this logic to "fix" syntax. A select is always followed by a from which is followed optionnally by where, order by and group by. Whereas having only appears after a group by:

interface ExpressionList {
  this add(String expression);
}
interface ToQuery extends ExpressionList {
  Query toQuery();
}
interface BeforeOrderBy extends ToQuery {
  OrderBy orderBy();
}
interface BeforeGroupBy extends BeforeOrderBy {
  GroupBy groupBy();
}
interface Select extends ExpressionList {
  From from();
}
interface From extends BeforeGroupBy {
  Where where();
}
interface Where extends BeforeGroupBy {}
interface GroupBy extends BeforeOrderBy {
  Having having();
}
interface Having extends BeforeOrderBy {}
interface OrderBy extends ToQuery {}

QueryBuilder
    .select()
        .add("x")
        .add("y")
    .from()
        .add("Coordinates")
    .where()
        .add("x < 10")
        .add("y != 20")
    .orderBy()
        .add("distance(x,y,0,0)")
    .toQuery();
simonthum commented 10 years ago

I'm using self types to achieve "this type":

shared interface TraitB<Representation> given Representation satisfies TraitB<Representation> {
    shared formal Representation modfiyB();
}

There are some rough edges but I believe that's another bug.

PhiLhoSoft commented 10 years ago

I involuntarily +1ed this issue with the following thread: https://groups.google.com/forum/#!topic/ceylon-users/Eg6hi1-1XRI I link to it here, as I tried to give more use cases for this feature.