Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it easy to write literal dictionaries #592

Open
fxgallego opened this issue Jul 20, 2018 · 6 comments
Open

Make it easy to write literal dictionaries #592

fxgallego opened this issue Jul 20, 2018 · 6 comments

Comments

@fxgallego
Copy link
Contributor

I'm playing with web things and I would like to have some syntax for writing literal dictionaries.
The new Dolphin's literal dynamic arrays, like {3+4. 'Hello' reverse}, could be an easy way to solve this.
We could write {'Hello'->'World'. 'Dolphin' -> 'Smalltalk'} asDictionary/asHash/asMap. Even the syntax looks very nice. I have to admit that I would have loved to still have the opportunity to use {} characters for hashes only, but on the other hand, Pharo uses the same syntax.
Another option to avoid a message send is to use double curly brackets.

Pharo implements #asDictionary in Collection using #associationsDo: for the magic but in Dolphin that wouldn't be possible because the way Array>>associations are built.

What do you think @blairmcg ?

@blairmcg
Copy link
Contributor

blairmcg commented Jul 20, 2018

I like the general idea of having a compact form for expressing dictionaries, particularly as the normal Dictionary construction is clunky, even more so than the Array with:+, which brace arrays address. The compactness of brace arrays is nice, both for writing and reading. The small perf boost is a bonus too.

I really don't like the asDictionary thing though because it only works when the array contains associations. The correct general purpose implementation of asDictionary on a sequenceable collection would be to return a Dictionary of integer keys and the contents as values. Of course that wouldn't be terribly useful. The arrangement in Pharo is, to put it kindly, inconsistent. Pharo's Collection>>associationsDo: is based on an assumption that the contents of the collection are associations, which only applies for some instances so this is not a general purpose method that belongs in a core hierarchy; it smells badly to me. There is also a SequenceableCollection>>keysAndValuesDo: which does the right thing and passes the indices as the keys, but which is then inconsistent with respect to the inherited associationsDo:, which is assuming that the elements themselves have keys. If one is actually working with a Dictionary, then there are consistent implementations of associationsDo: and keysAndValuesDo:.
There will need to be a better way which does not make me feel unclean :-)

@fxgallego
Copy link
Contributor Author

fxgallego commented Jul 20, 2018

I agree with you about the Pharo inconsistency. But in any case you should assert all elements are associations.

Time ago I wrote something close to the idea, clean but it requires parentheses, and implement #, in Association and then you can write a dictionary this way:

('class' -> 'button'), ('data-input' -> 'true')
Is it easy to read? Maybe.
Does it resembles a Dictionary? Not really.

The best would be:
{'class': 'button', 'data-input': true}

Other option using the current Array pattern:
{{'class' -> 'button'. 'data-input' -> true}}

Are you comfortable with any of them? :)

@blairmcg
Copy link
Contributor

I like your "best" option as well from the point of view of it being compact and not confused with regular Smalltalk. I'm wary of adding new Smalltalk syntax to the compiler as it goes against the spirit of the language really. I think it has to have proven general worth to justify the reduction in simplicity that results from any addition of more syntax. That said, I'd support any effort to add a JSON format, including adding a compiler for it that could come into play for particular tagged methods. I'm less keen on an inline escape into it (e.g. using your extra pair of curly braces), but not implacably opposed. By using an alternate and well known format that is deliberately not Smalltalk we get the freedom to use different semantics. In another language we can express constant, or partially constant, structures recognised by its compiler, which would allow one to avoid dynamic execution just because that is what you have to do in order to obey the rules of Smalltalk where everything (with very few historic exceptions) is expected to be a message send, including those #<- operations to make an association out of two literal constants. Doing as much as possible at compile time to create largely constant data structures is important for many uses I think. One good example is as a replacement for STL to replace its use for view resources. STL is an (unreadable) literal format that then requires quite slow interpretation to build things, although it is completely general purpose. The current alternative options of performing a lot of dynamic/imperative construction at runtime can make everything slow in a way that is very difficult to optimize- you don't really want a peanut-buttering effect, i.e. an overhead on everything that adds a performance drag one can't eliminate, because it is a little bit here, a little bit there, adding up to a lot. If the data is constant, it should be expressible as constant literals, or at least much as possible. It is also undesirable to create a load of intermediate objects that are discarded - the most common form of hashed-key lookup collection in Dolphin is actually LookupTable, and this doesn't contain associations at all.

