[
https://issues.apache.org/jira/browse/FREEMARKER-130?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17029167#comment-17029167
]
Pascal Proulx commented on FREEMARKER-130:
------------------------------------------
All that sounds fine to me.
> 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)