Git Product home page Git Product logo

soabase-halva's Introduction

Halva

Welcome to Halva - Idiomatic Scala... in Java!

Build Status Maven Central

Halva's goal is to bring as many features from Scala to Java as is possible without byte code generation or magic. Using the features in Java should be as close as possibile to how the features are used in Scala.

I wanted to see how close to Scala I could get in Java. Not only close in functionality, but close in syntax as well. No magic, no byte code generation, nothing (too) tricky or magical. Just pure, standard Java 8. Halva is the result. The only reflection is for Google Guice's TypeLiteral class. The only "special" stuff is an annotation processor to generate case classes, type aliases and implicits. There are no dependencies whatsoever (Google Guice's TypeLiteral and JavaPoet are shaded into the code).

There are many 3rd party libraries that add support to Java in some form or another for some of Scala's features. However, most of these libraries are large and use their own DSLs or syntax. I want, where possible, to take unmodified Scala (adding semicolons!!) and get it to work in Java. Of course, this isn't literally possible. But, I began to wonder how close I could actually get. Some features are pretty trivial: some simple "sugaring" that wouldn't be hard to add. Other features, like Tuples, could be supported just as well in Java as in Scala. There are, however, a few features that are complicated or seemingly impossible in Java: Case Classes, Pattern Matching/Extractors/Partials, Comprehensions, and Implicits. There's no way to get total compatibility with Scala. But, could I get 50%? 70%? 80%? I searched the net for what people think are the killer features of Scala and attempted to implement them in pure Java. Project Halva is the result.

Full Featured Example

To see the results of what Halva can do, please look at the Simple Interpreter Example.

Features

Example Generated Files

Many example generated files are shown here.

Using Halva

Halva is available from Maven Central. Use your favorite build tool and specify:

GroupId ArtifactId Description
io.soabase.halva halva-processor Contains the javac processor for @CaseClass, @CaseObject, @TypeAlias, @ImplicitClass, and @TypeContainer
io.soabase.halva halva All the runtime code for Halva: matchers, comprehensions, etc.

FAQs

See miscellaneous info and Frequently Asked Questions

Special Thanks

Special thanks to @Alex-At-Home for his help and great ideas.

soabase-halva's People

Contributors

azell avatar randgalt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

soabase-halva's Issues

MonadicFor annotation can generate NPE

(I think if the Monadic class is itself generic)

Example:

https://github.com/Alex-At-Home/halva-scalaz-examples/blob/master/src/main/java/person/alexp/halva/examples/ReaderOptionalForFactory.java

Try to build and it should fail during annotation with an NPE

A previous commit (Alex-At-Home/halva-scalaz-examples@ca2f848) avoids the generic type in the ReaderOptionalForFactory class (which might also cause errors downstream?), leaving a wildcard in the nested generic - this gives the same error as the non-wildcard version (and also separately involves causes type hell in Java, hence the late hour!)

Add support for typed exception handling in suppliers to improve forComp useability

One really annoying thing in J8 is that there's no way of passing up typed exceptions, so if you have a long pipeline of lambdas calling functions that can throw them, you end up having to wrap the nice 1-liners in horrible try/catch routines.

I wrote a bunch of simple utilities to convert lambda to untyped exceptions here (eg): https://github.com/Alex-At-Home/Aleph2/blob/master/aleph2_data_model/src/com/ikanow/aleph2/data_model/utils/Lambdas.java#L64

So eg you'd do forComp(a, Utils.wrap(() -> FileUtils.openFile(...)))

One thing you could do in Halva specifically to help with for comprehensions is provide a similar util throwing an (untyped) LambdaException, and then catch those in the forComp code and have a Optional<LambdaException> For.pipelineError / or make it throw err.getCause() / whatever a sensible response to the error would be? (Could specify the policy with like forComp(..).ignoreErrors().etc or forComp(..).throwOnError().etc)

(In the general "monadic for" case you could also specify what to do with an error, eg make Optional become empty, switch a Validation to fail)

Support generated values inside of generated classes

E.g. this is not currently possible:

@CaseClass interface Case1_ {String name();}

@CaseClass interface Case2_{Case1 case1();}

You'd have to specify "Case1_" in Case2 and then it wouldn't be the right type, etc.

Support container types for concrete classes

(Fine to enforce that they have a no-arg constructor if needed)

The use case is that a typical requirement for a type container is enforcing something close to "locality" for the aliases/case classes to the package being used, and forcing the class to be an interface is too restrictive (I think! even with defaults)

So eg

import path.UsingThisLikeNamespaceTypes.Stack;

@TypeContainer(suffix="Types", "unsuffix"="")
class UsingThisLikeNamespace {
   @TypeAlias interface Stack extends LinkedList<Integer> {}

   // Then lots of static or non-static methods that use Stack or case classes or whatever
}

Eg see the State monad example

exception with set() expression

        Any<Integer> i = anyInt.define();
        Any<Integer> j = anyInt.define();
        Any<Integer> from = anyInt.define();
        For(i, IntStream.range(1, 3))
            .set(() -> from.set(4 - i.val()))
            .and(j, () -> Iterable(IntStream.range(from.val(), 3)))
            .unit(() -> System.out.println((10 * i.val() + j.val()) + " "));

Produces

java.lang.IllegalArgumentException: No value set for: Any(class java.lang.Integer) value: null

More thoughts on pattern matching syntax

