You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by "Dániel Dékány (Jira)" <ji...@apache.org> on 2020/02/01 09:04:00 UTC

[jira] [Comment Edited] (FREEMARKER-130) Excluding keys from hashes (without_keys/exclude_keys)

    [ https://issues.apache.org/jira/browse/FREEMARKER-130?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17028015#comment-17028015 ] 

Dániel Dékány edited comment on FREEMARKER-130 at 2/1/20 9:03 AM:
------------------------------------------------------------------

Varargs is supported, but there's a little chance of ambiguity between {{?without_keys(seq)}} and {{?without_keys(str)}}, because in FreeMarker value can be both a string and a sequence (or any combination of types). So maybe, {{?without_keys()}} should be the varargs variation, and {{?without_keys_seq}} the one that you must pass a single sequence to.

For the map listing filtering case, the generic solution would be {{?filter}} (because that's what should be used when listing sequences too) except that it's not yet supported on hash values. Still, if your filter condition is only about excluding given keys, then {{?without_keys}} look better for sure.

Performance... I guess it won't be a big deal in general, so I would try to stick to re-using the universal solution, {{?without_key}}.

Map subtraction... my reservation with that is that the values has no role. So, then {{map1?without_keys_seq(map2?keys)}} is perhaps better. It's longer too for sure, but assuming it's needed rarely, it at least doesn't complicate FTL even more. We just got away with using a built-in that should added anyway.


was (Author: ddekany):
Varargs is supported, but there's a little chance of ambiguity between {{?without_keys(seq)}} and {{?without_keys(str)}}, because in FreeMarker value can be both a string and a sequence (or any combination of types). So maybe, {{?without_keys()}} should be the varargs variation, and {{?without_keys_seq}} the one that you must pass a single sequence.

For the map listing filtering case, the generic solution would be {{?filter}} (because that's what should be used when listing sequences too) except that it's not yet supported on hash values. Still, if your filter condition is only about excluding given keys, then {{?without_keys}} look better for sure.

Performance... I guess it won't be a big deal in general, so I would try to stick to re-using the universal solution, {{?without_key}}.

Map substraction... my reservation with that is that the values has no role. So, then {{map1?without_keys_seq(map2?keys)}} is perhaps better. It's longer too for sure, but assuming it's needed rarely, it at least doesn't complicate FTL even more. We just got away with using a built-in that should added anyway.

> Excluding keys from hashes (without_keys/exclude_keys)
> ------------------------------------------------------
>
>                 Key: FREEMARKER-130
>                 URL: https://issues.apache.org/jira/browse/FREEMARKER-130
>             Project: Apache Freemarker
>          Issue Type: New Feature
>          Components: engine
>            Reporter: Pascal Proulx
>            Priority: Minor
>
> In several cases I need to pass a hash to a macro or function while excluding certain keys. I've had to workaround this various ways including: adding an extra parameter to hash-accepting function of keys to exclude during iteration (then every function has to implement key exclude), to a transform that creates a new SimpleHash copy without the offending keys. My last workaround was to include in the original hash itself (through concatenation) a list of exclude keys for the target macro to exclude itself and which is excluded itself, to avoid extra macro parameters. It works, but ugly.
> This could be done more cleanly with a built-in implemented similar to hash concatenation. Suggested form:
> {code:html}
> myHash?without_keys(["key1", "key2"])
> myHash?without_keys("key1", "key2") (if varargs possible; list form more versatile though)
> {code}
> Other names: ?exclude_keys...
> Most relevant example is when passing the new .args variable added by Daniel from one macro to another using ?with_args. Example based on real usage:
> {code:html}
> <#macro m1 a="" b="" c="" attr...>
>   <#-- Let's say we want to add a class -->
>   <#if !attr?is_hash><#local attr = {}></#if>
>   <#local class = attr.class!>
>   <div<#list attr?without_keys("class") as k, v> ${k}="${v?html}"</#list> class="red<#if class?has_content> ${class}</#if>"><#nested/>: ${a} ${b} ${c}</div>
>  ...
> </#macro>
> <#macro m2 a="" b="" d="" e="" attr...>
>   <@m1?with_args(.args?without_keys("d", "e")) c=3><#nested/> <i>${d} ${e}</i></@>
> </#macro>
> <@m2 a=1 b=2 d=4 id="my-div" style="display:block;">Values</...@m2>
> {code}
> Like hash concatenation it could be implemented immutably as a hash wrapper (based on ConcatenatedHashEx) that returns nothing on hash get operations and skips iteration if the queried key is within the Set (java, internally) of passed keys to exclude. The downside is the extra key check for performance. Maybe it could be optimized a different way (see _Extra 3_) but this is probably acceptable in common cases.
> This also becomes convenient if you are looping a hash with keys you want to exclude. Consider:
> {code:html}
> <#list myHash as k, v>
>   <#if k != "key1" && k != "key2">
>     ...
>   </#if>
> </#list>
> {code}
> can be simplified to:
> {code:html}
> <#list myHash?without_keys("key1", "key2") as k, v>
>  ...
> </#list>
> {code}
> which is succinct _and_ instantly readable (roughly same performance?).
> _Extra 1:_ Since you'd have ?without_keys for key excludes, you could also have ?with_keys or maybe ?only_keys or ?include_keys for key includes, though I haven't used it as much (I think I did somewhere - some of my functions have an include/exclude keys mode).
> _Extra 2:_ Optionally you could also "easily" support a substract operator between two hashes, though I didn't have a particular need for this form and is more clumsy for the purposes above:
> {code:html}
>  <#assign newHash = myHash - {"key1":"value1", "key2":"value2"}>
> {code}
> Internally this could simply reuse the hash wrapper for `?without_keys` by storing as the excluded Set keys a copy of the keys from the second hash. But this form is probably more relevant to cases where the second hash is not inlined that way and you want a logical set substract (i.e. Java Map "removeAll") operation between two hashes. The inline form like that works analogous to the concatenation operation so it would be familiar to users, but if you only want to exclude predefined keys you have to assign empty values or empty strings that have no purpose and initialize a needless hash. Worse if they come from a non-hardcoded list. So I prefer ?without_keys for my purposes.
> _Extra 3:_ The form:
> {code:html}
>  ?with_args(.args?without_keys("d", "e"))
> {code}
> does have needless performance overhead that could be avoided if the two calls could be combined into `?with_args` somehow (such that the excluded keys are passed to `?with_args` so you skip the wrapping hash), but I'm not sure this is syntactically possible in a clear way (I use some map-processing functions that take a second arg which are keys to exclude, but it's not that readable for functions, so the functions end up taking map of arguments...) or worth the effort. e.g. only way it's readable is something like:
> {code:html}
>  ?with_args(.args, exclude=["d", "e"])
> {code}
> though you can see how this would be faster. Alternatively I imagine internally you could optimize the original some other way such as having ?with_args detect when its argument is a wrapper produced by ?without_keys and special case during iteration. This is basically nitpicking.
>  
> Of course these are only suggestions including the name and implementation and based on cursory past overview of the Freemarker source. The extras are for completeness's sake while my brain is working.



--
This message was sent by Atlassian Jira
(v8.3.4#803005)