There was a fairly long discussion a while back #848 on maybe replacing Context with a Dict class. We decided to just remove Context and just use Str:Obj.
However, the issue of "anonymous objects" or records of String name/Obj value pairs is something that I spent a great deal of time working with when slinging around data.
The biggest issue I have right now is that it would be really nice to use -> to access a named value and get a meaningful error such as "The key foo is not found" rather than just a NullErr if the key is missing. Andy and I talked about this at length today, and wish to propose a change to Map where trap performs a key lookup instead of a reflection call on Map. Of all the solutions this seems to be the simplest rule to remember, and enables Maps to be used easily as anonymous objects:
The only downside is that you can't use any duck typing with Map. This is actually something I occasionally do for things like each or size. But compared to how often I use Map as an anonymous object, I think I can live without it.
Any votes against this change or better ideas?
Another thing I'd love to do is avoid use of quoting the keys in map literals:
rec := ["name": "Brian"]
rec := [name: "Brian"]
However, that gets ugly if name is also a local variable or method on the enclosing class. So I don't think we can really do it without some special syntax annotation on the map syntax which I don't really like either (think I'd rather just live with quoting the keys).
tacticsThu 15 Apr 2010
I could go either way on the trap method change.
I'd rather not change the keys in map literals. It's already confusing enough trying to tell locals from this slots from it slots. Let's not add even more rules for figuring out what an identifier means in a code context.
jodastephenThu 15 Apr 2010
Every special rule that is added is something to remember. This one seems too magical.
map->size // I'd expect this to be reflective lookup
Most developers will learn that -> is a reflective lookup, rather than learning it calls trap which may do anything. Thus, from everything else in Fan, its obvious what this should do. The proposal would change that, which I see as unhelpful.
One simple option is to allow the trap target to be a quoted string:
In reality, if developers choose the right type, then they would rarely use the map() or dyn() conversions.
.
On the second issue, please don't make map keys clever just because they are a string. It confuses the namespace.
I believe that this is partly a problem caused by using strings for keys - if I was coding I'd want to use enums wherever possible for the extra type safety (bigger enterprise development needs that extra safety).
Looked at in that light, whatever is supported for strings should also be supported for enums as keys. Which rules out the first option above.
More generally, Fantom is still not making best use of enums. In most locations, the enum type can be inferred. Doing so, would simplify many cases like this:
With this inference and proper use of enums, the issue of map keys needing to have quotes disappears.
Finally, I'd note that for actors, I've already said that there should be an Actor mixin like Enum that allows the developer to define "local variables", but it compiles as a map of locals.
ivanThu 15 Apr 2010
I really like the idea with map.dyn and dyn.map from the post above, but there are still some unclear moments, for example:
personMap := [ "name" : "Ivan",
"address" : ["city" : Novosibirsk]
]
personDyn := personMap.dyn
personDyn->address //Is this Map or Dyn? Can I call personDyn->address->city?
Another moment - immutability, I assume that dyn.toImmutable should return immutable dyn, and dyn.map/'map.dyn' should return projections with same immutability state.
So I think another option would be to say that -> is always call to trap and have a special syntax for reflective calls, like map#size.
brianThu 15 Apr 2010
All great comments. Let me take it from another perspective...
I think it is important that we easily support the concept of anonymous or dynamic objects. These are essentially name/value maps. In fact they are so close to the existing Map class that there is really only two problems:
you can't use dynamic call on them
a little awkward syntax for the names being quoted
I agree we should keep the keys quoted in map literals, it would be too confusing otherwise. I bring up the point to illustrate the two main differences so that we can evaluate trade-offs in various solutions.
The most basic two options are:
make Map do the duty, only issue is how to use dynamic call
create new Dict, Dyn, Anom class that does the duty (but shares virtually the same APIs as map)
Option 1 has a couple different paths:
1.A: just swizzle -> to be a key lookup instead of reflection call (my proposal)
1.B: make -> behavior configurable; problem is how to this without adding even more confusion (so I don't think this is viable)
1.C: add yet another operator
Option 2 is to embrace a new first class collection which will require a new literal syntax for the language.
So it sort of boils down to this for me: is making -> on Map a anomoly to the standard rules justified versus the complexity of adding a new operator or literal syntax to the language?
qualidafialThu 15 Apr 2010
Option 3: Make the Dyn class a wrapper over the Map, but with none of Map's API, eliminating any confusion over slot resolution in the trap method.
brianThu 15 Apr 2010
Option 3: Make the Dyn class a wrapper over the Map, but with none of Map's API, eliminating any confusion over slot resolution in the trap method.
That is what we do today, and it really sucks because all your map methods like findAll return Map which you then have to convert back to a Dict. So I think we would end up creating convenience methods to mimic the Map API:
// to Map and back to Dict ...
Dict(["a":1, "b":2]).map.findAll(|v| { v.isOdd }).toDict
// versus duplicating map API into Dict
Dict(["a":1, "b":2]).findAll(|v| { v.isOdd })
// versus just using map
["a":1, "b":2].findAll(|v| { v.isOdd })
qualidafialThu 15 Apr 2010
I see now that we're talking about Dyn / Dict for two different purposes. Dict is clearly a data structure, whereas Dyn is more about object prototyping. My earlier comments were geared toward the Dyn case and so are not really relevant to this discussion.
brianWed 21 Apr 2010
Just an update, Andy and I have beat this horse to death trying to figure out the best way to tackle it either in Fantom or elegantly as a library. No suitable solution has emerged, so we're just going to punt on it for now. But the notion of a anonymous or dynamic object is something I think we really need to add to Fantom eventually.
DanielFathWed 21 Apr 2010
I'm probably stating the obvious, but have you guys considered making Dict : Map ?
It solves nearly all conversion problems and reduces the need for toMap method.
brianWed 21 Apr 2010
I'm probably stating the obvious, but have you guys considered making Dict : Map ?
yes, but it introduces all sorts of really complicated problems:
subclassing of a generic
opening up the implementation of map (but still providing a concrete implementation for map literals)
DanielFathWed 21 Apr 2010
* subclassing of a generic
Why is this a problem? Can't you lock the subclassing by making the constuctor private or internal? Or are there other problems with subclassing generic.
Speaking of, being a Java developer I do miss the ability to specify or extend implementation of Map, List, etc. What Map implementation did you guys make? Will it be optimal in most cases if not all?
brian Wed 14 Apr 2010
There was a fairly long discussion a while back #848 on maybe replacing Context with a Dict class. We decided to just remove
Context
and just useStr:Obj
.However, the issue of "anonymous objects" or records of String name/Obj value pairs is something that I spent a great deal of time working with when slinging around data.
The biggest issue I have right now is that it would be really nice to use
->
to access a named value and get a meaningful error such as "The keyfoo
is not found" rather than just a NullErr if the key is missing. Andy and I talked about this at length today, and wish to propose a change to Map wheretrap
performs a key lookup instead of a reflection call on Map. Of all the solutions this seems to be the simplest rule to remember, and enables Maps to be used easily as anonymous objects:The only downside is that you can't use any duck typing with
Map
. This is actually something I occasionally do for things likeeach
orsize
. But compared to how often I use Map as an anonymous object, I think I can live without it.Any votes against this change or better ideas?
Another thing I'd love to do is avoid use of quoting the keys in map literals:
However, that gets ugly if
name
is also a local variable or method on the enclosing class. So I don't think we can really do it without some special syntax annotation on the map syntax which I don't really like either (think I'd rather just live with quoting the keys).tactics Thu 15 Apr 2010
I could go either way on the
trap
method change.I'd rather not change the keys in map literals. It's already confusing enough trying to tell locals from
this
slots fromit
slots. Let's not add even more rules for figuring out what an identifier means in a code context.jodastephen Thu 15 Apr 2010
Every special rule that is added is something to remember. This one seems too magical.
Most developers will learn that
->
is a reflective lookup, rather than learning it callstrap
which may do anything. Thus, from everything else in Fan, its obvious what this should do. The proposal would change that, which I see as unhelpful.One simple option is to allow the trap target to be a quoted string:
A second option is an new operator, such as:
or
A third option is the
Dyn
/'Dict' class, but I'd suggest easy conversion to and from a map:In reality, if developers choose the right type, then they would rarely use the
map()
ordyn()
conversions..
On the second issue, please don't make map keys clever just because they are a string. It confuses the namespace.
I believe that this is partly a problem caused by using strings for keys - if I was coding I'd want to use enums wherever possible for the extra type safety (bigger enterprise development needs that extra safety).
Looked at in that light, whatever is supported for strings should also be supported for enums as keys. Which rules out the first option above.
More generally, Fantom is still not making best use of enums. In most locations, the enum type can be inferred. Doing so, would simplify many cases like this:
With this inference and proper use of enums, the issue of map keys needing to have quotes disappears.
Finally, I'd note that for actors, I've already said that there should be an Actor mixin like Enum that allows the developer to define "local variables", but it compiles as a map of locals.
ivan Thu 15 Apr 2010
I really like the idea with
map.dyn
anddyn.map
from the post above, but there are still some unclear moments, for example:Another moment - immutability, I assume that dyn.toImmutable should return immutable dyn, and
dyn.map
/'map.dyn' should return projections with same immutability state.So I think another option would be to say that
->
is always call totrap
and have a special syntax for reflective calls, likemap#size
.brian Thu 15 Apr 2010
All great comments. Let me take it from another perspective...
I think it is important that we easily support the concept of anonymous or dynamic objects. These are essentially name/value maps. In fact they are so close to the existing Map class that there is really only two problems:
I agree we should keep the keys quoted in map literals, it would be too confusing otherwise. I bring up the point to illustrate the two main differences so that we can evaluate trade-offs in various solutions.
The most basic two options are:
Option 1 has a couple different paths:
->
to be a key lookup instead of reflection call (my proposal)->
behavior configurable; problem is how to this without adding even more confusion (so I don't think this is viable)Option 2 is to embrace a new first class collection which will require a new literal syntax for the language.
So it sort of boils down to this for me: is making
->
on Map a anomoly to the standard rules justified versus the complexity of adding a new operator or literal syntax to the language?qualidafial Thu 15 Apr 2010
Option 3: Make the Dyn class a wrapper over the Map, but with none of Map's API, eliminating any confusion over slot resolution in the trap method.
brian Thu 15 Apr 2010
That is what we do today, and it really sucks because all your map methods like findAll return Map which you then have to convert back to a Dict. So I think we would end up creating convenience methods to mimic the Map API:
qualidafial Thu 15 Apr 2010
I see now that we're talking about Dyn / Dict for two different purposes. Dict is clearly a data structure, whereas Dyn is more about object prototyping. My earlier comments were geared toward the Dyn case and so are not really relevant to this discussion.
brian Wed 21 Apr 2010
Just an update, Andy and I have beat this horse to death trying to figure out the best way to tackle it either in Fantom or elegantly as a library. No suitable solution has emerged, so we're just going to punt on it for now. But the notion of a anonymous or dynamic object is something I think we really need to add to Fantom eventually.
DanielFath Wed 21 Apr 2010
I'm probably stating the obvious, but have you guys considered making
Dict : Map
?It solves nearly all conversion problems and reduces the need for
toMap
method.brian Wed 21 Apr 2010
yes, but it introduces all sorts of really complicated problems:
DanielFath Wed 21 Apr 2010
Why is this a problem? Can't you lock the subclassing by making the constuctor private or internal? Or are there other problems with subclassing generic.
Speaking of, being a Java developer I do miss the ability to specify or extend implementation of Map, List, etc. What Map implementation did you guys make? Will it be optimal in most cases if not all?