Giter VIP home page Giter VIP logo

derive4j / derive4j Goto Github PK

View Code? Open in Web Editor NEW
560.0 30.0 50.0 1.02 MB

Java 8 annotation processor and framework for deriving algebraic data types constructors, pattern-matching, folds, optics and typeclasses.

Java 100.00%
algebraic-data-types optics functional-programming algebra visitor-pattern annotation-processor java-8 derive4j catamorphisms visitor laziness java sum-types tagged-unions discriminated-unions exhaustiveness-checking fold

derive4j's Issues

Auto Type unification might not always be wanted.

I've noticed when having something like.

 . . .
    interface Cases<R,A> {
        R DoSomething(String x, F<Integer,A> k);
    }
 . . .

That it generates a Type constructor DoSomething(String x) without the k. It uses the Identity function for us.

However this might not always be the only function we would want to use.

For Example. Defining an F-algebra for the use in a free monad. Then defining a Functor instance for the F-algrebra becomes impossible, because we do not have DoSomething(String x, F<Integer,A> k) type constructor.

Maybe auto-generate both type constructors?

More descriptive name than `@Data`

What a nice processor!

I would ask you use a more descriptive name than @Data for annotation:

  1. "Data" is such a generic word, difficult to scan code with @Data and grok something interesting is happening.
  2. "Data" may clash with user code or other packages which also have a Data class, especially after imports.
  3. In particular, Lombok is probably the most popular 3rd-party annotation processor, and it, too, has a Data class, which does something rather different.
  4. This package is version 0.9.1 (as of when I wrote this), so you should have some freedom in refactoring before hitting the magic 1.0.0 and your public API is frozen (assuming semantic versioning).

You have many fine alternative choices for naming the annotation. First to my mind is @ADT. A web search on "ADT" turns up near the top the right Wikipedia page to understand some background on this library's goals.

Cheers,
--binkley

Eclipse (ecj) support

When compiling with ecj, total pattern matching syntax does not follow definition order in source file.
Also some generated code does not compile due poor type inference.

Auto-generate a Step Builder?

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)

Add support for varargs in datatype definitions

Would be it possible to add support for varargs:

interface Cases<R> {
  R develop(Job job, Job... dependencies);
  R review(Job job, Job... dependencies);
}

generators constructors like:

