Hacker Newsnew | past | comments | ask | show | jobs | submit | posix_monad's commentslogin

MLs require a lot of ceremony modelling simple record types.

What we want to express here is an object with a map of properties (name to type):

    string Type map
For the OOP minded:

    Map<string, Type>
And also compose those:

    type Foo = { "_foo", int }

    type Bar = { "_bar", string }

    type FooBar = mergeMaps Foo Bar
But at compile-time, of course.

Have any languages achieved this? I know TypeScript can do some of these things, but it's clunky.


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.

Here's three recent papers on extensible records:

* https://arxiv.org/pdf/2108.06296 * https://arxiv.org/pdf/2404.00338 * https://dl.acm.org/doi/pdf/10.1145/3571224

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.)


> I think what you describe is called "extensible records".

From what I read, gp only asks for a way to express the type, not a way to change/extend the type of something during runtime.

Elm's current implementation does this just fine.


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.

[0]: https://hgiasac.github.io/posts/2018-11-18-Record-Row-Type-a...


Even if Elm has had no new version in 4 years while purescript has?

You made me want to try purescript :)


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.)


Haskell does it just fine with the HasField typeclass, but like everything Haskell, it's not always easy to find the information.

    type HasFooBar s = (HasField "_foo" s String, HasField "_bar" s String) => s


Ok, but why?

I like the beauty of an expressive type system as much as the next guy, but is there any scenario where:

    type FooBar = mergeMaps Foo Bar
Is better for solving real-world problems than

    type FooBar = { "_foo", Foo, "_bar", Bar }
or whatever?


With mergeMaps, you don't have to write out all of the properties.


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.


> you would get back a `Map<String, String | int>` (or, equivalently, `Map<String, String> | Map<String, int>`)

How are these equivalent? Wouldn't the latter result in {foo:1, foo:"two"}, where the former wouldn't?


OCaml's first-class modules allow you to do this: https://ocaml.org/play#code=bW9kdWxlIHR5cGUgRk9PID0gc2lnCiAg...


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


The most consistent solution with the least ceremony. Now it is a module, not a type though.


You can create first-class values of this module type, and since values have types, "it" is a type. Specifically, my_foobar has type (module FOOBAR).

Actually getting values out of such a module-typed structure does involve some ceremony, however:

    let f =
      let module M = (val my_foobar) in
      M.foo


Hence, Objective Caml! This can be modeled in OCaml as an object type,

  type foo_bar = < _foo : int; _bar : string >
For a family of types matching any object with those methods, I think you can write something like

  type 'a has_foo_bar = < _foo : int; _bar : string; .. > as 'a


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.


Scala 3 can do it with some macro tricks. But the performance is not as good as with nominally typed structures.


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.


I am cheating a bit by naming a non-general purpose language, but Nickel[1] does that, for example through composing contracts.

[1]: https://nickel-lang.org/


Not sure whether this is what you intended, but Go structs can embed other structs

    type Foo struct {
     foo int
    }

    type Bar struct {
     bar string
    }

    type FooBar struct {
     Foo
     Bar
    }


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.


Ok, Go generics is pretty limited but you can achieve this in C++ via concepts.

    #include <iostream>
    #include <string>
    #include <type_traits>

    // Concept to check for 'foo' field of type int
    template <typename T>
    concept HasFooInt = requires(T t) {
        { t.foo } -> std::convertible_to<int>;
    };

    // Concept to check for 'bar' field of type string
    template <typename T>
    concept HasBarString = requires(T t) {
        { t.bar } -> std::convertible_to<std::string>;
    };

    // Generic function that accepts T with either 'foo' or 'bar'
    template <typename T>
    requires HasFooInt<T> || HasBarString<T>
    void printField(const T& t) {
        if constexpr (HasFooInt<T>) {
            std::cout << "foo: " << t.foo << std::endl;
        } else if constexpr (HasBarString<T>) {
            std::cout << "bar: " << t.bar << std::endl;
        }
    }

    struct Foo {
        int foo;
    };

    struct Bar {
        std::string bar;
    };

    struct FooBar {
        int foo;
        std::string bar;
    };

    int main() {
        Foo s1 { 42 };
        Bar s2 { "hello" };
        FooBar s3 { 100, "world" };

        printField(s1); // prints: foo: 42
        printField(s2); // prints: bar: hello
        printField(s3); // prints: foo: 100 (prefers foo due to order of checks)
        
        return 0;
    }


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).


I raise the bar and say the relational model has it and can be made to work at type level.

    type Person = User DESELECT password
    type Invoice = Customer JOIN Inv


Something like this in TypeScript:

    type Person = Omit<User, 'password'>
    type Invoice = Customer & { invoice: Inv }
    // or
    type Invoice = Inv & { customer: Customer }


> Have any languages achieved this?

”The MLs” solved it decades ago. Standard ML has very nice record types.


Building a language is too hard.

You need to create:

- 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.


Very Nice.

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.


Federico Tomassetti is also good. Check out his blog: https://tomassetti.me/blog/


Thank You. Seems like a expert "Language Engineer" worth learning from.


MPS is pretty cool but goddamn dod I hate projection.

Something that in theory sounds good but in reality means you have to build a bunch of extra tooling around it.

Feel like just text will be the way to go for a long time.


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.


Does MongoDB have serious market share compared to DynamoDB (and similar clones from Azure, GCP) at this point?


Totally. Many of the biggest tech companies are using for core use cases. Stripe uses a modified version: https://stripe.com/blog/how-stripes-document-databases-suppo...

We use MongoDB’s cloud offering called Atlas as our core DB at TableCheck.


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.


Yes, this is a reasonable approach, but how are certificates deployed and managed?

How do we deploy a list of certificates that a service should accept?

How do we do certificate rotation and revocation?


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



thank you


Vast majority do not have the disposable income to invest in stocks.


Nah, that was Postgres vector extensions



There is a value in living in a society that is culturally rich, not just GDP rich.

The last era where we had this kind of society, was probably the 70s and we are still recycling the cultural artefacts of that time.



Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: