Exotic Programming Ideas: Module Systems
41–50 of 57 posts
Re: Exotic Programming Ideas: Module Systems
#42Zig'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…
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
#43Amusingly, 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/
Re: Exotic Programming Ideas: Module Systems
#44Earlier 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(); } }
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
#45Earlier 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.
Re: Exotic Programming Ideas: Module Systems
#46Earlier 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?
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
#47Earlier 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?
[1]: https://existentialtype.wordpress.com/2011/04/16/modules-mat...
Re: Exotic Programming Ideas: Module Systems
#48Zig'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…
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
#49Coming 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…
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.