Show HN: Eno – A lightning fast, user-friendly YAML/TOML alternative
We migrated a big relational research database to a file-based solution - requirements were:
- Super fast and easy editability for users
- Highest performance for parsing/validating >10K documents on every user change.
Our trials with YAML/TOML showed us that we wanted something both faster [3][4] and easier [4], something tailored for file-based content management ... and after months of research & development it's now publicly available (under MIT license) for everyone!
Last but not least I also want to mention eno's document introspection capabilities - with a few lines of code you can build intelligent relational suggestion UIs as shown in [4] below.
[2] https://eno-lang.org/libraries/
[3] https://github.com/eno-lang/benchmarks/
[4] https://eno-lang.org/resources/introspection.mp4
PS. Your input for the Roadmap is highly welcome - what do you think should be in the next releases? More languages? (If so, which? Currently in progress: Rust/PHP, Currently planned: Go/Java) Additional IDE/editor support? (Currently supported: Atom/VSCode/Sublime) Or something else entirely? :) Looking forward to your feedback!
67 comments
[ 4.6 ms ] story [ 112 ms ] threadWhy? There is a need (see above enumeration) to declaratively specify how an Eno file should look like. I do not want validation to creep into my code, like you do with `document.string('author', required: true)`. This just scares the hell out of me. Say you want to parse some Eno file with different languages, you also end up replicating validation, which mains you end up maintaining it, or rather not maintaining it... Apply leverage by moving validation into your parser.
Another thing is that it appears you are implementing the parsers by hand instead of using a parser generator that consumes a grammar for Eno. What is your reasoning behind this? Is it performance? Did you benchmark using generated parsers (maybe wrapped in a nice API)?
If there is demand or initiative for a portable schema solution I'll gladly support it! The native architecture in the eno libraries is programmatic because that does have its own powerful merits which are employed to the fullest in the API design, like always there's not one best choice, and the 'validation creeping into code' can be turned around into 'external schema definition creeping out of line with code' just as well. ;) Do you have some concrete usecase in mind or planned where we could explore how a portable schema solution could look like for eno? Drafting things from various real life use cases has worked great for eno so far, so that's the route I would love to go here too if we follow that track!
Custom parser implementation is easier to answer: By now I've iterated through dozens of custom parser designs for eno in multiple languages, and I'm pretty much confident that generated parsers will not stand a chance of being faster, they after all do the same thing as I do, only I can't really hand-optimize what they produce afterwards. :) You can study the benchmarks I linked to under [3], there are some generated TOML parsers included with rather disappointing performance to put it mildly, and as it stands there's not much that's faster than the eno parsers in YAML/TOML land anyway, so I have low incentive to experiment in that domain currently. :) Long term goal is to (optionally) integrate (generated or custom) C (respectively Rust) parser cores through native bindings as well, so that will bring up this question again then for sure.
To truly be able to have a portable file format, there needs to be a way to do both validations reliably in different contexts (eg: different languages). If you ignore this part of your design then it may become the slowest part of the eno ecosystem because your grammar will have quirks that you'll end up needing to support long-term. I suggest toying with this functionality now and providing something which is extremely pessimistic on what it will pass. Only loosen things up as people show a need and keep your entire spec as tight as possible.
I would imagine that you could even use eno syntax to describe document structure, much like xml/dtd has such strong parallels with each other. Then you get the fast parser in both places essentially for free!
Finally, on the format of eno itself, I'm curious on your thoughts relating to unicode characters that visually masquerade as common characters. eg:
http://www.fileformat.info/info/unicode/char/ff1a/index.htm
Sample usage:
---
author: Jane Doe
email: jane@eno-lang.org
---
Does this parse?
How about this:
---
author: Jane Doe:
email: jane=doe@eno-lang.org
---
What do I do if I want a "#" in a name?
---
# #twitter
@hackernews = 0xC0FFEE
---
or:
---
# \#twitter
@hackernews = 0xC0FFEE
---
Are quotes optional somehow? Can I put arbitrary things into an identifier?
Cheers and keep up the great work!
Right now only an ASCII colon is interpreted as an operator, but this looks like a question to thoroughly consider for the next and final spec (which is planned for 2019, currently we're in frozen RC) - work on this currently happens at https://github.com/eno-lang/eno.
There is escaping for arbitrary keys by using backticks - see the advanced language feature documentation at https://eno-lang.org/advanced/, in the case of # #twitter you wouldn't need it though unless you omit the space.
Thanks for your input, appreciate it!
Also you have to decide on checking in garbage generated code into your source or adding a build step so you don’t check the code in (which is less trivial for certain languages/stacks).
Unless I’m making an MVP or a prototype I would write the parser. It’s not as hard as it sounds
To that end all error messages that can occur are handwritten, fully localized and shared across all eno libraries (see https://github.com/eno-lang/eno-locales/blob/master/specific...) and the API implicitly handles them for you when you write programs that consume eno.
So basically eno does no magic fallbacks of any sort when faults occur, but it is candid and friendly about it when it happens. :)
All parse to string? If not, hopefully please add date/datetime support (better as ISO) and decimal.
---- > More languages?
How about make the core on Rust and the rest use it?
BTW: What your use for the introspection? What editor is that? I like the auto-complete stuff..
> so as a user we usually just concern ourselves with editing the values and a friendly developer takes care of specifying the names for us. :)
The friendly developer gets the job of conveying -- somehow -- that values for landline: and mobile: have to be valid tel#s (but must they have the country prefix?) while "hire date:" must be in the form yyyy mm dd only... and also gets the friendly job of writing one-off, locally unique validation code for these fields and hacking them into the parser.
Asides the absolutely valid meta solutions (e.g. in-file comments, clear key naming, documentation) there is an additional way this is approached in eno: If you use the type loaders provided by the API (say 'my_var = document.url('website')'), and properly expose errors to the user, the user will get a localized (!) error message in his language, like "'website' must be a valid url (e.g. https://eno-lang.org/)".
In the long run we can have community packages for any number of important locally unique types (loaders are just simple functions, so they can be easily authored), so at some point you likely don't have to write any one-off validation code, and neither the error messages or their localizations, you just pull it in as dependencies.
https://randomgeekery.org/2004/12/26/rebol-datatypes/
http://www.re-bol.com/rebol.html
If I would add types and type inference again, then I would essentially arrive at YAML and TOML again, and I don't want to reinvent them. ;)
But if I actually misunderstood you there, please let me know and do clarify!
JSOn is a good example:
https://www.tutorialspoint.com/json/json_data_types.htm
Is so spartan that everyone need to encode dates somehow, to make a simple example. I think this are the base types (also, my experience with RDBMs and building a relational lang now, and always having troubles with cvs, json, and others formats in ETL kind-of-task):
- String
- Floats. Can be split Ints/Floats but stick to just Float is ok. However, make it Float64.
- Date(Time). And be ISO. Not ambiguity.
- Boolean
- Decimal64. This is a pet peeve of mine. A lot of data in the business space is about money, and floats are not ok. What if like in rebool $3.2 is decimal?
Then the composites.
ie: This is json + dates/decimal. And make a single encoding (utf8?).
Is insane that, for example, you save a CVS in excel and open it again and excel get lost, and can't parse it fine.
Apart from this, url, email, host, website, phone, cellphone, city, country, state are so common that maybe with a import like "!schema:common-fields" or something.
The core might in fact be reimplemented in C or Rust and used across implementations through native bindings, although only a small portion of the actual parsing core can actually be outsourced like that, so it will depend what the actual benchmarks say then, there's also cost associated with passing around the data through bindings, the devil's in the details there unfortunately. :)
The editor in the introspection demo is atom, the introspection is based on the excellent automcomplete boilerplate at https://codersblock.com/blog/creating-an-autocomplete-plug-i... paired with a few lines that utilize https://eno-lang.org/javascript/#Section-lookup to determine the exact context for the autocomplete suggestions. Glad you like, thanks for your interest!
It's also been asked in another thread on HN, I'm quoting myself here: "eno has neither indentation nor closing tags of any sort, that means if you use a section to group some values, you need to start another section to end the previous one (no closing tags!), that's why there are fieldsets, which allow short groupings that automatically end with the next field/list/fieldset."
Hope this explains it :)
> we wanted something 1) faster and 2) easier
1) a) why didn't you write a new library using the same spec? b) do you have speed tests to show your libraries are better than existing ones?
2) how is this spec easier? (like a basic rundown)
The OP linked to their benchmarks in [3]: https://github.com/eno-lang/benchmarks/
1a) because faster was only one aspect, it also needed to be easier, even more pressingly that in fact. 1b) see answer by other poster (thanks!) 2) Not whitespace sensitive, no way to enter wrong types through syntax mistakes, hardly any learning curve for users because there is so little syntax to memorize, fully localized, hand written parser and validation errors (provided on the library side) ... and so on, check out the website for more, it's all there! ;) Thanks!
1) Unlike YAML or JSON this doesn't parse into a simple array structure, but a library dependent object hierarchy?
2) Is the API of the libraries also part of the language spec?
3) Can I assume that document.lookup() will be available as document->lookup() in the PHP library?
4) What about programming language specifics that may differ between languages? Will the PHP objects implement the Iterable interface? Or the ArrayAccess interface? (There are probably similar but slightly different concepts in other languages).
5) There is eno.parse(), but is there some kind of reverse mechanism to create a new Eno document? Like document.addList([...]).addSection('hey', 2) or something.
2) Some detail aspects of whitespace-parsing around the line continuation syntax will need to be specified by the language, the shared official API I am implementing for the different platforms is fully open to improvement and future reinvention though, I'd love to see a completely new take for a library API if it comes up in the future. :)
3) Definitely!
4) I try to keep things as consistent as possible across the platforms, but if there are important language specific paradigms I think these should be taken advantage of! I can't answer details regarding the PHP implementation yet but keep in touch, I'm happy about a dialogue here! (Also I can't be good at everything :)).
5) I want one! Obviously there can't be a stable generic "just dump it already" implementation, but a smart builder-type API is definitely on the list, I even started one for enojs but had to re-prioritize because there was so much else to do for the whole ecosystem. ;)
Your benchmarks are flawed, as they only compare between different implementations using the same language. If you really care about performance, it seems bizarre that you'd use PHP or Ruby.
For me caring about performance also means caring about performance on all platforms, why not after all? You can take the tabular benchmark data I provide and paste it together, or use the raw data that is also available as eno files in the repository to compare language against language too (which I initially also did but later dropped because same-language comparison for libraries made more sense to me), if you want the quick run down as far as I remember it: mostly javascript parsers lead the ranking, ruby parsers are a bit behind and just slightly ahead of Python.
I looked at the Python implementation but did not see that type of functionality. Am I wrong?
I've given this some thought as well, and given that the eno libraries hold their own representation of data in memory this might actually be plausible to implement in some way. Still I fear this will turn out to be a hard, hard problem (as eno is not even generically serializable by design), so that's why I haven't explored it further. So for the moment I can only say - Maybe in the near future sometime, check back every once in a while! :)
I'm curious how modern DBs failed you here, more details of the problem space please.
Cultural research = notoriously underfunded, so although they have and rely on a relational database that holds their data (previously Postgres) the cost and effort associated with maintaining and extending the system is pretty high.
With the new setup the thousands of eno files represent both the place of storage and the interface to edit the data, so by that we eliminated the development effort to provide and maintain a full web frontend to the database, and the effort to just maintain the actively deployed technology somewhere and keep it at least patched for security reasons.
All that remains now technology wise is an Atom plugin that is locally installed on each client at the institute and takes care of validating, provides relational autocomplete helpers as demonstrated in [4] and offers a few hooks to kick off local builds for multiple deployment targets and deploy them to live as well.
Hope this clarifies things! :)
Also, redhat had promised support for a while yet, and I expect spoke to keep it working until they replace with Python 3 (or remove python altogether?)
Apple also only supports very ancient set of GNU tools. That doesn't mean that in the majority of cases it is a wise choice to lock yourself to those same outdated tools - especially on a platform that hasn't guaranteed the presence of said tools.
> I don't want to have to teach people how to install python 3, when they already have python 2, which is fine.
I haven't directly addressed anything to do with your personal choices. However, this thread started with the quote:
> no python 2 support means no python support
Which frankly, isn't true. Python 2 is about to be EOL'd.
> Also, redhat had promised support for a while yet
I wouldn't depend upon it being long-term however. RHEL8 drops Python 2, and RHEL 7.5 deprecated it. [0]
[0] https://www.phoronix.com/scan.php?page=news_item&px=RHEL-8-N...
You could read a truncated TOML file and not realize it.
You can nest as deeply as you want and multiple sections on the same level automatically turn into a list of sections. For just a list of flat dictionaries you can also use fieldsets, see https://eno-lang.org/advanced/. :)
Greenspun's tenth rule rules!
So I guess there is simply no high performance toml parser implementations?? (in the tested languages, which don't include rust)