Live data from Hacker News

Exotic Programming Ideas: Module Systems

stephendiehl.com

41–50 of 57 posts

Re: Exotic Programming Ideas: Module Systems

#42

Zig's compile time execution lets you do similar things I believe. In Zig, structs and modules are equivalent, and type declarations can be manipulated at compile time just like any other value. That, among other things[1], lets you write: fn LinkedList(comptime T: type) type { return struct { pub const Node = struct { prev: ?*Node, next: ?*Node, data: T, }; first: ?*Node, last: ?*Node, len: usize, }; } I wonder if t…

One thing is that OCaml modules are first class objects, so can be constructed at runtime and passed around as variables. But this Zig example, I think, will cover the most common case of functors for creating generics. (I'm also not sure if Zig does compile-time check if the type T is compatible with LinkedList. OCaml does this type of check, making sure the signature of T fits the requirement of the functor LinkedL…

(Not even close to a zig expert, but I've been playing with it a little bit, find it very interesting, and think what I'm about to say is correct:)

The zig equivalent of a module, a struct, is also a first-class object in zig, so should be the same as OCaml there.

However, Zig does not have anything like a signature -- in that sense, it's "dynamically typed" at compile time (i.e., like type args to C++'s templates, however not nearly as bad because the errors you get are immediate and understandable -- think of it as more like "I fucked up the types and got a python-style error" than I "I fucked up the types and got a C++ templates error").

Re: Exotic Programming Ideas: Module Systems

#43
post #13

Amusingly, I was precisely working today on extra-nice error messages for module type errors in OCaml. :) My reaction to the title was "But they are not exotic, I use them every day!" It's definitely the feature I miss the most every time I work in other languages, even presumable "advanced" ones, like Haskell. One notable attempt to add them elsewhere is "modular C"[1]. [1]: http://cmod.gforge.inria.fr/

Have you heard of Backpack for Haskell? https://gitlab.haskell.org/ghc/ghc/-/wikis/backpack

Re: Exotic Programming Ideas: Module Systems

#44
post #35

Earlier quoted context omitted.

Say you'd like to have an interface for things that are 'mappable'. For example, for arrays we could write: interface Mappable { map (f: (a: A) => B, fa: Array ): Array } Likewise, for `Promise`s we could write: interface Mappable { map (f: (a: A) => B, fa: Promise ): Promise } But in order to generalize this interface to an arbitrary type constructor such that `F: * -> *`, we would need to write interface Mappable {…

Not possible, but an approximation: interface Mappable , T> { flatMap (f: (x: T) => Mappable ): Mappable ; } class Maybe implements Mappable , T> { x: T | undefined; public flatMap (f: (x: T) => Maybe ): Maybe { if (this.x) { return f(this.x); } return Maybe.nothing(); } }

Yes, in fact this reminds me of the HKT implementation[1] found in fp-ts[2][3]

    interface HKT {
      _URI: F
      _A: A
    }
    
    interface Mappable {
      map(f: (a: A) => B, fa: HKT): HKT
    }
where F is a unique identifier representing the type constructor and A its type parameter.

[1]: https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-ki... [2]: https://github.com/gcanti/fp-ts [3]: https://gist.github.com/gcanti/2b455c5008c2e1674ab3e8d5790cd...

Re: Exotic Programming Ideas: Module Systems

#45
post #32
post #20

Earlier quoted context omitted.

It's not exactly the same thing because, for example, there is no subtyping or inheritance. In Ocaml you don't say that "type T1 is an Orderable". However, it does serve a similar purpose. For example, if you want to create a datatype for an OrderedList then you'd create a higher-order-module (functor) that receives as an argument another module containing all the necessary comparison functions for the list elements.…

This makes it seem like modules are strictly inferior, if you can't assign names to common requirements.

You can still give a name to the interface. However, interfaces apply to modules, not to types. In Ocaml you'd say "this module implements the Comparable interface" while in an OO languague you'd say "this type is a subtype of Comparable". Sorry for the confusion.

Re: Exotic Programming Ideas: Module Systems

#46
post #40
post #35

Earlier quoted context omitted.

Say you'd like to have an interface for things that are 'mappable'. For example, for arrays we could write: interface Mappable { map (f: (a: A) => B, fa: Array ): Array } Likewise, for `Promise`s we could write: interface Mappable { map (f: (a: A) => B, fa: Promise ): Promise } But in order to generalize this interface to an arbitrary type constructor such that `F: * -> *`, we would need to write interface Mappable {…

