-
Notifications
You must be signed in to change notification settings - Fork 856
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
Tuples and Inline Tables: A Motivation #219
Comments
For what it's worth, this would significantly clean up the biggest areas (so far) where Toml has been an awkward fit for Cargo. |
I should also note that you get a pseudo-map syntax that you can use in any value position for free via: |
I am torn about tuples. I very much wanted it >1 year ago (I even implemented it in a separate and long-obsolete branch), but part of me feels like too much time has passed. In my view, adding tuples implies fixing arrays to be properly homogeneous. That would make this existing TOML invalid: Of course, we could add tuples without fixing arrays (this would be backwards compatible), but then we lose some advantages of the type safety that it would bring us and arrays and tuples become less orthogonal. In any case, I am pretty solidly opposed to inline maps. TOML has a very nice property that there is only one way to construct a map. It'd be great if we could stick to that. The cost here is some vertical space, which I don't think we should be afraid to pay. With that said, if we added tuples (regardless of fixing arrays), then you could have your inline map by using an association list: key = [("version", "1.0.0"), ("git", "git://...")] If that would be an acceptable compromise, then we could throw inline tables out and focus on whether tuples (and maybe fixing arrays) is worthwhile or not. |
I'm obviously open to a compromise solution, but I think the inline table solution presented above is both well-motivated and far more ergonomic than the just-tuple approach to the problem. Is there a reason you object to the limited inline table above? Is there a particular form of abuse you're worried about? [UPDATE] I missed some of your initial response. I don't think that "there is only one way to construct a map" is actually that nice of a property. Can you explain some of the benefits you think it provides and what problems you feel a limited form of inline table might introduce? |
It's not necessarily about abuse, it's about keeping the specification as simple as possible. Stated differently, keep features orthogonal as much as possible. The benefit of inline tables over existing TOML is less vertical space and the benefit of inline tables over TOML w/ tuples is fewer parens and quotes. My feeling is that these aren't enough of a benefit to sacrifice orthogonality. Certainly, reasonable people may disagree. Perhaps we need more opinions? @mojombo? Others? |
I definitely like the ability to define heterogeneous tuples, and with the original restriction mentioned adding inline tables would keep the feature sectioned off to only enable use cases that are otherwise impossible. For example, if tuples were added, an array of tables is possible to express, but a tuple with a table would be impossible to express. The inline tables mentioned would allow you to only have a table at the end of a tuple, but delimiters could likely be introduced to allow tables in the middle (and the delimiters are optional for the last table). It seems that if inline tables are left out then of the three container types (tuples, arrays, tables), only tuples cannot contain other tables. On the other hand, if inline tables are added, they'd likely only be accepted in tuples, which seems like a similar gotcha. The succinctness of inline tables at the end of tuples is indeed quite nice, and it likely opens up the door to a bunch of interesting forms of configuration (as seen for cargo), so I'd be slightly in favor, but I can see where it may not necessarily fit in quite well with the rest of the language so far. |
For what it's worth, I think the before/after is pretty compelling: Before[dependencies.hamcrest]
version = "0.5.0"
[dependencies.hammer]
version = "1.0.0"
git = "https://github.com/wycats/hammer"
branch = "wip"
[dependencies.simplediff]
version = "0.2.0"
git = "https://github.com/carlhuda/simplediff"
tag = "v0.2.0" After[dependencies]
hamcrest = "0.5.0"
hammer = ("1.0.0", git: "https://github.com/wycats/hammer", branch: "wip")
simplediff = ("0.2.0", git: "https://github.com/carlhuda/simplediff", tag: "v0.2.0") |
YES! IT IS! Considering that in a "normal" project you'll ended up with more dependencies your proposal looks much more important. |
Please also consider what it would look like as an association list: [dependencies]
hamcrest = "0.5.0"
hammer = [("version", "1.0.0"), ("git", "https://github.com/wycats/hammer"), ("branch", "wip")]
simplediff = [("version", "0.2.0"), ("git", "https://github.com/wycats/hammer"), ("tag", "v0.2.0")] I'm personally not that miffed by the "before" version, but I recognize that that is up to taste. @alexcrichton You make some good points. Personally, if we're really going to go down the path of inline tables, then I'd rather not dance around it and add a real dictionary syntax: |
I think the association table is clearly less ergonomic: Association Table[dependencies]
hamcrest = "0.5.0"
hammer = [("version", "1.0.0"), ("git", "https://github.com/wycats/hammer"), ("branch", "wip")]
simplediff = [("version", "0.2.0"), ("git", "https://github.com/wycats/hammer"), ("tag", "v0.2.0")] Inline Tables[dependencies]
hamcrest = "0.5.0"
hammer = ("1.0.0", git: "https://github.com/wycats/hammer", branch: "wip")
simplediff = ("0.2.0", git: "https://github.com/carlhuda/simplediff", tag: "v0.2.0") Additionally, you now have to communicate to the decoder that you intend to represent a table with the array of tuples somehow. We could support inline dictionaries, but I think a rule that allows eliding |
Allow me to step back and wonder about the fundamental utility of a tuple. Tuples are ordered, typed data. Useful for things like expressing xy coordinates And yet, the brevity and simplicity of tuples aligns with the minimalist aspirations of TOML. What to do!? I propose we forget tuples and embrace single-level inline tables. Then we get both minimal AND documented data: coords = { x: 2.3, y: 5.9 }
person = { name: "Tom", age: 35, location: "Iowa" } By limiting inline tables to a single level, we keep them simple. We no longer have to worry about 0-tuples or 1-tuples or such nonsense. They map to an unambiguous dictionary, just like everything else in TOML. They eliminate the need for a new data type and complex syntax. @wycats Would this serve your needs? |
👍 |
@mojombo I can get on board with that. I like your reasoning about tuples! Let's try flushing it out. To be clear, this would be disallowed? person = { name: "Tom", geography: {"lat": 1.0, "long": 2.0}} Also, this: person = { name: "Tom", age: 35, location: "Iowa" } Should have precisely the same representation as [person]
name = "Tom"
age = 35
location = "Iowa" Right? The single level restriction is interesting, otherwise a valid TOML document could start to suspiciously look like JSON. Consider what would happen if we had nested inline dictionaries: key = {
a: {
b: {
c: 5
}
}
} A few more clarifications:
|
I think I can get on board with that. Let's do a quick comparison: Inline Table + Tuple[dependencies]
hamcrest = "0.5.0"
hammer = ("1.0.0", git: "https://github.com/wycats/hammer", branch: "wip")
simplediff = ("0.2.0", git: "https://github.com/carlhuda/simplediff", tag: "v0.2.0") Inline Table Only[dependencies]
hamcrest = "0.5.0":
hammer = { version: "1.0.0", git: "https://github.com/wycats/hammer", branch: "wip" }
hammer = { version: "1.0.0", git: "https://github.com/carlhuda/simplediff", tag: "v0.2.0" } The primary benefit of the inline tuple version is that it allows stretching the single-value right-hand-side to additional configuration. @mojombo I think it's worth considering this kind of scenario, where a single value represents some default concept (like a version here), and you may not want to name it simply because you're expanded into dictionary form. That said, I can appreciate that it might be abused. You tell me! |
Correct, that is disallowed.
Correct.
Yes, that's the idea. This syntax is designed to clean up what would otherwise be overly verbose configurations. I definitely don't want them abused to flesh out hierarchies.
Let's see what it looks like with person = { name = "Tom", age = 35, location = "Iowa" } I'm not convinced. I'm worried that being so similar to the normal table syntax will encourage people to always use this format when they should really be using the regular table syntax instead. The colons make it feel like a slightly different, more cohesive object, rather than just a more concise table syntax. I think there's value in that mental distinction. But it does introduce more complexity in key naming rules. @wycats What's your take on this?
Hmm, I could be convinced either way on this one. There is definitely utility to allowing it, for collections of very simple objects, but you're right about the crazy nesting it would allow. I'm actually leaning towards allowing it, though, mostly because I like the consistency of just saying that an inline table is a value and can be used in the same way as any other value. At some point we have to assume that users will be reasonable in how they write their config files. |
I agree with you. I think there is value in keeping the two syntaxes different. I also think that the |
I think this should be a guiding principle here. If we provide facilities for sane config files, people will likely use them. Today, it's possible to represent your entire config as a single string-encoded JSON, but people don't usually do it. I think the ergonomics of "sections" for most cases will clearly win out over people trying to do weird JSON stuff, but there are real cases for small dictionaries in different positions that will win out when the ergonomics favor it. In other words, people will lean towards doing sane things, all else being equal. |
Question: is the current status quo heterogeneous Arrays? |
While I understand the utility of this added functionality, I am concerned that it might cause confusion for the TOML user. Curly braces are so familiar (especially with JSON) that it I don't think it will be obvious why TOML would only allow a single level when using them. Anybody coming from JSON will assume curly braces denote an object, and objects can be multi-leveled. Would it perhaps be better to use parenthesis instead of curly braces? When I see this:
...it strikes me as a special TOML syntax and I'm more prone to understanding why I can't do this:
|
But TOML is not JSON. I come from Ruby, and I don't have a strong opinion on which way this decision goes, but I think that assuming TOML should be like any other format is a red herring. |
@steveklabnik A good point, to be sure. Though I wasn't assuming TOML should be like any other format, and rather pointing out that users might assume TOML shares conventions with other formats. In most data structures that use I'm not really advocating for parenthesis, either. I'm just trying to think of a way to format the syntax of this particular feature that makes it more apparent that it has a single-level restriction. The curly braces format is so familiar (including Ruby with |
@wycats Arrays are homogeneous. The following are not valid TOML values: |
I'll just make my case and then defer to you. I'd personally take
Compared to something like this:
We already have two subtly different syntactic categories for keys (keys + table names). This would add a third. Is it a huge deal? Probably not. But I want to give spec simplicity a voice. FWIW, I actually prefer the
We can't quite say that though. We have to say that inline dictionaries can be used like any other value except they may not be used directly as a value in another inline dictionary. And we would assuredly have to clarify that, e.g., [[key]]
sub = 5
I'm kind of torn on this one too. But if we're going to give users the alternative to use inline dictionaries to write table arrays, then we might as well go whole hog and let them use inline dictionaries to write nested hashes. So we've got three options on the table:
|
Both (2) and (3) have perfectly good longer-form representations that would often be appropriate by the time you're nesting. Instead of: bins = [
{ name: "conduit", path: "src/main.rs" },
{ name: "routable", path: "src/examples/routable.rs" }
] You can do: [[bins]]
name = "conduit"
path = "src/main.rs"
[[bins]]
name = "routable"
path = "src/examples/routable.rs" My gut feeling is that once you get to multiple levels of nesting, you're better off expanding into long-form anyway. That said, that's probably being too paternalistic here, and we can let people decide on a good trade-off between terseness and clarity for themselves, I think. |
A lot of ^^ probably has to do with how many keys are involved. An array of tables with a small number of keys (2-3 max) seems totally reasonable inline. An array of tables with 10 keys would definitely be inappropriate for inline treatment. |
@wycats You're probably right about the relationship between # of keys and whether to use inline maps or not. But probably not a good idea to codify that in the spec. (Wasn't sure if you were suggesting that or not.) |
I like the idea and proposed syntax of single-level inline tables 👍 |
No, definitely not. |
Maybe just allowed inlined sections. [person] name = "Tom", age = 35, location = "Iowa" Or more to the need at hand. [dependencies]
[dependencies.hamcrest] version = "0.5.0"
[dependencies.hammer] version = "1.0.0", git = "https://github.com/wycats/hammer", branch = "wip"
[dependencies.simplediff] version = "0.2.0", git = "https://github.com/carlhuda/simplediff", tag = "v0.2.0" It can be all lined up real nice like if you want. [dependencies]
[dependencies.hamcrest] version = "0.5.0"
[dependencies.hammer] version = "1.0.0", git = "https://github.com/wycats/hammer", branch = "wip"
[dependencies.simplediff] version = "0.2.0", git = "https://github.com/carlhuda/simplediff", tag = "v0.2.0" Or made something in between. [dependencies]
[dependencies.hamcrest]
version = "0.5.0"
[dependencies.hammer]
version = "1.0.0", git = "https://github.com/wycats/hammer", branch = "wip"
[dependencies.simplediff]
version = "0.2.0", git = "https://github.com/carlhuda/simplediff", tag = "v0.2.0" Does it satiate the @wycats? |
@trans This seems worse than any of the other proposals :/ |
LOL 😄 Well, I figured it couldn't hurt to try a minimalist approach. (I'll just quietly tip-toe back over to my YAML world now). |
@trans sorry for being curt 😉 |
In defense of @trans 's proposal I would like to point out that, together with my equally dead proposal to allow newlines as alternative element separator in arrays (#227), it would offer a pretty flexible and simple unified model for writing tables and arrays. Roughly put:
This would allow writing tables like this:
This:
This:
Or even this:
And arrays like this:
This:
This:
Or even this:
In either case, the "even" form would be bad taste and should be avoided, but it would pose no problems for the parser. I don't expect that this proposal will be accepted but still want to point out that it would be quite simple and yet powerful -- certainly simpler than adding a whole new syntax for inline tables, while offering similar benefits. |
Compare: With inline dictionaries[dependencies]
hamcrest = "0.5.0":
hammer = { version: "1.0.0", git: "https://github.com/wycats/hammer", branch: "wip" }
hammer = { version: "1.0.0", git: "https://github.com/carlhuda/simplediff", tag: "v0.2.0" } With inline sections[dependencies]
[dependencies.hamcrest] version = "0.5.0"
[dependencies.hammer] version = "1.0.0", git = "https://github.com/wycats/hammer", branch = "wip"
[dependencies.simplediff] version = "0.2.0", git = "https://github.com/carlhuda/simplediff", tag = "v0.2.0" I think inline dictionaries are clearly better here. To my eyes, the alternative crosses the acceptable noise barrier. |
@ChristianSi Nicely done 👊 @wycats Out of curiosity, [[dependency]] name = "hamcrest", version = "0.5.0"
[[dependency]] name = "hammer", version = "1.0.0", git = "https://github.com/wycats/hammer", branch = "wip"
[[dependency]] name = "simplediff", version = "0.2.0", git = "https://github.com/carlhuda/simplediff", tag = "v0.2.0" Hmm... ok, bit of a stretch but maybe it would even be possible to have array-table eliding, [[dependency]] "hamcrest", version = "0.5.0"
[[dependency]] "hammer", version = "1.0.0", git = "https://github.com/wycats/hammer", branch = "wip"
[[dependency]] "simplediff", version = "0.2.0", git = "https://github.com/carlhuda/simplediff", tag = "v0.2.0" |
@ChristianSi Here's the issue. It seems to me that the whole point of adding inline tables is for aesthetic reasons. Your proposal has an interesting uniformity in syntax, but it isn't very aesthetically pleasing IMO. You've added optional commas to places that don't need them (key-values) and make commas optional in places that traditionally have them (arrays). My personal view leans toward rejecting inline tables. However, I agree with @wycats that inline tables can really reduce noise in legitimate use cases. I agree with his before/after. I'm just not sure if it's important enough to add inline tables. If we do go with inline tables, then I fully support the |
👍 Agree with @BurntSushi, including preferring to not add inline tables. I can see the benefits in edge cases, but I feel like it duplicates functionality in the spec. |
@redhotvengeance I really do not agree that my motivating cases here represent edge-cases. |
@BurntSushi I am comfortable starting off with non-nested inline tables, and seeing whether we find a strong motivation for supporting further nesting. At the moment, I find it hard to believe that you could reasonably fit nested tables in a single line, and I think using inline tables for multi-line cases is probably an anti-pattern. |
I don't agree. Admittedly the simplicity of the proposal would allow ugly variants, but nobody would be forced to use them. My personal aesthetic preferences would be:
So, I would write tables like this:
Or this:
And arrays like this:
Or this:
I think that's pretty aesthetic. (Indeed, my original motivation for proposing newlines as alternative array separators was purely aesthetic.) I do agree that the |
I find that when there are no simple answers it's best to focus on the big goals of TOML: obviousness and minimalism. I'll endeavor to ask the outstanding questions here, and then answer them with those principles in mind.
Everything added to TOML makes it less minimal. But, like Einstein said, things should be as simple as possible, but not simpler. I think @wycats makes a very compelling case for the existing TOML spec being too simple. So I'm still +1 on the idea.
At first I thought that colons would be better, but I think that's just because I'm used to them from Ruby. After sitting with the various syntax forms for a while I don't mind them with
I'm going to say yes. It's the answer that's most obvious and means there's less to explain. Inline tables should just be values, treated like all other values. This means that people could abuse them, but I think we should optimize for the best case, not the worst case.
Yes, they are just values.
If we want to prevent abuse, then this seems like the best way to do it. I think people will naturally avoid long lines, and thus avoid using inline tables for undesirable nesting and other inappropriate uses. This dovetails nicely with the previous two answers, allowing for consistent value semantics while only constraining formatting.
I don't like that option, it feels weird to me. Every key should have an explicit value, period. Obvious. Minimal. Unambiguous mapping to a hash table is the most important thing that TOML will ever have. |
I can live very well with everything @mojombo said and withdraw my own proposal. (Not that I seriously expected it to be accepted anyway.) |
Same. I can get onboard with @mojombo's proposal. 👍 |
👍 on @mojombo's conclusion 😄 Let's get this merged so we can get to implementing it! |
Just a quick sanity check for posterity: [dependencies]
hamcrest = "0.5.0":
hammer = { version = "1.0.0", git = "https://github.com/wycats/hammer", branch = "wip" }
hammer = { version = "1.0.0", git = "https://github.com/carlhuda/simplediff", tag = "v0.2.0" } |
It's slightly more verbose and the |
👍 @mojombo. I just want to flesh out one thing:
So would something like this be disallowed? key = { some_array = [
1, 2, 3, 4
] } If not, then the "new line" restriction would only apply to the syntax of I think I like allowing my example above because it avoids restricting other values inside inline dictionaries. I'm happy either way though. |
@BurntSushi Sounds like the idea is that the opening |
(so yes, disallowed) |
I've opened PR #235 to push this forward. Closing this issue; all future conversation should happen over there. |
This builds on #154, which would be very useful regardless of whether this proposal is accepted.
Currently, there is no syntax for inline tables, which means that you're forced to create a new section for simple cases. In Cargo:
We have a shorthand for the case where a dependency only needs to specify a version:
With tuples, we could do something like:
I would like to propose a syntax for inline tables:
I would be comfortable with restricting inline tables to the last element of a tuple, to eliminate the need for additional braces or something around the table.
The text was updated successfully, but these errors were encountered: