#281 Maps and Strings

tompalmer Fri 11 Jul 2008

One thing I love about JavaScript but not about JSON (nor about Python) is the map syntax. About 95% of the time (stats invented on the spot), map keys are strings, so I love how ECMAScript interprets identifier-like keys as strings.

For example, I'd much rather type (and read) [name: "Someone", age: 999] than ["name": "Someone", "age": 999].

However, there are those odd cases where you want a different kind of object key, so I'd recommend the $ syntax. For example, here's current Fan:

Str:Obj personWithPair(Str key, Obj value) {
  return ["name": "Someone", "age": 999, key: value, key + key: [value, value]]
}

And here's my proposal:

Str:Obj personWithPair(Str key, Obj value) {
  return [name: "Someone", age: 999, $key: value, ${key + key}: [value, value]]
}

I'd allow number literals, IDs (interpreted as Str values), Str literals ("..."), and $ expressions as keys in map literals.

Also, as this makes maps more convenient, I could imagine nice use cases like this for extra ducky occasions:

// DynObj here takes a map and intercepts slot calls for the map keys.
person := DynObj([name: "Someone", age: 999])
name := person->name

andy Fri 11 Jul 2008

My JavaScript is a bit rusty – but it's a bit different in JavaScript since a "Map" is just an Object with dynamic slots – and you can optionally reference the slots using the map/str syntax. I may be completely wrong about that – but wanted to draw attention that that seems more conceptually similar to anonymous types:

obj := { name = "Foo"; age = 99; }

Obj foo() { return { x=5; y="bar"; } }

I feel like we tossed that around in the past, but don’t really remember.

tompalmer Sat 12 Jul 2008

ECMAScript behaves as you described. I understand that Fan isn't like that.

I actually meant the "DynObj" example as a side note example (which is actually doable in Fan today, if I understand the issues correctly), but maybe it is more central to the topic than I thought. Purely dynamic languages might care more about strings that look like IDs than Fan does.

Still, if you find much code with Map keys that look like IDs, it still might be worth considering the Map key syntax I proposed. Just a thought.

gizmo Sat 12 Jul 2008

On my side, I would really prefer that, we don't allow this kind of annotation in a map as, for a statically typed language, it introduces some variation in the way one particular object is written comprae to the others.

I would really prefer that Fan has something like Clojure's symbols that you may consider as some kind of global enum where each symbols equals only itself and has itself as value.

brian Sat 12 Jul 2008

From a grammar perspective we can't start treating identifiers differently - resolving an identifier is already pretty complicated since it can resolve to a local variable or a slot in your containing class. I certainly don't want to say those rules are different only in a specific context like a map literal. And I don't really like making special cases in the grammar such as restricting what the key expression can be in the map. It is better to allow general purpose expressions everywhere, otherwise language features start to interact in bad ways instead of natural ways.

But I agree that string keys are annoying and that we could come up with a better solution. Over time I've considered some type of symbol syntax such as Ruby's :symbol syntax. That would be a general purpose feature applicable to many cases. In a map it would look like:

[:name:"Brian", :age:36]

I'm not sure I love that.

The real problem is that Fan is really built around the notion of Strs, not symbols - for example pod names, type names, slot names, are Strs not symbols. So I'm not sure how we would cleanly integrate a symbol concept.

tompalmer Sat 12 Jul 2008

I also don't see a need for symbols as a separate type. If anything, any symbol syntax should just give an interned Str.

As for the map syntax, I think people who use ECMAScript don't get confused by the ID-as-string syntax. But there all object keys have to be strings, at least in the language today (even for arrays, interestingly enough). So maybe it fits better in that world.