As I said, I'm not keen on the asDictionary conversion because these have the appearance of being very general, but are in fact not (it only works for a collection of a particular type of element). I also have to add another of these methods to the collection that is going to be converted for each type of thing I want to convert into. So I need at least asLookupTable, asIdentityDictionary, as well as asDictionary. asXXX conversions have these problems in general, and are rather over-used I think, probably because of their convenience, brevity, and the binding priority of unary selectors. Now, those are useful qualities, but not enough to outweigh the unclean feeling they give me. I wouldn't object so much to a conversion selector that didn't try to imply complete generality in the way that asDictionary does.

It strikes me that the object that knows how to consume the array of information is the target collection class, and we should be sending a message to it that asks it to produce an instance from a known input. What you are really saying, expressed in long form is Dictionary fromArrayOfAssociations: {'class' -> 'button', ...}, and one could have a similar instance creation method for LookupTable, or any other collection type one added. This could then be abbreviated using an appropriate binary selector, e.g. <<, which has the advantage of brevity and reduces the need for parenthesis, but loses information and then has some of the less desirable qualities of asXXX. I don't think it is as bad, particularly if the chosen binary selector is only used for this, or only in this context. #<< is currently used for bit shifting, and would make sense as a general stream operation as well (given existing use elsewhere for that). A class object is a factory for its instances, so "streaming" in an array of its constituent elements to create an instance kind of works conceptually.

Having written all that, and thought about it, I'd rather see a compiled JSON solution. In the meantime I'd just write:

Dictionary withAll: {'class' -> 'button'. 'data-input' -> true}

This works already, although has a number of the qualities I don't like:

  • It builds a collection at runtime that is either entirely static, or has large amounts of static data, and could have been constructed at compile time. ##() will help for the entirely static case.
  • it fails if the elements of the collection argument are not associations (or at least things that understand key and value) .
  • it will work for LookupTable, but the associations are discarded creating needless overhead.

One could add the asDictionary method to do Dictionary withAll: self, but I think there is relatively little value.

@fxgallego
Copy link
Contributor Author

fxgallego commented Jul 21, 2018

@blairmcg thanks as usual for your detailed explanation. I agree with you about everything. I'm not too worried about introducing a bit of new syntax to Smalltalk while it doesn't introduces secondary effects. The json syntax seems a bit problematic with the colon and comma characters hanging around.
My use case is not always for static data, sometimes keys and values are built dynamically. And this remembers me that there is another new syntax I like to have in Smalltalk, template strings, like in JS6. For things like building html, specifying dynamic classes or ids, both json syntax and template strings are really handy. I recently discovered the new method #?? and love it because allows me to write more concise.

As for the alternatives you've mentioned I like #<<. In Ruby it's used to append to an Array. Another option would be to add a Json subclass to LookupTable and using it this way:

Json withAll: {'class' -> 'button'. 'data-input' -> true}
or
Json << {'class' -> 'button'. 'data-input' -> true}.

This has the convenience that we don't have to implement #<< in current dictionary like classes and in addition we could provide a valid json #displayString

@blairmcg
Copy link
Contributor

@fxgallego, are template strings the same as interpolated strings in C#? It looks like they are. Yes, I've often thought those would be nice to have. Its one of those things pending replacement of the compiler - it could be done in the current compiler, but it is hard to extend. I really must get back to finishing off #119.
I was thinking that for JSON one would write it in the native form, but be able to refer to variables and such from Smalltalk, and that there would be a separate compiler for it that would emit a compiled method that would product the result much more efficiently than trying to interpret it at runtime. I like the idea of separate tagged methods that are built with a different compiler, but that might not be flexible enough. One might want to mix it up in order to be able to put in loops and such to generate collection content. It's difficult to know what would work in the abstract, however. With a compiler framework in Smallltalk it will be much easier to experiment.

@fxgallego
Copy link
Contributor Author

Yes, the same as interpolated strings. But the JS syntax is lighter, it uses only backticks and thus more suitable for smalltalk, I think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants