Am Mo., 25. Feb. 2019 um 00:46 Uhr schrieb Daniel Dekany <[email protected] >:
> Sunday, February 24, 2019, 11:05:52 PM, Christoph Rüger wrote: > > > Am So., 24. Feb. 2019 um 19:40 Uhr schrieb Daniel Dekany < > [email protected] > >>: > > > >> Sunday, February 24, 2019, 12:45:48 AM, Christoph Rüger wrote: > >> > >> > Thanks you very much. This looks great. > >> > Our testsuite passes all freemarker related tests. > >> > > >> > Also some simple manual tests were successful e.g. > >> > <#list row.cols?map(col -> col.title)?filter(ct -> > ct?contains("name")) > >> as > >> coltitle >>${coltitle!}<#sep>,</#sep></#list> > >> > <#list description?split(" ")?filter(word -> word == "test") as > >> filteredWord>>${filteredWord}</#list> > >> > > >> > Regarding other use-cases for lazy-evaluation: > >> > > >> > Do you think a ?count (as terminal operation) without building up a > list > >> in > >> > memory? > >> > E.g. > >> > Number of products < 1000: ${products?filter(it -> it.price < > >> 1000)?count} > >> > > >> > (I tested ${products?filter(it -> it.price < 1000)?size} but I think > this > >> > builds up a list in memory which can be large) > >> > >> Good point! We don't need to introduce ?count for this though, simply > >> ?size has to be made smarter. > >> > > > > Right, that's better. Let's use ?size. > > > >> > Or what about other aggregations for numerical values like sum, avg, > min, > >> > max e.g.: > >> > Cheapest price of products < 1000: ${products?filter(it -> it.price < > >> > 1000)?map(it -> it.price)?min} > >> > >> We have ?min and ?max, they also need to be made smarter. > > > > > > Ok. If it can be done efficiently then good. As ?min ?max are already > there > > I guess people would expect to use them...as I did. > > After writing the previous mail I have realized that ?min, ?max, > and also ?first, ?seq_contains, and ?seq_index_of already enable lazy > processing. > > >> As of ?sum > >> and ?acg... they are doable. But aggregates are mostly useful combined > >> with "group by". Technically, we can do all that, but I'm not 100% > >> sure if it's wise to do. > > > > The more such operations the template > >> language supports, the more people will tend to move such calculations > >> from the Java code that builds the data-model to the template. But the > >> good practice was always the opposite of that: only do presentation > >> logic in templates, not "business" calculations. But then again, there > >> are the situations when the template authors have little control over > >> the data model (typically, when you make a report/mail template for > >> some business application that was developed by another party), and > >> then they are happy that they can get the work done in the template. > >> So, it's not easy to decide which compromise is the better. > >> > > > > Yeah difficult decision.... We are such an application you mention where > > the template is the only tool available. > > > > Maybe groupBy and aggregates like ?sum, ?avg should not be part of it for > > now. I think Grouping requires keeping data in memory... that's pretty > > powerful but performance critical. Or have a way to enable/disable it > with > > configuration. > > > > Speaking of configuration / customization: We have built something like > > groupBy ourselves outside the template language (for a special table > > object). We added some "safety" stuff to abort processing if the data > grows > > too large over a defined threshold. It would be good to be able to > > customize this too. > > Often such thing has the best place in the data-model, as you know > more about your application than the template engine. (Not to mention > when you could do the group-by in SQL, but if the template can do it > as well, maybe the template authors just use that, and so the backend > guys will never know about this requirement.) Also there's the > inherent inefficiency with built-ins that they have to work with > TemplateModel-s, while a solution in the model probably can work > directly with the native objects. So I'm not sure if we want > aggregating groupBy in the template language. > Right. It's probably better to not have ?groupBy in the template language and do it in the data-model. > > >> BTW, the kind of "group by" operation that doesn't aggregate anything, > >> just splits the collection into smaller collections for each distinct > >> "group by" value, like Collectors.groupBy in Java 8, would be often > >> useful for presentation logic. The use case is that you get a List of > >> rows, and let's say you have a Year column in there, and the List is > >> ordered by that. Then you should render it so that you don't show the > >> same year again and again. Like if it's a HTML table then you are > >> supposed to use td.@rowspan in the year column. It's quite awkward to > >> do such thing currently (try it and you will see...), despite that > >> it's a common requirement and is clearly a presentation decision, not > >> really "business logic". So, we could introduce a built-in for that: > >> collection?splitBy(lambdaOrFunctionOrMethod). I would avoid calling it > >> groupBy, since that has a different meaning in SQL (the aggregating > >> groupBy veriant). So now rowspan by year would be like: > >> > >> <table> > >> <tr> > >> <th>Year</th> > >> <th>Department</th> > >> <th>Income</th> > >> </tr> > >> <#list rows?groupBy(row -> row.year) as sameYearRows> > > I meant ?splitBy... > Sorry, forgot about it while writing. > > >> <#list sameYearRows as row> > >> <tr> > >> <#if row?isFirst> > >> <td rowspan="${sameYearRows?size}">${row.year}</td> > >> </#if> > >> <td>${row.department}</td> > >> <td>${row.income}</td> > >> </tr> > >> </#list> > >> <#list> > >> </table> > >> > >> Or if you have to print a new H2 and table for each year: > >> > >> <#list rows?groupBy(row -> row.year) as sameYearRows> > > Again, ?splitBy > >> <h2>${sameYearRows[0].year}<h2> > >> <table> > >> <tr> > >> <th>Department</th> > >> <th>Income</th> > >> </tr> > >> <#list sameYearRows as row> > >> <tr> > >> <td>${row.department}</td> > >> <td>${row.income}</td> > >> </tr> > >> </#list> > >> </table> > >> <#list> > >> > >> > > Yes that can be handy. But the implementation should be careful to avoid > > large in-memory structures for the "groups" > > Does ?groupBy(row -> row.year) create a new List for each group? Or is > > it kind of a "Views" on the original list? > > It has to collect the whole thing into the memory, as we can't assume > that the source collection is sorted by the grouping key. So maybe it > should instead start a new group when the grouping key value changes > in the stream of elements. Then the name should be "splitAtChanges": > <#list rows?splitAtChanges(row -> row.year) as sameYearRows> > >> Lambdas happen to be handy with this as well. Actually, some long > >> existing built-ins, like ?sortBy, ?contains, etc. could use them as > >> well (a bit tricky for them technically due to backward compatibility > >> requirements, but I belive I can work that around.) > >> > > > > Overall those lambdas allow some great new use cases with pretty concise > > syntax. That is good. The ?filter and ?map built-ins are very cool > already. > > If some existing built-ins will be made "smarter" (as you said), then it > > will be a great enhancement. > > > > Speaking for us, performance and memory usage is always a concern. So it > > would be good to keep an eye on avoiding large new in-memory structures. > > Template authors can always do something like > <#assign cheapProducts = prods?filter(...)>, and that will collect > everything into a List internally, as it's eager processing. If an > aggregating ?groupBy is concerning, then this is even more so. Hmm... well.... difficult.... So, *<#assign cheapProducts = prods?filter(...)>* will create a new list, while the following does not: <#list products?filter(it -> it.price < 1000) as product> ${product.name} </#list> Right? Earlier you wrote: *"Of course these built-ins aren't specific to #list, they can be usedanywhere. Naturally, they can be chained as well:"* What would be a downside of allowing ?filter / ?map only in #list? I guess it makes it more complicated to explain. Difficult tradeoffs here :) Sorry sometimes I get confused by jumping between lazy and eager processing. > I'm not sure how efficiently could a configuration setting catch these > cases, or if it should be addressed on that level. > Maybe let's postpone configurability discussion a bit until the above is more clear. > > > My thoughts so far. > > Thanks > > Christoph > > > > > >> > >> > Didn't think this through, but just had the idea while testing. > >> > > >> > Anyway, good work! > >> > > >> > > >> > > >> > > >> > > >> > > >> > Am Sa., 23. Feb. 2019 um 18:19 Uhr schrieb Daniel Dekany < > >> [email protected] > >> >>: > >> > > >> >> 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 > >> >> > >> >> > >> > > >> > >> -- > >> Thanks, > >> Daniel Dekany > >> > >> > > > > -- > Thanks, > Daniel Dekany > > -- Synesty GmbH Moritz-von-Rohr-Str. 1a 07745 Jena Tel.: +49 3641 5596493Internet: https://synesty.com <https://synesty.com> Informationen zum Datenschutz: https://synesty.com/datenschutz <https://synesty.com/datenschutz> Geschäftsführer: Christoph Rüger Unternehmenssitz: Jena Handelsregister B beim Amtsgericht: Jena Handelsregister-Nummer: HRB 508766 Ust-IdNr.: DE287564982
