jslt icon indicating copy to clipboard operation
jslt copied to clipboard

WHEN statement proposal

Open up-to-you opened this issue 5 years ago • 3 comments

The proposal:

statement / syntax block, similar to Java switch/case statement or Kotlin when block to replace boilerplate if else if conditions. Allows to increase code readability and maintenance drastically. We can meet such syntax in almost every ETL / data transformation frameworks, tools, since this is a common case in T phase.

The syntax may be near like:

      when(.node.child)
           "value": { 
               "a": .  
            }
           "another-value": { 
               "b": number(.) 
           }

We hit the wall after 300+ autogenerated if else if conditions. So at a first glance it may be simple syntax sugar, but afterwards there could be room for optimizations such as compile-time HashMap construction based on Value literals, which allow constant time branch lookup.

Currently, made this mvp working:

[for (.tags)
    when(.)
        cx: {
          "rs-cx": string("cx-" + .),
          "size": size(.)
        }
        cx-prefix: {
          "rs-cx-prefix": string("cx-prefix-" + .),
          "size": size(.)
        }
        cx-ext: {
          "rs-cx-ext": string("cx-ext-" + .),
          "size": size(.)
        }
]
void WhenStatement() :
{}
{
  <WHEN> <LPAREN> Expr() <RPAREN>
    (WhenLiteral() Colon() Expr())*
}

void WhenLiteral() :
{}
{
  <IDENT> | <STRING>
}

if you agree with this syntax - i'll continue to work on PR.

up-to-you avatar Mar 02 '21 00:03 up-to-you

Let's start with the purpose: I assume the reason you are interested in this is to improve performance?

From the looks of that JSLT you can do this:

let mapping = {
 "cx" : "rs-cx",
 "cx-prefix" : "rs-cx-prefix",
 "cx-ext" : "rs-cx-ext",
  // ...
}

[for (.tags)
  {
    get-key($mapping, .) : "cx-" + ., // + with a string implicitly converts to string anyway
     "size": size(.)
   }]

This code doesn't entirely make sense, though, so whether this really works for your use case I can't tell.

An alternative is to let the syntax be, and simply implement an optimizer on top of the current syntax. You can take any if ... else if ... else if ... with more than, say, 10 else branches, and collapse all the initial expressions where the condition has root operator ==, one operand is the same variable for all branches, and the other operand is a literal (string, number, boolean). It requires building a Map<JsonNode, ExpressionNode>, but that's ok.

larsga avatar Mar 04 '21 14:03 larsga

the block

let mapping = {
 "cx" : "rs-cx",
 "cx-prefix" : "rs-cx-prefix",
 "cx-ext" : "rs-cx-ext",
  // ...
}

will compute all 300+ "branchers" before the, loop. The issue is to compute only single branch. My thoughts, that while we can silently optimize huge if ... else ... branches - without having any JIT compiler in language design - this optimization looks like breaking semantics

up-to-you avatar Mar 04 '21 21:03 up-to-you

the block ... will compute all 300+ "branchers" before the, loop

I don't understand what you mean by that. The object will be created at compile-time. So the only thing that happens at runtime is that the object is assigned to the variable, which is pretty much instant.

The issue is to compute only single branch.

The loop I showed you only computes a single branch.

My thoughts, that while we can silently optimize huge if ... else ... branches - without having any JIT compiler in language design - this optimization looks like breaking semantics

The language design only deals with semantics: what code+input gives what output, and nothing more. So implementors are free to have optimizers/JIT compiler etc or not as they please.

Two expressions are equivalent if give the same output for all inputs. Optimization is transforming some slow expressions into equivalent expressions that are faster. It follows that you can have as many optimizations as you want without breaking the semantics because a correct optimization always preserves semantics.

There are actually an optimizer already with a number of optimizations implemented.

And there is no difference between optimizing if ... else ... and optimizing when. In both cases you are doing an optimization, but the transform does exactly the same thing it did before the optimization.

larsga avatar Mar 05 '21 07:03 larsga