Wow, it's amazing!
I'd spend some time testing it.

Thanks a lot!

Woonsan

On Sat, Feb 23, 2019 at 12:19 PM Daniel Dekany <[email protected]> wrote:
>
> I have pushed an implementation of the restricted lambdas we were
> talking about. Guys, please review/test it.
>
> I call these "local lambdas" in the source code (but I'm open for
> suggestions), as the function they define can only be called in the
> same variable scope where the lambda was (as we have no closures in
> the template language, nor final variables). Also, they can only be
> used as the parameters of the built-ins that explicitly support them.
> As the subject shows, the main goal was just support filtering for
> #list, without a nested #if, as that breaks #sep, #else, it?hasNext,
> it?index, etc. (I will try to make more universal lambdas in FM3... I
> guess it would be way too tricky in FM2.)
>
> For now, I have only added two built-ins that support lambdas: ?filter
> and ?map. Any ideas what other such built-ins would be often useful in
> templates (with use case, if possible)?
>
> Examples of using ?filter and ?map:
>
>   <#list products?filter(it -> it.price < 1000) as product>
>     ${product.name}
>   </#list>
>
>   <#list products?map(it -> it.name) as name>
>     ${name}
>   </#list>
>
> Of course these built-ins aren't specific to #list, they can be used
> anywhere. Naturally, they can be chained as well:
>
>   <#assign chepProdNames = products
>      ?filter(it -> it.price < 1000)
>      ?map(it -> it.name)
>   >
>
> As a side note, ?filter and ?map also accepts FTL function-s and Java
> methods as its parameter, not only lambdas.
>
> A tricky aspect of this feature is lazy evaluation, in similar sense
> as Java 8 Stream intermediate operations are lazy. On most places,
> ?filer and ?map are eager, that is, they return a completed sequence
> of items. That's because our restricted lambdas only work correctly
> "locally". However, there are very common situations where it's clear
> that we can use lazy ("streaming") evaluation, as we know where the
> resulting stream of elements will be consumed:
>
> - One such case is when these kind of built-ins are chained. Like in
>   the last example, ?filter doesn't construct a List in memory,
>   instead the elements just flow through it (some is dropped, as it's
>   filtering), into ?map. Only the ?map at the end of the chain will
>   built a List eagerly. Some other built-ins also allow the left-hand
>   built-in to stream, like in ?filter(...)?map(...)?join(", ") no List
>   is built anywhere, instead the elements flow through both ?filter
>   and ?map, and ?join just appends them to the StringBuilder where it
>   creates its results.
>
> - #list also enables lazy evaluation to its 1st parameter. So in the
>   #list examples above, yet again no List is built in memory.
>
> - There are other such cases, which I didn't implement yet. For
>   example the sequence slice operator, like, xs?filter(f)[10..20],
>   should allow lazy evaluation.
>
> Feedback is welcome!
>
>
> Monday, December 17, 2018, 11:39:36 AM, Christoph Rüger wrote:
>
> > Hey Daniel,
> > I'm very sorry, but I didn't make any progress with this. I think I was a
> > bit over-motivated, but unfortunately I cannot spend more time on this for
> > various reasons.
> > You can take this over. I'm glad to help out with testing and feedback.
> >
> > Thanks
> > Christoph
> >
> >
> >
> >
> > Am Mo., 17. Dez. 2018 um 11:04 Uhr schrieb Daniel Dekany <[email protected]
> >>:
> >
> >> Any progress in this? I think I will give it a try in the coming days
> >> otherwise.
> >>
> >>
> >> Sunday, November 18, 2018, 10:31:29 PM, Daniel Dekany wrote:
> >>
> >> > See my answers inline...
> >> >
> >> > Sunday, November 18, 2018, 8:44:40 PM, Christoph Rüger wrote:
> >> >
> >> >> Thanks Daniel for your feedback. See my answers below
> >> >>
> >> >> Am So., 11. Nov. 2018 um 19:14 Uhr schrieb Daniel Dekany <
> >> [email protected]
> >> >>>:
> >> >>
> >> >>> Sunday, November 11, 2018, 11:40:50 AM, Christoph Rüger wrote:
> >> >>>
> >> >>> > Am So., 11. Nov. 2018 um 09:25 Uhr schrieb Daniel Dekany <
> >> >>> [email protected]
> >> >>> >>:
> >> >>> >
> >> >>> >> Saturday, November 10, 2018, 3:08:14 PM, Denis Bredelet wrote:
> >> >>> >>
> >> >>> >> > Hi,
> >> >>> >> >
> >> >>> >> > Le 9 novembre 2018 à 22:36, Christoph Rüger <[email protected]>
> >> a
> >> >>> >> écrit :
> >> >>> >> >
> >> >>> >> > Am Fr., 9. Nov. 2018 um 22:55 Uhr schrieb Daniel Dekany <
> >> >>> >> [email protected]
> >> >>> >> > :
> >> >>> >> >
> >> >>> >> > It's certainly tricky, but as far as I see possible (but then, who
> >> >>> >> >
> >> >>> >> > knows what will one find when actually working on it). It's also a
> >> >>> >> > feature missing a lot. It's especially missing for #list (I know
> >> that
> >> >>> >> > you need it for something else), because if you filter the items
> >> >>> >> > inside #list with #if-s, then #sep, ?hasNext, etc. will not be
> >> usable.
> >> >>> >> >
> >> >>> >> > Let me say that I disagree here.
> >> >>> >> >
> >> >>> >> > I do not think that closures are required for FreeMarker, nor that
> >> >>> they
> >> >>> >> are a good idea.
> >> >>> >> >
> >> >>> >> > If we add new features to the FreeMarker *tempate engine* I would
> >> >>> >> > rather we focus on multi-part macro body rather than an advanced
> >> >>> >> language feature like closures.
> >> >>> >> >
> >> >>> >> > You can add ?filter and ?map if you want, a simple expression as
> >> >>> >> parameter should be enough.
> >> >>> >>
> >> >>> >> Yes, as I said, we certainly start with only allowing lambdas in
> >> >>> >> ?filter/?map, also certainly in ?contains.
> >> >>> >>
> >> >>> > Would be enough in my opinion and very useful.
> >> >>> >
> >> >>> > Is it possiblefor you to give some pointers to the code on how this
> >> could
> >> >>> > be implemented? I would maybe like to wrap my head around this a
> >> little
> >> >>> bit.
> >> >>>
> >> >>> Please feel yourself encouraged! (:
> >> >>>
> >> >>> > I started looking at seq_containsBI (
> >> >>> >
> >> >>>
> >> https://github.com/apache/freemarker/blob/a03a1473b65d9819674b285a0538fed824f37478/src/main/java/freemarker/core/BuiltInsForSequences.java#L291
> >> >>> )
> >> >>> > and
> >> >>> > and reverseBI (
> >> >>> >
> >> >>>
> >> https://github.com/apache/freemarker/blob/a03a1473b65d9819674b285a0538fed824f37478/src/main/java/freemarker/core/BuiltInsForSequences.java#L264
> >> >>> )
> >> >>> > just to find something related (seq_containsBI checks something) and
> >> >>> > reverseBI returns a new sequence.
> >> >>> > What I haven't found is a function which takes an Expression as a
> >> >>> > parameter.
> >> >>> > Is there something similar already or would that be a new thing?
> >> >>>
> >> >>> It's a new thing in that it will be part of the expression syntax
> >> >>> (even if for now we will only allow lambdas as the parameters of a few
> >> >>> built-ins, so that we can get away without closures). So it's a new
> >> >>> Expression subclass, and has to be part of the parser (ftl.jj) as
> >> >>> well.
> >> >>
> >> >> Hmm, that parser stuff is new for me, it'll take me some time to get
> >> into
> >> >> it.
> >> >
> >> > So it's a JavaCC lexer+parser. With a few twists... sorry, but this
> >> > code has history... :)
> >> >
> >> >>> As of lazy evaluation of parameters expressions, that's already
> >> >>> done in the built-ins in BuiltInsWithParseTimeParameters, and you will
> >> >>> see it's trivial to do, but the situation there is much simpler.
> >> >>>
> >> >>
> >> >>
> >> >>>
> >> >>> In principle, a LambdaExpression should evaluate to a
> >> >>> TemplateMethodModelEx, and then you pass that TemplateMethodModelEx to
> >> >>> the called built-in or whatever it is. But with the approach of
> >> >>> BuiltInsWithParseTimeParameters we can certainly even skip that, and
> >> >>> just bind to the LambdaExpression directly, add a LocalContext that
> >> >>> contains the lambda arguments, end evaluate the LambdaExpression right
> >> >>> there, in the built-in implementation. Or at least at a very quick
> >> >>> glance I think so.
> >> >>>
> >> >> Not sure I can follow completely but that hint with*
> >> >> BuiltInsWithParseTimeParameters* got me started, but at the moment I'm
> >> >> stuck as I need to get more familiar with the internals of Freemarker.
> >> I am
> >> >> also not sure I am on the same page regarding the syntax we are aiming
> >> for
> >> >> and why I would need to extend the parser when there is something like
> >> >> BuiltInsWithParseTimeParameters....
> >> >
> >> > I assumed that the syntax will be similar to the Java lambda syntax
> >> > (see below why), and that's of course needs lexer/parser changes.
> >> >
> >> >> Here is an example what I have in mind:
> >> >> I started with the ?filter() builtin. I had a syntax like this in mind:
> >> >>
> >> >> *Example 1: ["a","b","c"]?filter(element, element == "c")*
> >> >> *Example 2: ["a","b","c"]?filter(element, element == someOtherVariable,
> >> >> someOtherVariable="c")*
> >> >
> >> > Looking at the above one believe that the value of `element` is passed
> >> > in as first argument, and the the value of `element == "c"` as the
> >> > second, but that's not the case. It's much better if it's visible that
> >> > you got some kind of anonymous function definition there without
> >> > knowing about the "filter" built-in.
> >> >
> >> > Java already has a syntax for expressing this kind of thing, so it
> >> > would be better to use that familiar syntax. As far as I know it
> >> > doesn't conflict with ours (it kind of it does, but not fatally).
> >> >
> >> > Also, even if for now we only allow this in said built-ins, we
> >> > shouldn't exclude the possibility of making this kind of expression
> >> > accessible elsewhere as well. Then, on many places the parsed won't
> >> > know what kind of value is expected (consider passing a lambda to an
> >> > user defined directive for example), so a syntax like above won't
> >> > work.
> >> >
> >> >> Not sure if that's what you have in mind too, but to me it made sense
> >> with
> >> >> regards to BuiltInsWithParseTimeParameters and I could start without
> >> >> touching parser stuff.
> >> >
> >> > BuiltInsWithParseTimeParameters doesn't affect the syntax (only a
> >> > bit...), as a call to a such built-in looks like a call to any other
> >> > built-in.
> >> >
> >> >> *1st argument 'element'* would just be the iterator variable similar to
> >> >> <#list ["a","b","c"] as *element*>
> >> >> 2nd argument is the filter lambda expression... aka our filter condition
> >> >> 3rd+n argument are optional parameters in case used in the lambda
> >> expression
> >> >
> >> > #list is special, similarly as `for ... in ...` is in most languages.
> >> > It's not lambda-ish either; you can't write `as element/2` or such.
> >> >
> >> >> So at first I was looking how <#list> works and found IteratorBlock, and
> >> >> though I could reuse it somehow.
> >> >>
> >> >> Here is some simple pseudo code I played around for the for Example 1:
> >> >>
> >> >> static class filter_BI extends BuiltInWithParseTimeParameters {
> >> >>
> >> >>
> >> >>
> >> >>         TemplateModel _eval(Environment env) throws TemplateException {
> >> >>
> >> >>             // sequence
> >> >>
> >> >>         TemplateModel targetValue = target.evalToNonMissing(env);
> >> >>
> >> >>
> >> >>
> >> >>             List parameters = this.parameters;
> >> >>
> >> >>
> >> >>
> >> >>             Expression iteratorAlias = (Expression) parameters.get(0);
> >> >>
> >> >>             Expression conditionExpression = (Expression)
> >> parameters.get(1);
> >> >>
> >> >>
> >> >>
> >> >>             TemplateSequenceModel seq =  (TemplateSequenceModel)
> >> target.eval
> >> >> (env);
> >> >>
> >> >>             for (int i = 0; i < seq.size(); i++) {
> >> >>
> >> >>                TemplateModel cur = seq.get(i);
> >> >>
> >> >>
> >> >>                // this is where I am stuck at the moment
> >> >>
> >> >>                // I basically want to evaluate conditionExpression
> >> >> where iteratorAlias
> >> >> is basically what I passed as 'element'
> >> >>
> >> >>                // I am not sure if or how LocalContext could come into
> >> play
> >> >> here
> >> >>
> >> >>                // basically for each iteration I would assign the
> >> current
> >> >> loop element to a context variable with the name 'element'
> >> >>
> >> >>                // and then evaluate conditionExpression with that
> >> context.
> >> >>
> >> >>                // if conditionExpression is "true" then I would populate
> >> >> add the current sequence element 'cur'
> >> >>
> >> >>                // to a new result-List.... and return that.... something
> >> >>
> >> >>                // I wanted to reuse IteratorBlock here somehow, but
> >> didn't
> >> >> get it to work yet.
> >> >>
> >> >>                // maybe this is a stupid idea, or we just need something
> >> >> similar
> >> >>
> >> >>
> >> >>
> >> >>             }
> >> >>
> >> >> }
> >> >>
> >> >>
> >> >>
> >> >> Ok so far for my pseudo code....  Maybe you could give some more
> >> pointers
> >> >> based on that... in case this makes any sense ...
> >> >
> >> > For LocalContext, Environment has a pushLocalContext method. See the
> >> > calls to it, like in visit(TemplateElement[], TemplateDirectiveModel,
> >> > Map, List).
> >> >
> >> > To avoid running into more new things at once than necessary, perhaps
> >> > you should start with seq?seq_contains(lambda). The end result should
> >> > be like:
> >> >
> >> >   users?contains(u -> u.paying)
> >> >
> >> >>> Another similarity to BuiltInsWithParseTimeParameters is that we won't
> >> >>> allow separating the `?someBI` and `(arg)`. Like, with the example of
> >> >>> `cond?then(1, 2)`, you aren't allowed to do this:
> >> >>>
> >> >>>   <#assign t=cond?then>
> >> >>>   ${t(1, 2)}
> >> >>>
> >> >>> That maybe looks natural, but most other built-ins allow that. Of
> >> >>> course we can't allow that for ?filter etc., because then we need
> >> >>> closures again.
> >> >>>
> >> >>> >> Multi-part macro body is also planned. Means, I know it definitely
> >> >>> >> should be added, but who knows when that's done... I mean, it's like
> >> >>> >> that for what, a decade? (: It's not even decided what it exactly
> >> >>> >> does, as there are many ways of approaching this. (I have my own
> >> idea
> >> >>> >> about what the right compromise would be, but others has other
> >> >>> >> ideas...)
> >> >>> >>
> >> >>> >> Filtering lists bothers me because the template language should be
> >> >>> >> (and somewhat indeed is) specialized on listing things on fancy ways
> >> >>> >> that used to come up when generating document-like output. (If it
> >> >>> >> doesn't do things like that, you might as well use a general purpose
> >> >>> >> language.) Thus, that filter is unsolved (filtering with #if is
> >> >>> >> verbose and spoils #sep etc.) bothers me a lot.
> >> >>> >>
> >> >>> >> BTW, ?filter and ?map is also especially handy in our case as
> >> >>> >> FreeMarker doesn't support building new sequences (sequences are
> >> >>> >> immutable). Although it has sequence concatenation with `+`, it's
> >> not
> >> >>> >> good for building a sequence one by one, unless the sequence will be
> >> >>> >> quite short.
> >> >>> >>
> >> >>> > Good point.
> >> >>> >
> >> >>> >
> >> >>> >>
> >> >>> >> > Cheers,
> >> >>> >> > -- Denis.
> >> >>> >>
> >> >>> >> --
> >> >>> >> Thanks,
> >> >>> >>  Daniel Dekany
> >> >>> >>
> >> >>> >>
> >> >>> >
> >> >>>
> >> >>> --
> >> >>> Thanks,
> >> >>>  Daniel Dekany
> >> >>>
> >> >>>
> >> >> Thanks
> >> >> Christoph
> >> >>
> >> >
> >>
> >> --
> >> Thanks,
> >>  Daniel Dekany
> >>
> >>
> >
> > --
> > Christoph Rüger, Geschäftsführer
> > Synesty <https://synesty.com/> - Anbinden und Automatisieren ohne
> > Programmieren - Automatisierung, Schnittstellen, Datenfeeds
> >
> > Xing: https://www.xing.com/profile/Christoph_Rueger2
> > LinkedIn: http://www.linkedin.com/pub/christoph-rueger/a/685/198
> >
>
> --
> Thanks,
>  Daniel Dekany
>

Reply via email to