[ 
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)

Reply via email to