1. 27
  1.  

  2. 5

    It turns out that they’re not necessary but they are certainly useful sometimes, and so their absence makes some things tougher.

    Swift’s an interesting example here. My understanding is that they don’t do semicolons, but also, unlike, eg, go and Kotlin, don’t do “newlines are semicolons”. So Swifts syntax is ambiguous at times, but with static types it ends up mostly OK.

    On the other hand I really want to make Garnet compile fast, and that means being able to parallelize every stage of compilation possible, so the package system may shake out a little differently than Rust’s “glom all files together into one compilation unit”. Need to do some more research there; Zig has some crazy and awesome ideas. I also want to be able to have a sensible ABI, at least for most things, so that’s related too. Rust has way too many great libraries that are far too difficult to use from other languages.

    This rhymes well with using modules for generics. One thing that struct me in modular implicits paper is that typeclasses are at odds with separate compilation (to have coherence, you need a global, none separately compiled view of the world), while modules mostly just work. Also, module signatures are pretty great for parallel compilation, and for reading docs of the libraries. But if you do signatures, you’d have to put types in your structs.

    Semi-repeatedly, https://news.ycombinator.com/item?id=35811423 points out LEAN as an interesting design point wrt generics.

    Sorry for dumping random thoughts here, it’s just that I”ve been thinking along similar lines recently :-)

    1. 4

      Swift…

      Sounds like something I should look more at. Unfortunately the current ambiguity in Garnet’s syntax is easy to hit by accident, and is made worse by the one or two pieces of syntactic sugar I don’t want to give up.

      …typeclasses are at odds with separate compilation…

      ….hmm, good point. I had given some good hard thinking about using modules as compilation units but hadn’t noticed that angle. Mostly I wanted to avoid needing to somehow instantiate modules (and potentially supply type arguments to them) just to be able to split a program across files.

      I kinda loathe having separate module signatures in OCaml, ngl. Like C headers, it was always just another copy of the code to keep in sync with what I was working on. But when I actually want modules as part of my code instead of just an organizational tool they make a lot more sense to write by hand, and the rest of the time they can just be generated automatically. So I might have to think more about this.

      One of my concerns with module == compilation unit is it’s harder to optimize stuff across compilation units. I may end up having to write my own linker of one sort or another so I can know more about how LTO works between compilation units; Rust’s global and aggressive inlining is so wonderful most of the time that I’m not willing to give it up. It sounds like fun, but also the sort of thing I could easily spend the rest of my life on…

      1. 1

        I think the sweet spot might be to derive module signature automatically, and add it to package published to “crates.io”. That way, once you download a DAG of crates, you only need to compile signatures in topological order, and the actual crates can be compiled in parallel. Similarly, operations which modify a single crate but don’t touch signature can avoid recompiling revdeps.

        But of course this requires ability to actually have useful signatures, which Rust lacks (impl trait, also sizes of things leak).

        1. 1

          Yeah, whatever curséd binary representation I end up with will almost certainly involve a pile of metadata about the actual types of the things in it. If you want to actually have separate compilation then you kinda need that. I suppose that reading and compiling that signature data is still a parallelism bottleneck but is a lot cheaper than compiling the whole code…

          Sizes of things leaking… ugh, that’s a hard problem to solve, they leak even in C DLL’s and such afaik, if you’re referring to what I think you’re referring to.

    2. 2

      Turns out that ML modules and Haskell/Rust traits/typeclasses are basically isomorphic, you can turn either into the other one automatically with enough work.

      They certainly are not[1]. ML modules can encode type classes, but not the other way around. ML module types are a form of dependent types[2] while type classes can be encoded into System F[3].

      [1] https://dl.acm.org/doi/10.1145/1190215.1190229

      [2] https://dl.acm.org/doi/10.1145/3474834

      [3] https://okmij.org/ftp/Computation/typeclass.html

      1. 1

        ah feck. Really? I’ve read that first paper but it’s quite possible I’ve misunderstood the implications of it. Howver, [4] presents mechanical translations from one to the other and back. The long version [5] is 170 pages long though, so I haven’t dug through it all; it may be incomplete. [1] does mention that its translation from modules to typeclasses is not very friendly to actual use, though I’d kinda expect it to be the other way around tbh – if modules are more powerful than typeclasses I’d expect it to be harder to translate modules into typeclasses than the other way around.

        Alas, as I said at the end of the post, I’m not actually very good at the theory side of this.

        [4] https://www.stefanwehr.de/publications/Wehr_ML_modules_and_Haskell_type_classes_SHORT.pdf [5] https://www.stefanwehr.de/publications/Wehr_ML_modules_and_Haskell_type_classes.pdf