public static JobType develop(Job job, Job[] dependencies) {
...

It would be handy if the constructor method matched the vararg definition.

GADTs : provide an 'Id' functional interface for type association (unification ?)

Jamais deux sans trois,

Re-reading my example :

abstract class Seminar<T> {
    Seminar() {}
    interface Cases<R, T> {
        R Init(SData.Core core, F<Init, T> __);
        R Temp(SData.Core core, SData.Add1 add1, F<Temp, T> __);
    }

  public static final class Init { private Init() {} }
  public static final class Temp { private Temp() {} }
}

I realize that the free F type (any functional interface would do, right ?) used to associate the T type parameter with a concrete (Init or Temp) type could be profitably (for the newcomer) replaced by a fixed Id type provided by derive4j.

What do you think ? Wouldn't it make the intent of that mysterious additional parameter clearer to the boeotian (that I once was) ?

GADT : total getters for specific types

Hi Jean-Baptiste,

Let's say we have the following GADT :

@data
interface Seminar<T> {
  interface Cases<R, T> {
    R Init(SData.Core core, TypeEq<Init, T> __);
    R Temp(SData.Core core, SData.Add1 add1, TypeEq<Temp, T> __);
  }
  <R> R match(Cases<R, T> cases);

  enum Init {}
  enum Temp {}
}

then the following function

// getter not returning an Option<SData.Add1>
public static SData.Add1 getAdd1(Seminar<Temp> temp) {
  return Seminars.getAdd1(temp).some(); // cannot fail since we have a specific Seminar<Temp>
}

has to be written by hand, as you can see.

Would it be possible, in the case of GADTs, to generate total getters for specific cases ?

Ability to add annotations to generated classes

It would often be nice to have sensible ToString implementations on the generated classes. One way of providing toString() is to use lombok's @tostring annotation on the generated class. This could be enabled by being able to specify which annotations should apply to the generated classes.

Possible ExceptionInInitializerError with static fields

Given:

@Data
abstract class Nat {
  interface Cases<R> {
    R Zero();
    R Suc(Nat nat);
  }
  public abstract <R> R match(Cases<R> cases);

  private static Function<Nat, Integer> eval =
     Nats.cases().Zero( ()        -> 0)
        .Suc(nat  -> eval(nat) + 1);

  public static Integer eval(Nat nat) {
    return eval.apply(nat);
  }
}

When executing

public class Main {
  public static void main(String[] args) {
    Nat nat = Suc(Suc(Suc(Suc(Zero()))));
    System.out.println(eval(nat));
  }
}

You get

Exception in thread "main" java.lang.ExceptionInInitializerError
    at org.jomaveger.lambda.adt.Nats.<clinit>(Nats.java:10)
    at org.jomaveger.lambda.main.Main.main(Main.java:15)
Caused by: java.lang.NullPointerException
    at org.jomaveger.lambda.adt.Nat.<clinit>(Nat.java:21)
    ... 2 more

Due to:

  • the Zero constructor being eagerly cached in a static final field in the generated class.
  • a static field in Nat make use (indirectly) of another static field of the generated class.
    This cylcle in the initialization dependencies trigger a nullpointer exception.

To avoid this, the Zero constructor must not be eagerly initialized.

Existential (phantom ?) type parameters on constructors

Hey, me again,

the following haskell idiom seems currently not expressible using derive4j :

data Stream a = forall s. Stream (s -> Step a s) s

Would it be possible to allow that kind of construct :

@Derive4J(flavour = Flavour.FJ)
abstract class Stream<A> {
  interface Cases<R, A, S> {
    R Stream(Unlifted<S> s, F<S, Step<A, S>> stepper, S init);
  }
  abstract <R, S> R match(Cases<R, A, S> cases);
}

or better

@Derive4J(flavour = Flavour.FJ)
abstract class Stream<A> {
  interface Cases<R, A> {
    <S> R Stream(Unlifted<S> s, F<S, Step<A, S>> stepper, S init);
  }
  abstract <R> R match(Cases<R, A> cases);
}

What do you think ?

Smarter default name for generated classes.

Currently, by default, the generated class is the name of the class + 's'.
This issue is about making the default name to better follow English grammar for plural:

  • if the class ends with 'y' -> the 'y' is substituted with 'ies' for the generated class
  • if the class ends with s, x, ch, sh, or z then we add 'es' for the generated class.

There is also lots of other special cases but not sure they are worth it.

Additionnal facilities for partial matching

today there is only

Function<ADT, R> otherwise(Supplier<R> expression)

I think it should be renamed into

Function<ADT, R> otherwiseEval(Supplier<R> expression)

And then we can add:

Function<ADT, R> otherwise(R value)
Function<ADT, Option<R>> otherwiseNone()
Function<ADT, Either<L, R>> <L> otherwiseLeft(L leftValue)
Function<ADT, Validation<E, R>> <E> otherwiseFail(E error)

What others think about that feature? (are naming good?)

Add support for a la carte derivation

Last week I was experimenting with reworking some existing $work code ( using jADT ) to use derive4j and whilst it worked well, and the code translated fairly seamlessly, I started noticed weird parse/completion errors in IntelliJ.

It seems IntelliJ by default stops parsing files if there over 2500 lines long, and the derive4j generated source file in question was something like 60,000 lines.

In this particular instance, once an object has been created, we have no desired to have mutations, so don't really need any of the set/mod support functions to get generated ( also saving a lot of lambdas being compiled ).

As a simple solution ( other than increasing the rather hidden setting for IntelliJ completion ) was to have a new Flavour.None for derive4j that prevented the generation of get/set/mod methods, leaving you only with the constructors.

Would something like this be useful to anyone else? Or would a more generic (disable set, disable mod, disable get) generation control across flavours be more useful?

Enum like facilities

For a given ADT, EnumAdt, where all constructors take 0 parameters, then derive4j should derive:

public static List<EnumAdt> values();
public static Option<EnumAdt> forConstructor(String);
public static String constructorName(EnumAdt);
public static int constructorOrdinal(EnumAdt);

The last two could be generated for all ADT (can be useful for serialization).

Better naming suggestions are welcomed!

Make the @Data annotation available for package (package-info.java)

Components of the annotation would then be overridable:
package and class annotated deeper in the package hierarchy override the configuration found in annotated packages closer to the root.

This will alleviate the need to repeat the same @Data configuration over and over on each data type in a given project.
Also the name of the generated class will support template, ie something like @Derive(inClass="{ClassName}Impl").

Generation fail if a same type variable is used multiple time in a given field.

The following:

@Data(value = @Derive(inClass = "Draw2DFImpl", withVisibility = Visibility.Package), flavour = Flavour.FJ)
public abstract class Draw2DF<M,A> implements __2<Draw2DF.Mu,M,A> {
    public static class Mu {}

    public static <M,A> Draw2DF<M,A> narrow(__<__<Mu,M>,A> a) {
        return (Draw2DF<M,A>)a;
    }

    public interface Cases<R,M,A> {
        R GetTextSize(String text, F<V2<Double>,A> textSizeK);
        R GetTextXWrappedHeight(String text, Double width, F<Double,A> textHeightK);
        R WithScale(Double scale, FreeT<__<Mu,M>,M,A> drawing);
        R DrawTextXWrapped(String text, Double width, A next);
        R DrawText(Vector2D pos, String text, Double alignmentX, Double alignmentY, A next);
        R Draw(Shape2D shape, A next);
        R Fill(Shape2D shape, A next);
        R GetImageSize(Draw2D2.ImageSource imgSrc, F<V2<Double>,A> imageSizeK);
        R DrawImage(Draw2D2.ImageSource imgSrc, Vector2D pos, Double alignmentX, Double alignmentY, A next);
        R DrawImageScaled(Draw2D2.ImageSource imgSrc, Vector2D pos, Double width, Double height, Double alignmentX, Double alignmentY, A next);
        R GetCurrentFont(F<Draw2D2.Font,A> fontK);
        R WithFont(Draw2D2.Font font, FreeT<__<Mu,M>,M,A> drawing);
        R UseColour(Draw2D.Colour colour, FreeT<__<Mu,M>,M,A> drawing);
        R InSpace(Axes2D axes, FreeT<__<Mu,M>,M,A> drawing);
    }

    public abstract <R> R match(Cases<R,M,A> cases);
}

Has the error: Error:java: com.sm.fp.graphics.Draw2DF: tag M cannot be used for both 'M' and 'M_'

But when wrapped into a new type (Drawing), the following this works fine:

@Data(value = @Derive(inClass = "Draw2DFImpl", withVisibility = Visibility.Package), flavour = Flavour.FJ)
public abstract class Draw2DF<M,A> implements __2<Draw2DF.Mu,M,A> {
    public static class Mu {}

    public static <M,A> Draw2DF<M,A> narrow(__<__<Mu,M>,A> a) {
        return (Draw2DF<M,A>)a;
    }

    public interface Cases<R,M,A> {
        R GetTextSize(String text, F<V2<Double>,A> textSizeK);
        R GetTextXWrappedHeight(String text, Double width, F<Double,A> textHeightK);
        R WithScale(Double scale, Drawing<M,A> drawing);
        R DrawTextXWrapped(String text, Double width, A next);
        R DrawText(Vector2D pos, String text, Double alignmentX, Double alignmentY, A next);
        R Draw(Shape2D shape, A next);
        R Fill(Shape2D shape, A next);
        R GetImageSize(Draw2D2.ImageSource imgSrc, F<V2<Double>,A> imageSizeK);
        R DrawImage(Draw2D2.ImageSource imgSrc, Vector2D pos, Double alignmentX, Double alignmentY, A next);
        R DrawImageScaled(Draw2D2.ImageSource imgSrc, Vector2D pos, Double width, Double height, Double alignmentX, Double alignmentY, A next);
        R GetCurrentFont(F<Draw2D2.Font,A> fontK);
        R WithFont(Draw2D2.Font font, Drawing<M,A> drawing);
        R UseColour(Draw2D.Colour colour, Drawing<M,A> drawing);
        R InSpace(Axes2D axes, Drawing<M,A> drawing);
    }

    public abstract <R> R match(Cases<R,M,A> cases);

    public static class Drawing<M,A> {
        private final FreeT<__<Mu,M>,M,A> toFreeT;

        private Drawing(FreeT<__<Mu,M>,M,A> toFreeT) {
            this.toFreeT = toFreeT;
        }

        public static <M,A> Drawing<M,A> drawing(FreeT<__<Mu,M>,M,A> toFreeT) {
            return new Drawing<>(toFreeT);
        }
    }
}

Potential identifers clash in generated code

There is some possibilities that the generated classes does not compile due to clash of identifers (eg. identifier originating from user code clash with hard-coded identifier in generated code).
This should not happen in most cases, but we should ensure hygienism of all generated code.
Will be facilitated by square/javapoet#338
Should go hand in hand with #2.

Alternative lazy value implementations

Additionally from the current synchronized-based implementation, two alternative implementations should be available and configurable via annotation:

  • one based on AtomicReference<Adt>
  • one based on AtomicReference<WeakReference<Adt>>

which allow better throughput than the synchronized based implementation at the price of possible concurrent evaluation.

Readme.md code typo

First code showcase:

public abstract <R> R match(Cases<X> cases);

Should probably be Cases<R>

Provide a means for supplying a validator

I was thinking it would be handy to have some means of providing a validator over the ADT values, I often use this feature of Immutables and keep thinking it would be useful here.

Currently, derive4j checks for nullness in its created instances, but it would be nice if we could define something like:

protected|public void validate() {
  this.match(....)
}

or

protected|public static Predicate<MyAdtType> validator = it -> ....;

which if existed, gets called before returning the constructed value.

Thoughts?

Avoid overloading in pattern matching synthax.

Since the time I closed #8, I encountered quite a few place where the overload was ambiguous (at least for javac) and had to use explicit type annotation to fix them. This was annoying and I'm thinking of avoiding overloading for the next release.

This means adding the suffix _ to the overload that take a plain (constant) value as argument.

This suffix is consistent with the syntax in haskell/scala where _ is a place holder for an ignored value.

Support for optional nullchecks in constructors

Although not necessary if you never use null (compiler plugin, anyone?), a mechanism ensuring that null never slip into a data-structure is expected by some user.
Null checks in generated code will be triggered by:
@Data(arguments = checkedNotNull)

[Q] Method must have one, and only one, type variable

Consider the following example:

@Data(flavour = Flavour.JDK)
public abstract class MyMap<A, B> {

  public interface MyMapVisitor<A, B> {
    MyMap<A, B> EmptyMap();
    MyMap<A, B> Insert(Pair<A, B> argPair, MyMap<A, B> argMap);
  }

  public abstract MyMap<A, B> match(MyMapVisitor<A, B> visitor);

}

and I get the following on mvn clean compile:

Method must have one, and only one, type variable (without bounds) 
that should also be the method return type.

which is from AdtParser#144.

What am I doing wrong?

Manage annotated inner classes

Given these classes :

@Data(flavour = Flavour.FJ)
public abstract class Product<T> {
  // all the stuff needed...

  // ...and then
  @Data(flavour = Flavour.FJ)
  public abstract class Seminar<T> {
     // all the stuff needed...
  } // end of Seminar

} // end of Product

only the Products class gets generated. It would be nice if annotated inner classes could be parsed and get their utilities generated just as top level ones.

Allow @Data on enums

It would be nice to be able to pattern match on enum. If derive4j detect that the annotated class is an enum then it should skip constructors generation.

Auto-generate Jackson (de)serializers

It would be nice to automatically generate Jackson serialization and deserialization code for Derive4J defined ADTs. The encoding seems fairly straightforward: for any subclass, serialize its attributes int a JSON object and additionally add a "type" field that is a string encoding of the variant name (i.e. the tag).

There are probably some corner cases, like if a subclass has an attribute named "type". So there may need to be an option to define the name of the field that holds the tag. The default could be "type".

This of course should be optional (opt-in), so users who don't want this code generated are not affected.

Add support for JavaSlang 2.0 as a Flavour

After playing with Derive4J for even just an evening, I think having support for JavaSlang from @danieldietrich would be a great addition once the 2.0 release it made.

This would be a natural fit alongside the other flavours - reusing JavaSlangs Option and Function2. Adding generators for tryEmailAddress along side getEmailAddress when using JavaSlang flavour may also be a potentially nice addition.

Unable to use primitives in values

I changed one of my objects to include a boolean and got this:

[INFO] Compiling 9 source files to /Users/amrk/IdeaProjects/upstream/halbuilder/halbuilder-core/target/classes
java.lang.IllegalArgumentException: boolean
    at com.sun.tools.javac.model.JavacTypes.getDeclaredType0(JavacTypes.java:252)
    at com.sun.tools.javac.model.JavacTypes.getDeclaredType(JavacTypes.java:222)
    at org.derive4j.processor.DeriveUtilsImpl.resolve(DeriveUtilsImpl.java:137)
    at org.derive4j.processor.derivator.GettersDerivator.visitorDispatchLensGetterImpl(GettersDerivator.java:197)
    at org.derive4j.processor.derivator.GettersDerivator.lambda$generateLensGetter$10(GettersDerivator.java:173)

I guess derive4j doesn't support primitives? Should it? Or should gracefully fail, I switched to Boolean for now anyway.

Add intermediate visibility to hide constructors and setters

You need smart constructors for your data type when the constraints of your model cannot be made practical by the type system. Eg. you need to validate at runtime the input needed to construct the data type with constructors of the form input -> Option ADT instead of normal input -> ADT constructors generated by derive4J.
If that happens then you cannot expose constructors and setters generated by Derive4J (all data constructions must go through the smart constructors).

To support this idiom, we need a Visbility.Smart (better name?) that will only expose getters and pattern matching as public (rather, 'same' visibiliy) and generate constructors and setters method with visbility 'package'.

Additional "caseOf" pattern matching synthax

This idea is to support the following pattern matching scheme:

Request r = ...
int bodySize = Requests.caseOf(r)
      .GET(path          -> 0)
      .DELETE(path       -> 0)
      .PUT((path, body)  -> body.length())
      .POST((path, body) -> body.length())

Question: please clarify equals/hashCode/toString philosophy as stated in README

The README says:

Derive4J philosophy is to be as safe and consistent as possible. That is why Object.{equals, hashCode, toString} are not implemented by generated classes by default. Nonetheless, as a concession to legacy.

If you have a minute, can you please clarify what this means? Specifically, why is not having these methods "safe and consistent".

Thank you!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.