Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-generate a Step Builder? #33

Open
clinuxrulz opened this issue May 11, 2016 · 13 comments
Open

Auto-generate a Step Builder? #33

clinuxrulz opened this issue May 11, 2016 · 13 comments

Comments

@clinuxrulz
Copy link

Can be handy if a data constructor has a lot of fields, and you want to insure the user of your data structure supplies the fields in the correct order. (avoid mixing up fields with the same type, like width and height)

@clinuxrulz
Copy link
Author

Sort of trying to achieve named-parameters for data constructors.

@jbgi
Copy link
Member

jbgi commented May 11, 2016

I would say that using newtype is a better solution to avoid mixing parameters, but it is true that we won't have cheap newtype until jdk10... on the other hand the builder pattern is also a bit heavy...
But why not!

@clinuxrulz
Copy link
Author

clinuxrulz commented May 11, 2016

Some purist will be unhappy with this comment.

But maybe combining the popsicle pattern with the step builder pattern to insure 1 memory allocation per object.

E.g.

@Data
public abstract class Shape {
    public interface Cases<R> {
        R rectangle(double width, double height);
        R circle(double x, double y, double radius);
    }
}

Produce:

public interface RectangleBuilderSetWidthStep {
    RectangleBuilderSetHeightStep width(double width);
}

public interface RectangleBuilderSetHeightStep {
    Shape height(double height);
}

class Rectangle extends Shape implements RectangleBuilderSetWidthStep, RectangleBuilderSetHeightStep {
    double width;
    double height;
    boolean __frozen = false;

    public Rectangle width(double width) {
        if (__frozen) {
            throw new IllegalStateException("Shape is already frozen!");
        }
        this.width = width;
        return this;
    }

    public Rectangle height(double height) {
        if (__frozen) {
            throw new IllegalStateException("Shape is already frozen!");
        }
        this.height = height;
        __frozen = true;
        return this;
    }
}

And as long as the end-user can not get a hold of the Rectangle type, then all is well.

Maybe something like the following can work:

Shape rect = Shapes.rectangleWith().width(100.0).height(100.0);

And the user can still do it the other way too.

Shape rect = Shapes.rectangle(100.0, 100.0);

@talios
Copy link
Contributor

talios commented May 12, 2016

@clinuxrulz The purest in me says "oh hai, you want curried constructors?" of some description.

public static Function<Double, Shape> rectangle(double width) {
  return height -> Shapes.rectangle(width, height);
}

public static Function<Double, Function<Double, Shape>> cube(double width) {
  return height -> depth -> Shapes.rectangle(width, height, depth);
}

and called with:

Shape rect = Shapes.rectangle(100.0).apply(100.0);
Shape cube = Shapes.cube(100.0).apply(100.0).apply(100);

With any more parameters, that return declaration would get mighty nested, but I guess you could also generate something like:

public static class CubeBuilder extends Function<Double, Function<Double, Shape>>  {
}

public static CubeBuilder cube(double width) {
  return height -> depth -> Shapes.rectangle(width, height, depth);
}

which just reshapes things.

@clinuxrulz
Copy link
Author

clinuxrulz commented May 12, 2016

@talios That looks ideal. However does javac optimize curried lambda application?

My use cases involve over 30 arguments to a type constructor, which would equate to 31 memory allocations per object (if javac does not optimize curry)

Maybe its just premature optimization on my part.

Edit:
Ignoring poor coding conventions, this is what I do currently to obtain named-parameters.
https://gist.github.com/clinuxrulz/2ca616cdcf158b7920d9a1b0d81d65c3

@clinuxrulz
Copy link
Author

On a side note. FunctionalJava's $ class seems to serve the purpose of a new-type wrapper.

class Width {}
class Length {}

Shapes.rectangle($.<Width,Double>__(100.0), $.<Height,Double>__(100.0));

@talios
Copy link
Contributor

talios commented May 14, 2016

@clinuxrulz AFAIK the first immediately returned lambda will be compiled as a static/singleton since it doesn't close over anything, but since the subsequent instances close over the earlier result, each invocation will create a new instance of a Function wrapper instance.

30 arguments.... ug - yeh, you probably wouldn't want to curry that - that would be 30 function instantiations and invocations and gcs. I wouldn't exactly want to do that in a tight loop :)

@h908714124
Copy link
Contributor

h908714124 commented Aug 10, 2016

One advantage of builder pattern is descriptive method names, like so:

Shape cube = Shapes.cube().width(100.0).height(80.0).depth(90.0);

Easier to read than if with(), height() and depth() were all just apply().

@jbgi
Copy link
Member

jbgi commented Oct 4, 2016

For now zerobuilder is solution.
But ideally, zerobuilder could also work as a derive4J extension, so that the builder factories are generated in the same file as the one produced by derive4j.
Will look into how we can support this.

@h908714124
Copy link
Contributor

@jbgi I'm thinking of exposing zerobuilder's core functionality as a library (more or less what Generator.generate does, see http://bit.ly/2dSjvav)

@jbgi
Copy link
Member

jbgi commented Oct 5, 2016

@h908714124 yeah that would be great!
Something like
MethodElement => Either<Error, (List<TypeSpec>, List<MethodSpec>, List<FieldSpec>)>
would be perfect.

@h908714124
Copy link
Contributor

h908714124 commented Oct 6, 2016

I hope to release a separate artifact zerobuilder-api in a few days; this will be a non-shaded, regular jar, with a dependency on guava (because zerobuilder is supposed to run on java 7).

@h908714124
Copy link
Contributor

Api is already released. See https://github.com/h908714124/zerobuilder/tree/master/api

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants