I lost a fair bit of time today wrestling with the subtleties of Groovy Maps.
Take a look:
def val = 99
def map = [:]
map = ["fred$val":val]
println map["fred$val"]
// => null
map = [("fred$val"):val]
println map["fred$val"]
// => null
map = [("fred$val".intern()):val]
println map["fred$val"]
// => 99
// compilation error...ok, expected
//map = ['fred' + val:val]
//println map["fred$val"]
map = [('fred' + val):val]
println map["fred$val"]
// => 99
map = [("fred$val".toString()):val]
println map["fred$val"]
// => 99
def s = "fred$val"
map = [(s):val]
println map["fred$val"]
// => null
s = "fred$val"
map = [(s):val]
println map[s]
// => null
map = [:]
map.put("fred$val", 99)
println map.get("fred$val")
// => 99
The amount of strangeness shown here is all too much for my liking…
On leafing through the Groovy ‘bible’, GINA, I found the possibility of a hint of a reason:
There is also a corner case with GStrings if their values write themselves lazily.
Which led me to the Groovy site on Strings and GStrings, which gives the salient advice:

So, now I know.
It’s partly (mostly?) my misconception…or foolishness: expecting a String to be a GString.
Well:
How can I possibly put a new idea into your heads, if I do not first remove your delusions?
“Doctor Pinero” in Life-Line (1939)
I now carrying one less delusion around with me. This is good.
Not sure I am any happier, TANJ it!
Here’s a debugging nightmare:
final val = 99
println """
GString:"""
def map = [:]
// NB: without toString()
map.put("fred$val", '?')
println map.get("fred$val")
println map.get("fred99")
println map.get("fred99".toString())
println map.get('fred99')
println map["fred99"]
println map['fred99']
println map[("fred99")]
println map[('fred99')]
println map['fred' + 99]
println map["fred" + 99]
println map[("fred" + 99)]
println map[("fred" + 99).toString()]
map.each {k,v ->
println "$k=>$v"
}
println """
String:"""
map = [:]
// NB: with toString()
map.put("fred$val".toString(), '!')
println map.get("fred$val")
println map.get("fred99")
println map.get("fred99".toString())
println map.get('fred99')
println map["fred99"]
println map['fred99']
println map[("fred99")]
println map[('fred99')]
println map['fred' + 99]
println map["fred" + 99]
println map[("fred" + 99)]
println map[("fred" + 99).toString()]
map.each {k,v ->
println "$k=>$v"
}
println """
Buyer beware:"""
map = [:]
map.put("fred$val", val)
map.put("fred$val".toString(), val)
map.each {k,v ->
println "$k=>$v"
}
Here’s what you get:
GString: ? null null null null null null null null null null null fred99=>? String: null ! ! ! ! ! ! ! ! ! ! ! fred99=>! Buyer beware: fred99=>99 fred99=>99 Result: [fred99:99, "fred99":99]
TANSTAAFL, I guess…you win some, you lose some and this is but a small entry on Groovy’s otherwise clean “rap sheet.”
[edit]
Jim Shingler (who writes at: Shingler’s Thoughts) sent me the following alternative:
def val = 99 def map = [:] map."fred$val" = val println map println map["fred$val"] println map."fred$val"
Seems to be a little more robust, thanks.
The takeaway message seems (to me) to be: “Don’t use GStrings as keys in a map. Especially don’t use GStrings with embedded placeholders because their lazy evaluation may make life awkward.”