Seems like something covered by typeclasses in Haskell, right?

Seems like it, but typeclasses are inherently anti-modular, they provide a globally coherent unique instance. consider the Ord typeclass giving a single ordering for a specific type. To reverse the order you need to create a new type with a new instance of ord which reverses it.

Where in a module system its perfectly fine to have multiple instances of Ord for a given type. One gives global consistency where the other gives local consistency.

Re: Exotic Programming Ideas: Module Systems

#47
post #40
post #35

Earlier quoted context omitted.

Say you'd like to have an interface for things that are 'mappable'. For example, for arrays we could write: interface Mappable { map (f: (a: A) => B, fa: Array ): Array } Likewise, for `Promise`s we could write: interface Mappable { map (f: (a: A) => B, fa: Promise ): Promise } But in order to generalize this interface to an arbitrary type constructor such that `F: * -> *`, we would need to write interface Mappable {…

Seems like something covered by typeclasses in Haskell, right?

In addition to @ratmice's comment, check out this post[1] on Existential Type. I've dabbled in Haskell myself with not much experience in ML, so I found it interesting to see how ML modules differ from Haskell typeclasses. Though they seem to be equi-expressive for the most part.

[1]: https://existentialtype.wordpress.com/2011/04/16/modules-mat...

Re: Exotic Programming Ideas: Module Systems

#48

Zig's compile time execution lets you do similar things I believe. In Zig, structs and modules are equivalent, and type declarations can be manipulated at compile time just like any other value. That, among other things[1], lets you write: fn LinkedList(comptime T: type) type { return struct { pub const Node = struct { prev: ?*Node, next: ?*Node, data: T, }; first: ?*Node, last: ?*Node, len: usize, }; } I wonder if t…

One limitation is privacy and abstraction. You can hide implementation details with most ML module systems - eg. hiding the underlying type of `Node`. You can also make local definitions in the module private. It's pretty challenging to get this stuff right - there's lots of research about it.

Also, as noted in other comments, Zig's type parameters are also dynamically typed. This leads to implementation details leaking out. This is not an issue in OCaml and other ML-style languages.

Re: Exotic Programming Ideas: Module Systems

#49

Coming in without much OCaml experience, I don't really think this is a great demonstration of why this construct has value. I don't really want to read a long form description of the OCaml implementation of modules. I want a comparison to the languages he dismissed at the beginning of the article, and a discussion of why this feature has some value that isn't provided by those languages. Basically - This feels like…

It's absolutely a different approach to generics. Or, rather, that's the ringer. I want to say first: OCaml's take on modules is just a really nice way of doing namespacing as well.

Secondly, generics depend upon (a) having a means to discuss functionality which abstracts over one or more types and certain behaviors those types must support, (b) having a means to bundle up one or more types along with some behaviors, and (c) being able to combine those two.

In Typescript/Java/C# this is mostly carried out by classes and subtyping. Abstraction occurs when we ask not for a specific type but instead for something a little less than that specific type, one of its supertypes; bundling occurs in classes; and the combination occurs naturally as subtypes are transparently upcast to their supertypes.

There are two practical drawbacks to this approach:

First, it's hard to abstract over behavior that doesn't merely consume your abstract type but also returns it. When we do (c) via subclassing we have to upcast and it's not always clear or possible to re-downcast things back to the appropriate type. OO has tons of workarounds for this issue and related ones.

Second, it's hard to abstract over multiple interrelated types at once. For instance, a generic graph implementation might want to be abstract both in the types of nodes and the type of edges. The generic implementation can thus handle annotations at either the edges or the nodes. In OO abstraction, you might do something like have the edges be an associated type of the nodes, but this creates an unnecessary asymmetry.

The solution is a classic one. Instead of having the class represent an object, have the class represent a bundle of operations which act on abstract objects (the C++ vtable approach). For example, in pseudocode

    class GRAPH

      type Graph
      type Node
      type Edge

      # These are hard to do with subclassing since Graph will often be upcast on return
      def emptyGraph(): Graph
      def simplify(g: Graph): Graph

      # These represent non-trivial interactions between multiple types abstracted simultaneously
      def addNode(g: Graph, n: Node): Graph
      def neighbors(g: Graph, n: Node): List
And this, with the appropriate type discipline, is what OCaml does. Unfortunately, what you'll find is that OCaml's type discipline is critical and difficult to emulate. Making this sort of modularity work consistently involves some notions of equivalences and transparency that are natural to discuss when talking about modules but rarely show up in OO systems.
Post reply on HN