I really like the idea of implementing all the standard scala cases as caseOfXxxx calls of Matcher, eg:

  • caseOfHeadTail(head, tail, ...)
  • caseOfNil(...)
  • caseOfTuple*(a, b, ...) one for each of the Tuples
  • caseOfSome(a, ...) optional present
  • caseOfNone(...) optional not present

That covers 90% of how people construct code in a nice tab-completable way :)

Halva annotations must appear first

There's a bug which causes Halva annotations not to be interpreted if they're not first. You also see the compiler error: Internal error. Could not find annotation: XXX

Improve documentation for non-Scala users

@dain noticed that the docs assume familiarity with Scala:

So in a couple of places you have "you may need to enable Java Annotation processing in your IDE/build tool" maybe link to instructions if this is a real issue

Improve docs for CaseClass

From @dain

also maybe describe why @CaseClassIgnore is useful... wasn't clear what the problem was

I was a bit confused by the case class documentation - I don't understand the difference between a CaseClass and CaseObject... they look the same in the examples

Have simpler Consable<T> interface?

And then ConsList<T> extends List<T>, Consable<T>, and all your internal logic that currently depends on ConsList can depend on Consable instead.

This leaves library users free to write their own list implementations (inheriting from Consable but not List), important since Java doesn't really have a workable immutable list implementation (but eg Functional Java does, even though it can't be used easily because of its abstract construction, annoying)

(Consable just needs head/tail in your internal logic I think? Doesn't need either Iterable or concat?)

Use enums for case objects

Enums have certain advantages over vanilla classes:

  • True singletons regardless of classloader tricks
  • Built-in serialization
  • Can be used in a switch statement

It would be a simpler implementation to use an enum instead of a class for CaseObjects. I did a quick proof of concept and made the following changes:

  1. Named sole enum member the same as the enum to preserve existing usage and imports
  2. Removed Serializable from implements list
  3. Removed private constructor, equals and hashCode
  4. Removed static from method signatures and fields

Basic unit tests pass. Will try and put together a pull request though it is possible that this change may break existing clients.

Add debug tools

It's currently hard to debug for-comp and extraction due to all the indirections. Maybe some debug scaffolding can be added.

TypeAliases - a few more issues/thoughts from using it

One of the common uses of type aliasing is to tidy up generics in local code, eg

@TypeAlias Stack extends ConsList<Integer> {}

There's a couple of issues with how that works at the moment:

  1. Currently the type alias has to be declared in its own compilation unit, which makes the code much less local than you'd normally want. What would be really nice would be to be able to do something like
@TypeAliasesInside
class MyPackage {
   @TypeAlias StackAlias extends ConsList<Integer> {}

    // My business logic that actually uses `Stack`
}

which would then generate MyPackageAlias containing Stack, ie my code in MyPackage then uses final MyPackageAlias.Stack stack; (note: I think the default unsuffix/suffix should be reversed for the inner type alias, ie user types StackAlias and the compiler generates Stack).

That way all my local type aliases remain local to my package, and the codebase is more compact.

  1. The type alias seems to return the original class, eg:
interface StackAlias extends ConsList<Integer> {
   //...
   // Shouldn't this return StackAlias?
   ConstList<Integer> tail(); 
}

I'm not sure that's the only reason, but one of the consequences of this is that the following code doesn't work:

   static Tuple2<Integer, StackAlias> pop(final StackAlias stack) {
        final Any<Integer> head = new AnyType<Integer>() {};
        final Any<StackAlias> tail = new AnyType<StackAlias>() {};
        return Matcher.match(stack)
                .caseOf(Any.anyHeadAnyTail(head, tail), () -> Tuple.Tu(head.val(), tail.val()))
                .get();
    }

You have to replace StackAlias with ConsList<Integer> inside the Any

Consider renaming or other API changes for when()

From @martint

One thing that's not clear is that there seems to be some relationship between the "when"s and the >"and"s, but the linear chaining structure doesn't capture it. E.g., if I'm reading it correctly, the:

.when(() -> author.val().name().startsWith("Ayn"))
.and(year, () -> author.val().years())

executes nested within each:

.>and(author, () -> book.val().authors())

Think about supporting Bean Validation 1.1 annotations on fields

https://docs.jboss.org/hibernate/stable/beanvalidation/api/
http://beanvalidation.org/

It would be useful to support JSR-349 annotations on case classes. Unfortunately, the spec only supports property annotations on JavaBean getter methods. Halva could apply any supplied annotations on the field.

Ex:

@CaseClass
class Foo {
  @NotNull
  @Size(min = 8, max = 12)
  String type();
}

could generate:

class FooCase {
  @NotNull
  @Size(min = 8, max = 12)
  private final String type;
...

One implementation is to take the property annotations, filter out Jackson annotations and copy the remaining onto the field definition. See http://fasterxml.github.io/jackson-annotations/javadoc/2.7/com/fasterxml/jackson/annotation/JacksonAnnotation.html:

Meta-annotation (annotations used on other annotations) used for marking all annotations that are part of Jackson package. Can be used for recognizing all Jackson annotations generically, and in future also for passing other generic annotation configuration.

Support custom JsonProperty annotation in case class interfaces

I would like to define a case class such as:

@CaseClass(json = true)
public interface JsonTest
{
    @JsonProperty("first_name")
    String firstName();
    @JsonProperty("last_name")
    String lastName();
    int age();
}

Not sure how much halva wants to expose a dependency on Jackson, in terms of the actual annotation used by the interface.

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.