7 ms·
Other than the syntax, I’m not sure I understand how this is different from any other object-oriented class definition. I suppose the ability to project the mod
by OkGoDoIt 6y ago
Other than the syntax, I’m not sure I understand how this is different from any other object-oriented class definition. I suppose the ability to project the module into the local or top-level scope, but that seems more like syntactic sugar than anything meaningful. What am I missing here?
- cultus 6y agoModule functors and first-class modules are probably the big feature. Those are more akin to allowing functions between classes, rather than just objects.
- Someone 6y agoA module, by definition, is a singleton (for parametrized modules, a “once per type” object) C has (unparametrized) modules, calling them “compilation unit”.
- sparkie 6y agoExcept where first-class modules are used, the application of a functor to a module happens at at (or before) compile-time. You can have a fully resolved type before runtime, but which can still be abstract at design time. They can be seen as a zero-cost abstraction. Modules are structurally typed, so the more typical sub-typing behavior expected from mainstream OOP languages is not applicable to them.
- dan-robertson 6y agoModule signatures may contain types. So you can write a signature like: module type VectorSpace = sig module Field : Field type t val zero : t val (+) : t * t -> t val (*) : Field.t * t -> t end In OOP it becomes hard to write an interface like this simple example, and more complex examples become harder still.
- horsawlarway 6y agoCan you give an example of the value this would provide that isn't provided by generics?
- spopejoy 6y agoOne thing the example shows is the classic problem with OO's mechanism, namely binary functions. For a given class, something like `+` is impossible to implement elegantly, it has to bias one operand over the other -- after any syntactic sugar, it's always `arg1.op(arg2)`. Module functors resemble true ADTs more, in that they do not dispatch from a live instance of the datatype.
- kdkeyser 6y agoFor me, the value of a module is that you can describe a set of multiple types and the functions that operate on these types, all in one place. In OO, there is essentially one type that is "special", the type of the class you define. The power of the Ocaml module system is really in the functor, which the article only touches upon. You define a module and refer in its definition to types/functions of other modules that must be provided at the time you construct the module. Even better, you can define relations between the types and do type substitutions, e.g.: to construct this module, you need to give me 2 other modules, each with a specific set of functions, and for which type t1 of the first module, matches type t2 of the second module, while the actual type of t1/t2 does not matter. See https://dev.realworldocaml.org/functors.html https://dev.realworldocaml.org/functors.html for more examples.
- neurotrace 6y agoI'm trying to wrap my head around this > Even better, you can define relations between the types and do type substitutions, e.g.: to construct this module, you need to give me 2 other modules, each with a specific set of functions, and for which type t1 of the first module, matches type t2 of the second module, while the actual type of t1/t2 does not matter. Isn't this essentially the same as generic type arguments in other languages? Like in this pseudo TypeScript: class CustomModule<T1 extends Module1Interface, T2 extends Module2Interface> { constructor(t1: T1, t2: T2) { ... } }
- ufo 6y agoIt'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. For example, if you apply the OrderedList functor to the IntOrdering module the result would be an OrderedListOfInt module that provides an abstract data type implementing an ordered list of integers. In the Ocaml standard library the names would be different but that's the basic idea.
- gmueckl 6y agoModule parameterization by type is a kind of a big deal. This enables reuse and composability opportunities that can become pretty impressive. C++ templates can be used in a similar way, but it's not perfect. The closest match I can think of is a template class with static members. D has one more mechanism that is conceptually very different, but could be used to express somewhat similar patterns: template mixins. These are best described as parameterized template sections of code worh one or more declarations that have to be reasonably well formed on their own and are virtually pasted into the code at the location where they are instantiated. They exist somewhere in the space between templates and C preprocessor macros.
- a-nikolaev 6y agoAs another answer pointed out, a class in OOP is supposed to implement only one type and operations on it. It works in many cases, but sometimes it does not fit naturally the problem at hand. Modules relax this single-type requirement, and let you define multiple types in it, which makes certain things more natural to express, without a need to create multiple shallow classes or manager-like classes. Also, functors (i.e. "module functions") in the OCaml module system allows generics, when a module is essentially parameterized by one or more other modules.
- rwmj 6y agoI think Lisp's object system (CLOS) removes this limitation? (Actually a question - I believe this is what multiple dispatch does, but not certain)
- a-nikolaev 6y agoI'm not really talking about multiple dispatch, but more like a module that store two or more types, like: module Data = struct type pair = int * float type numbers = int list let make_pair () = (0, 0.0) let make_numbers () = [] end This example is not very illustrative, but just explain the idea what I mean. We have two constructors: make_pair and make_numbers. So in a way, we can have multiple types in the module, if they are meant to be tightly related. We are not forced to make three classes here (Pair, Numbers, Data), everything is in one module. EDIT: OOP classes have a primary type in your class, and sometimes this creates artificial chicken or egg type of problem, where you cannot decide what is more fundamental (message vs receiver vs sender). Modules don't force this on you.