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

Dynamic partials support #140

Open
agentgt opened this issue May 10, 2023 · 6 comments
Open

Dynamic partials support #140

agentgt opened this issue May 10, 2023 · 6 comments
Labels
enhancement New feature or request
Milestone

Comments

@agentgt
Copy link
Contributor

agentgt commented May 10, 2023

One of the only parts of the mustache spec not implemented is "dynamic names" aka dynamic partials.

Why are dynamic templates useful: polymorphic data. The idea is some field says what template to use. The rational is explained here.

I had first thought that this was not possible because of the dynamic nature.

Then thought perhaps it is possible with some restrictions. I had first thought of making it possible to annotate an enum entry as a template and generating all the code for each entry ... which is a combinatoric explosion of code generation (every time you access the dynamic name would require a giant switch).

The real solution I think is to allow JStache models themselves to be referenced almost like a lambda.

@JStache(template="Hi my color is {{.}}")
public enum Color {
RED,
GREEN,
BLUE
}

@JStache(template="{{>*color}}")
public sealed interface Ball {
  Color color();
}

public record RedBall() implements Ball {
  Color color() {
    return Color.RED;
  }
}

This does have some serious limitations in that the whole stack is not available to the Color template.

Also the shape cannot be resolved ie no casting. Pattern matching is the ideal way to dispatch on polymorphic data for templating because OOP single dispatch does not really help as we want the calling template in control.

That is JStachio sees Ball as just that and not RedBall. The only solution to that would be a custom lambda.

@agentgt
Copy link
Contributor Author

agentgt commented May 12, 2023

Another solution is to use sealed interfaces. We can reuse JStachePartial.

@JStacheDynamicPartial
public sealed interface Shape permits Square, Triangle {
}

@JStachePartial(template = "...")
public record Square(int height, int width) {}

