soabase / soabase-halva

Idiomatic Scala ... in Java
https://github.com/soabase/soabase-halva/blob/master/README.md
Apache License 2.0
81 stars 5 forks source link

Support container types for concrete classes #25

Closed Alex-At-Home closed 8 years ago

Alex-At-Home commented 8 years ago

(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

Randgalt commented 8 years ago

In the generated class, the Aliases/Cases, etc. would still be "static" classes, right? i.e. you don't need to instantiate the container class.

Alex-At-Home commented 8 years ago

I don't know what else I want to do in/with the container class (ie it's not just a container, it's also the namespace for all my logic - that's important because that's what lets me keep the case classes and type aliases "local" in a natural way), so I don't want to restrict what I do with it, all I know is that I want to declare Halva artefacts inside it and have them appear in (eg) MyClassName+Types

So eg two examples:

Randgalt commented 8 years ago

So, in the monad example, would the StateExample container extend StateExample? Maybe it should be an option in @TypeContainer.

Alex-At-Home commented 8 years ago

The way I'd like to write the actual code looks something like this:

package oh.hai;

import oh.hai.ExampleTypes.Stack;
import static oh.hai.ExampleTypes.Stack.Stack;
import oh.hai.ExampleTypes.Point;
import static oh.hai.ExampleTypes.Point.Point;
//...

@TypeContainer(suffix="Types", unsuffix="")
public class Example {
   @TypeAlias(suffix="", unsuffix="Alias") interface StackAlias extends ConsList<Stack> {}
   @CaseClass(suffix="", unsuffix="Alias") interface PointAlias { double x(); double y(); }

   // Business logic

   int main(String[] args) {
      // Eg
      final Stack s = Stack(List(1, 2, 3));
      final Point p = Point(1.0, 1.0);
   }
}

So although the code is going into a different class (ExampleTypes vs Example), as a developer everything looks like it's local, someone in a different class can happily declare Stack to be ArrayList<String>, etc.

Randgalt commented 8 years ago

In your example above, the generated class would be ExampleTypes. So, should all the methods, etc. in Example be copied into ExampleTypes or should ExampleTypes extend Example? What's clumsy is that main() expects the generated types to exist but they won't exist until processing. After processing, main() would have to Reference (or have a static import for) ExampleTypes.Stack or something similar.

Alex-At-Home commented 8 years ago

Hmm interesting, having used now @TypeContainer a bit more I see what you mean.

Seems like there are two ways of doing it, neither of which is great:

  1. you'd be forced to create your own ExampleTypes_, containing the "local" data model, and then Example with the logic, importing the generated ExampleTypes (and not having the definitions local)
  2. or you write your case classes etc at the top of Example, hit rebuild, and then go write the logic, which is kinda ugly but not a million miles different from creating the data model in a separate file and hitting rebuild? (and then you get the data model visible locally)

I feel like some people are going to prefer 1 and some 2?

(In the latter case, ExampleTypes doesn't inherit from Example, it's linked to Example only by the common name stem)

(Do you ever want to inherit the generated container? It's not like you can use the declared versions of anything anyway, you always use the generated ones, no?)