I think what you describe is called "extensible records". Elm had them in a prior version. You can implement them in a language with an expressive enough type system (e.g. Haskell or Scala) without special support, but the syntax can be a bit unwieldy.
I'm not claiming these are definitive in any sense, but they are useful guides into the literature if any reader wants to learn more. (If you are not used to reading programming language papers, the introduction, related work, and conclusions are usually the most useful. All the greek in the middle can usually be skipped unless you are trying to implement it.)
This is often referred to as "row typing", and the only language I've ever used that implemented it first-class with polymorphism is Purescript[0]. It's possible to model such things in other languages with sufficiently powerful type systems, though they're normally not as nice to use.
As an aside, Purescript is one of the most fun languages I've ever used for frontend work, and I lament that Elm seems to have overtaken it in the FP community.
The construction of the type 'Map<string, Type>' is entirely standard in languages like Agda and Coq (and I bet Idris too). In these languages, Type is itself a type, and can be treated like any other type (like string or int). Nothing clunky about it. (If you're curious, Type is usually referred to as a "universe type" in type theory circles.)
The Ceylon language allowed you to do that sort of thing with types.
If you "added" two maps together of types `Map<String, int>` and `Map<String, String>`, you would get back a `Map<String, String | int>` (or, equivalently, `Map<String, String> | Map<String, int>`).
But for some slightly complex reasons, most language designers find adhoc union types (which are required for this to work) a bad idea. See the Kotlin work related to that, they explicitly want to keep that out of the language (and I've seen that in other language discussions - notice how tricky it can be to decide if two types are "the same" or whether a type is a subtype of another in the presence of generalized unions) except for the case of errors: https://youtrack.jetbrains.com/issue/KT-68296/Union-Types-fo...
> If you "added" two maps together of types `Map<String, int>` and `Map<String, String>`, you would get back a `Map<String, String | int>` (or, equivalently, `Map<String, String> | Map<String, int>`).
But these are obviously not equivalent: the first type is a map where all values are either strings or ints, and the second one is either a map where all values are strings, or all values are ints.
If that's confusing, consider: {foo: 1, bar: "2"}. It satisfies `Map<String, String | int>` but not `Map<String, String> | Map<String, int>`.
(In fact, the latter is a subtype of the former.)
P.S. You also seem to have misunderstood the toplevel comment about modeling record types as maps, as being about typing maps that exist in the language.
These types are equivalent if you consider a value of both of these types have the exact same read operations. Calling `get(String)` will return `String | int` in both cases. You're right that you could build a value of one of these types that does NOT conform to the union, however. I am not sure what's the technical name for what I am trying to say... are they "covariantly equivalent"???
EDIT: ok, I just wanted to say that one type, `Map<String, String | int>`, is a supertype of `Map<String, int> | Map<String, String>`, so if a function accepts the former, it also accepts the latter. They're not equivalent but you can substitute one for the other (one way only) and still perform the same operations (always assuming read-only types, if you introduce mutation everything becomes horrible).
I was trying to emphasize how having type "combinations" ends up causing the type system to become undecidable as you end up with infinite combinations possible, but as I haven't really gone too deeply into why, I am having trouble to articulate the argument.
Ocaml object system can also achieve this in a quite lightweight way
type foo = < foo:int >
type bar = < bar:int >
type k = < foo; bar >
type u = < k; baz:int >
let f (x: <u; ..>) (\* the type annotation is not needed \*) = x#m
You can easily do composable tagged (by index type or even comp time strings) tuples in C++. The syntax is not pretty, but there is minimal to non-existent overhead.
On the other hand, thanks to the loose "duck typed" nature of templates, you can write functions that act on a subset of the fields of a type (even type checking it with concepts) while preserving the type of the whole object.
Go structs sorta behave this way if you put a "parent" struct in the first field of a struct definition. They are, of course, not maps though. But a map with statically defined keys/values/types is basically a struct.
This doesn't function as an interface though. You cannot pass a FooBar to a function that expects a Foo, for example, and although you can fairly easily reference the Foo-part of a FooBar instance (`foobar.Foo`) there is no way to pass e.g. an array of FooBar instances to a function that takes a slice of Foo[] as its argument. That's the problem to be solved.
Totally achievable by using interfaces instead of structs
type Foo interface {
Foo() int
}
type Bar interface {
Bar() string
}
type FooBar interface {
Foo
Bar
}
Then functions that accept a Foo will also happily take a FooBar. Does not solve the problem of passing a FooBar[] to a function that expects Foo[] but that can be solved with generics or a simple function to convert FooBar[] to Foo[].
That sounds achievable at compile-time in c++ with some type-level map implementation such as in boost.mp11 (sadly the language does not allow general use of types as values preventing the use of standard map containers).
- Grammar, parser, compiler, interpreter (delete as appropriate)
- Editor plugins for nice syntax highlighting
- Language server
- Packages for common things
- Nice website (or no one will use it)
- etc...
So the pressure is always to shoe-horn a big existing language into you problem. Maybe you can build a nice library if your language has decent syntax (or little to no syntax). If you have an AST representation, you probably dump it to JSON etc.
I am curious if any projects are trying to make this easier.
Charles Simonyi and his Intentional Software tried to solve this, publishing some interesting articles in the 1990es. However their technology was not broadly used and they were acquired by Microsoft.
The key ideas are called Intentional Programming and Language Workbenches.
The best accessible implementation of that is JetBrains’ MPS (it is free). It allows you to define a language and “projectional” editors together.
It is really fascinating but it suffers from a learning curve where there is no small step from what people use in their everyday common languages and IDEs to building domain-specific solutions with MPS, so adoption is low.
Markus Voelter has some highly recommendable publications and elaborate applications of MPS for domains specific languages, see http://voelter.de/
I am sure there is something great in that area but it has not found the right form and shape yet, so keep exploring.
A rare mention of Intentional Programming aka IP (https://en.wikipedia.org/wiki/Intentional_programming) on HN. I first came to know of this from an article by Charles Simonyi titled "The Death of Computer Languages, The Birth of Intentional Programming" on MSDN. But alas, the promise never came to pass. The only other place i know of which covers it is a chapter in the book Generative Programming Methods, Tools, and Applications by Krysztof Czarnecki et al. IP is rather hard to understand (i still don't get it completely) and afaik there are no publicly available tools/IDEs to learn/play with it.
I don't believe Jetbrains MPS is a IP programming editor, it is meant for designing DSLs. IP has aspects of a DSL but is not the same.
Finally a huge upvote for mentioning Markus Voelter who is THE Expert in DSL design/implementation/usage. Checkout his articles/essays and the free ebook "Domain Engineering: Designing, Implementing and Using Domain-Specific Languages" from his above mentioned site.
The original Intentional Programming in the mid-nineties was a much broader vision than Language Workbenches, more like a grand unified theory of software development and related tools such as IDEs, languages, compilers, and a marketplace of components in that space.
My understanding, from the demos they were giving around 15 years ago, is that the Intentional company ended up focusing on a smaller feature set similar to MPS (I don’t have personal development experience with the Intentional product, only MPS).
It would be interesting to learn more about their work and lessons learned.
GraalVM's Truffle languages try to make this easier. You still have to write a parser and an interpreter. But from these you get a compiler for free. Plus tools like a debugger, I think maybe a language server as well. And your language can easily call out to the Java standard library, which solves the problem of standard packages. But you're tied to Java.
Building a small language is super easy. You can even just use JSON as a functional but ugly stand in for the syntax. However I usually just write a quick recursive descendant parser. It is easy and quick to do once you know how to do it.
Mongodb and dynamodb are completely different dbs. One is unlimited scale KV but very expensive , another is document nosql db that sells you idea “it just works” for lots of features , indexes on anything , aggregation , time series . Vector DB, sharding , replicas etc . It’s a very powerful db for sure.
You can use a configuration management tool but you can also just have a bundled archive that is deployed and extracted with SSH. Here's one example: https://community.chef.io/tools/chef-habitat
What we want to express here is an object with a map of properties (name to type):
For the OOP minded: And also compose those: But at compile-time, of course.Have any languages achieved this? I know TypeScript can do some of these things, but it's clunky.