5 ms·
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
by horsawlarway 6y ago
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 a different take on generics to me. There might be a lot of value in how this is implemented when compared to generics in a language like Java/C#/Typescript, but I didn't find that content anywhere in the article...
- zcesur 6y agoSay you'd like to have an interface for things that are 'mappable'. For example, for arrays we could write: interface Mappable<Array> { map<A, B>(f: (a: A) => B, fa: Array<A>): Array<B> } Likewise, for `Promise`s we could write: interface Mappable<Promise> { map<A, B>(f: (a: A) => B, fa: Promise<A>): Promise<B> } But in order to generalize this interface to an arbitrary type constructor such that `F: * -> *`, we would need to write interface Mappable<F> { map<A, B>(f: (a: A) => B, fa: ?): ? } which is not possible in TypeScript since it does not support higher-kinded types or type parameters that take type parameters or parametrized modules.
- karmakaze 6y agoNot possible, but an approximation: interface Mappable<P extends Mappable<P, unknown>, T> { flatMap<U>(f: (x: T) => Mappable<P, U>): Mappable<P, U>; } class Maybe<T> implements Mappable<Maybe<unknown>, T> { x: T | undefined; public flatMap<U>(f: (x: T) => Maybe<U>): Maybe<U> { if (this.x) { return f(this.x); } return Maybe.nothing(); } }
- zcesur 6y agoYes, in fact this reminds me of the HKT implementation[1] found in fp-ts[2][3] interface HKT<F, A> { _URI: F _A: A } interface Mappable<F> { map<A, B>(f: (a: A) => B, fa: HKT<F, A>): HKT<F, B> } 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-kinded-polymorphism.pdf https://www.cl.cam.ac.uk/~jdy22/papers/lightweight-higher-ki... [2]: https://github.com/gcanti/fp-ts https://github.com/gcanti/fp-ts [3]: https://gist.github.com/gcanti/2b455c5008c2e1674ab3e8d5790cdad5 https://gist.github.com/gcanti/2b455c5008c2e1674ab3e8d5790cd...
- girvo 6y agogcanti’s work in Flow and Typescript is amazing and I use it daily :)
- elbear 6y agoSeems like something covered by typeclasses in Haskell, right?
- ratmice 6y agoSeems 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.
- elbear 6y agoWhen you put it like that, modules sound more powerful than typeclasses. Is there any situation where typeclasses are superior to modules?
- ratmice 6y agoI think the extra power comes with cognitive overhead, I.e. operators are now relative to some module, and you're stuck with that overhead whether or not you actually use the power or not. Trying to stay neutral, I think its no suprise that some people prefer typeclasses...
- zcesur 6y agoIn 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-matter-most/ https://existentialtype.wordpress.com/2011/04/16/modules-mat...
- tel 6y agoIt'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<Node> 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.
- ufo 6y agoI love this example. I have a feeling that I'll be borrowing it often in the future.