Still, I bring it up because I think would make the common case look nicer (at the cost of complicating the language, so maybe you are right that it's not a good trade off).

tompalmer Sat 12 Jul 2008

It occurs to me that facets are a lot like symbols, though you've overloaded the syntax for field storage reference, too. Also, using @facet="value" before a declaration doesn't perfectly match [@key: "value"] in maps. Still, maybe there's some kind of synergy available in this area.

brian Sat 12 Jul 2008

It occurs to me that facets are a lot like symbols, though you've overloaded the syntax for field storage reference

I was thinking along those lines, and would like to find the synergy. Although facets are definitely more of a declarative thing than map literal expressions.

tompalmer Mon 21 Jul 2008

I've been thinking more on facets, symbols, and maps. Facet collections really are just Str:Obj maps, right? (And that could be with or without my preference for namespacing them.) Here's a sample from the language docs:

@serializable
@icon=`/icons/account.png`
@version=Version("1.2")
@table=Table { name="Accounts" autoCreate=true }
@todo=["fix it", "really fix it"]
class Account
{
}

Here's the same with more map-like syntax. I don't think it looks worse, and it makes it clearer that what's being built is really a map.

@serializable
@icon: `/icons/account.png`
@version: Version("1.2")
@table: Table { name="Accounts" autoCreate=true }
@todo: ["fix it", "really fix it"]
class Account
{
}

And for fun, here's the full map syntax:

[
  @serializable: null,
  @icon: `/icons/account.png`,
  @version: Version("1.2"),
  @table: Table { name="Accounts" autoCreate=true },
  @todo: ["fix it", "really fix it"],
]
class Account
{
}

And without symbol syntax:

[
  "serializable": null,
  "icon": `/icons/account.png`,
  "version": Version("1.2"),
  "table": Table { name="Accounts" autoCreate=true },
  "todo": ["fix it", "really fix it"],
]
class Account
{
}

What the extreme case reminds me of is Python docstrings. That is, for API docs in Python, rather than using a comment, they just put in a string literal at the top of a definitions:

def hello():
  """This function does some really interesting things."""
  print("Hello, world!")

Putting a map literal in front of a definition in Fan has some of the same feel to me. Not to say that's the right syntax, especially for the null value case. I just feel that it lends some credibility to the idea of @symbol being the same for symbols anywhere, whether for facets or just expressions in the middle of code.

However, if you plan to support facets at the expression level in the future (such as b := @notnull someObj), then using the same syntax for both could look ugly or maybe even be ambiguous. Barring this concern, I think it would be legit to use the same syntax for both.

Again, you'd just have to find a new syntax for field storage reference. Probably some other prefix char?

tompalmer Mon 21 Jul 2008

So my summary proposal from all the above:

  1. Saying @symbol always gives you an interned Str.
  2. Change facet syntax from @icon=`/icons/account.png` to @icon: `/icons/account.png`. (But no other changes from my games above.)
  3. Use some other char for field storage such as %fieldStorage, even though that's less like Ruby.

brian Mon 21 Jul 2008

I like that. Seems pretty elegant. With that feature we can use @symbols for maps:

// today
out.writeObj(obj, ["indent":2, "skipDefaults":2])

// with symbols
out.writeObj(obj, [@indent:2, @skipDefaults:2])

Not sure about % for field storage. Since it is a prefix operator we could potentially overload a binary operator (which % actually is). Other options:

Str name { set { @name = val } }
Str name { set { %name = val } }
Str name { set { #name = val } }
Str name { set { ^name = val } }
Str name { set { :name = val } }
Str name { set { .name = val } }

Any other yeahs or nays?

jodastephen Mon 21 Jul 2008

So what exactly are we solving here? This seems to create two ways to initialize a map which have the same meaning but different syntax, one using quotes "" (that all newcomers would read and understand instantly) and one using @ (which all newcomers would have to learn).

brian Tue 22 Jul 2008

I think the root of the discussion is "are symbols useful for Fan" - they are used very heavily in Ruby, and there have been cases where a symbol makes more sense than just string literals.

andy Tue 22 Jul 2008

I'm not really on board with this. That just seems like an arcane hack to get around the root issue that its a Str based Map instead of a dynamic Type.

tompalmer Tue 22 Jul 2008

I think Brian's latest summary is a good summary of where the discussion went. I started as "I want easier map literals" and Andy's response might be a good one for that. But the answer to easier map literals became "symbols are the general purpose way to do this". Symbols in Ruby are sweet. It's easier to read and write :name than "name". Really, you get used to it super fast there.

And then I started thinking about if I could create some unification principle behind seemingly disparate Fan features.

But I think I've been convinced that @symbol for string literals isn't a good idea. I think Stephen is right that it will be too alien to most folks. On TIOBE's top 10, I think Ruby is the only one with symbols in the language. So it really won't be all that familiar compared to normal string syntax.

Still, I want (maybe only) one last try at getting namespaces into facets, and that might be another take on the symbol concept in Fan. Facets (still looking like symbols) have something in common with one other language feature: enums. So, let me try a look at that. Imagine we have a <podName>.fan file in a project with (among other things), the following "anonymous" enum:

enum {
  @icon,
  @version,
  @todo
}

There's no defined type for their values (since Brian didn't like that). Instead, they are pod-scoped enums/symbols (rather than within a specific enum type). And if using podName, then @icon really means a reference to @podName::icon.

Again, you could use @symbol anywhere in code (and field storage syntax would again need changed), but it's a reference to the pod-global value, and facet maps would be Symbol:Obj instead of Str:Obj.

Maybe not a good idea. Just some thoughts. (And I guess I still really want that auto-namespacing.)

I think it might be good to change field storage syntax independent of any of this, too, just for clarity, but I guess that's not the main point here.

jodastephen Tue 22 Jul 2008

I think the <podName>.fan concept (or similar named file) is a good one if we have something to put in it. Java has a package java concept, so this isn't alien.

One use for such a pod-scoped file could be to define which other pods are imported by default within this pod. This would centralise dependency management. Effectively it promote build.fan to something more meaningful.

On Tom's point, I am also concerned about the lack of namespacing for Facets. It may be fine for small systems, but won't be helpful for larger systems.

brian Tue 22 Jul 2008

My problem is not with declaring them at the pod level (you would do that in the build script).

My problem with implicit pod namespace scoping is that you have to introduce some new naming concept or else facets have to be declared as types. Then you have to figure out how to deal with using, qualified names, etc.

So I still lean towards just simple string/object maps with a naming convention of prefixing with pod name for avoiding conflicts. That model tends to work just fine in things like Java properties files.

I can't justify the extra complexity. Today I just do this:

@fluxView=File.type
class HexEditor : View {...}

The facet is uniquely prefixed with the pod name.

There is a part of me that thinks namespace scoped facets are a good thing, but the practical part of me thinks it is overkill.

andy Tue 22 Jul 2008

I agree with Brian, that is a problem easily solved using the pod name prefix convention, no need to complicate the current simple model.

jodastephen Tue 22 Jul 2008

The problem would be long term in large sourcebases:

@comFooBarProductAccountsModelMaxLength

Namespaces are generally a Good Thing. I suspect if you don't add this now, you'll probably eventually have to.

tompalmer Wed 23 Jul 2008

Here's an entirely different formulation to symbol/string/facet namespacing that I rejected. Just mentioning it here to show some thoughts I went through.

To avoid predefining symbols, I figured you could say @@symbol to refer to a symbol in the current pod's namespace. So @@symbol means @thisPod::symbol, and it would be required unless you gave the pod name explicitly. Also, in this version, symbols are just interned Str objects again. So @@symbol is the same as "thisPod::symbol".intern.

To use someone else's symbol, you say @symbol or explicitly @otherPod::symbol which means the same as "otherPod::symbol".intern.

I rejected this on the basis that @@symbol is a pain, and as long as you say it only once in your own pod, it proves the point that you want your own. If you say it only once, then you might as well have a standard place to say it, and that led me again to the prior idea of:

@enum {
  symbol,
  andAnother
}

Note that I changed the @ location here to allow facets on the members. Also, maybe the list in "build.fan" is sufficient instead of custom syntax like this.

brian Wed 23 Jul 2008

I like where you are heading (which I think is in conjunction with namespace aware facets). If someone can proposal a simple design which deals with namespace aware symbols (or facets), then I would like that idea.

tompalmer Wed 23 Jul 2008

I'll keep thinking about it. (Can you tell I want this feature (namespacing, specifically)? I'll try to find what will fit in best to the Fan world view.)

f00biebletch Mon 22 Sep 2008

I'm not sure I see the value of symbols/atoms - they are great for pattern matching in erlang and scheme, wonderful as part of tuples, and a great space saver in ruby, but I just don't see a compelling need for them in Fan.

Login or Signup to reply.