@JStachePartial(template = "...")
public record Triangle(int a, int b, int c) {}
{{#shapes}}
{{>*.}} {{! I assume implicit is allowed for dynamic names}}
{{/shapes}}

Then JStachio generates Java code like:

Shape shape = ...; //coming from some List<Shape>

if (square instanceof Square square) {
// do rendering
}
else if (square instanceof Triange triangle) {
}
else {
// possibly throw some exception here
}

In future JDKs we can replace the if conditions with a switch once that is supported.

@sviperll might be interested in this (given his experience with writing a library that generates ADTs).

The above is actually not that hard I think as its not far off from the logic dealing with lambdas.

@jgonggrijp
Copy link

I don't have much to add, as I never work with Java and the sealed interface stuff is going over my head. However, I noticed one thing that I could comment on:

{{>*.}} {{! I assume implicit is allowed for dynamic names}}

Yes, it is. I mean, I did not fact-check whether this is explicit in the spec, but at least in spirit, it is supposed to work.

@agentgt
Copy link
Contributor Author

agentgt commented May 25, 2023

Sealed classes are basically sum types (well with OOP mixed but ignore that).

For some reason I thought you had some Haskell experience but I must have mixed that up with someone else.

I think you know TypeScript? In TypeScript they are called union types and "narrowing" is pattern matching or in the case of current non-preview Java the crappy if (square instanceof Square square).

JStachio associates templates with types through annotations however typescript doesn't have annotations but the idea is still the same pretend we can associate a type with a template.

Thus in Typescript:

interface Triange {
  type: "triangle"; // we somehow register Triangle to triangle.mustache
  a: number; b: number; c: number;
} 

interface Rectange {
  type: "rectange"; // we somehow register rectangle to rectangle.mustache
  height: number;
  width: number;
} 

type Shape = Triangle | Square;

Now let us say we have json like:

{ data : [
  { type : "rectangle", height: 1, width: 2 },
  { type : "triangle", a: 1, b: 2, c: 3 }
]}

In something like wontache you:

{{#data}}
{{>*type}}
{{/data}}

But a typescript powered one (this might not be possible with typescript but lets assume it is):

{{#data}}
{{>*.}}
{{/data}}

The second one is better because there are only two possible templates that can be resolved where as the first one it is just string based.

This is very powerful because in language like OCaml, Haskell, Rust etc they either do not allow casting or it is strongly discouraged and you can't just do duck typing like you can in Javascript. The problem is still the same: polymorphic data but one is type-safe.

@jgonggrijp
Copy link

I do know Haskell and sum types (and also TypeScript and union types), so clarifying that sealed interfaces play a similar role in Java was very helpful to me!

With your examples, if you write {{>*type}}, you do pretty much the same thing as TypeScript when it narrows a union type:

if (datum.type === 'rectangle') {
    // can assume height and width here
} else {
    // can assume a, b, c here
}

Using dynamic partials in this way in JStachio definitely seems like a very elegant solution!

However, it is unclear to me whether you want to interpret {{>*.}} as implicitly meaning {{>*type}}. In any case, that is not how it is intended in the spec! Consider the following example of how one might use {{>*.}} in accordance with the spec:

data

{
    name: 'JStachio',
    description: 'type-safe Mustache engine for Java',
    modes: ['preview', 'editable'],
}

preview.mustache

<article class="engine-preview">
    <h3>{{name}}</h3>
    <p>{{description}}</p>
</article>

editable.mustache

<form class="engine-editor">
    <label>
        Name:
        <input name=name type=text value="{{name}}">
    </label>
    <label>
        Description:
        <textarea name=description>
            {{description}}
        </textarea>
    </label>
</form>

main.mustache

{{#modes}}
{{>*.}}
{{/modes}}

output

<article class="engine-preview">
    <h3>JStachio</h3>
    <p>type-safe Mustache engine for Java</p>
</article>
<form class="engine-editor">
    <label>
        Name:
        <input name=name type=text value="JStachio">
    </label>
    <label>
        Description:
        <textarea name=description>
            type-safe Mustache engine for Java
        </textarea>
    </label>
</form>

Playground savestate:

{"data":{"text":"{\n    name: 'JStachio',\n    description: 'type-safe Mustache engine for Java',\n    modes: ['preview', 'editable'],\n}"},"templates":[{"name":"preview","text":"<article class=\"engine-preview\">\n    <h3>{{name}}</h3>\n    <p>{{description}}</p>\n</article>\n"},{"name":"editable","text":"<form class=\"engine-editor\">\n    <label>\n        Name:\n        <input name=name type=text value=\"{{name}}\">\n    </label>\n    <label>\n        Description:\n        <textarea name=description>\n            {{description}}\n        </textarea>\n    </label>\n</form>\n"},{"name":"main","text":"{{#modes}}\n{{>*.}}\n{{/modes}}"}]}

Ironically, this is more like an intersection type in TypeScript, or simply a way to present the same information in different ways.

@agentgt agentgt added this to the v1.2.0 milestone Jun 6, 2023
@agentgt agentgt pinned this issue Jun 6, 2023
@agentgt agentgt added the enhancement New feature or request label Jun 6, 2023
@agentgt
Copy link
Contributor Author

agentgt commented Jun 7, 2023

@jgonggrijp sorry for the late follow up but your example would be completely fine because the context stack will be preserved.

In the case of some object actually using literals instead of types in Java those literals would be enums.

Given your example of modes:

    modes: ['preview', 'editable'],
@JStacheDynamicName
public enum Mode { 
  @JStachePartial(path="preview.mustache") // or whatever annotation
  preview, 
  @JStachePartial(path="editable.mustache") // or whatever annotation
  editable;
}

Its important to understand in the Java world JSON strings are often represented as enums and the serializers like Jackson do the conversion.

Thus this data:

{
    name: 'JStachio',
    description: 'type-safe Mustache engine for Java',
    modes: ['preview', 'editable'],
}

In Java as a type would probably be something like:

public record Data(String name, String description, List<Mode> modes) {}

And the main.mustache in your example would work exactly like a Javascript with one exception:

{
    name: 'JStachio',
    description: 'type-safe Mustache engine for Java',
    modes: ['preview', 'editable', 'somethingnaughty'],
}

Would fail for somethingnaughty. That is we are limiting the choices of templates to dispatch to.

Basically editable.mustache and preview.mustache or more like Go langs typing or some form of structural typing.

That is a new Data type called:

public record OtherData(String name, String description, List<Mode> modes, ... more fields) {}

Would work as well since it also has a "name" and "description" field.

@jgonggrijp
Copy link

Great!

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

No branches or pull requests

2 participants