3

I want to build a set of related functions parametrized by a function variable (the function encapsulates a coordinate transformation). I.e., for some variable:

val transformation : float * float -> float * float

I want to build at runtime a set of functions based on this transformation. Now, in "mainstream" imperative languages, I'd normally go for some kind of object with methods. I know those are possible in OCaml too, but I've also read that in OCaml, it's generally preferable to use modules (i.e., a functor here, I presume).

Should I build an object with methods, or a module (via functor)? And if the latter, how should I code it? I plan to go with object, as I think I'd know how to write it; as to module, I'm confused and not sure if what I want is possible. But I'd love to learn "the better way" if you can help me with it. I want something like:

val make_set_of_functions : (float * float -> float * float) -> 'magic

where 'magic is a module (or object).

TIA!

akavel
  • 4,789
  • 1
  • 35
  • 66

1 Answers1

8

OCaml provides several mechanisms for generalization and abstraction. Sometimes it is not obvious which one to choose. In that case, I usually suggest to use the most lightweight mechanism available, in other words, do not use a mechanism that gives you more than you need (unless you anticipate that in the future you will need more).

So, you have the following choices:

  1. Functor
  2. Function
  3. Class
  4. Object

Functor

Functors provide the most powerful generalization mechanism. They use module types to specify abstractions and modules as implementations. This comes with a cost of course. Many people find, that functors are syntactically heavy.

module type Coordinates = sig
   val transform : (int * int) -> (int * int)
end

module Canvas(Coord : Coordinates) = struct
    let draw obj = 
       let x,y = Coord.transform obj.coords in
       ...
end

And here is the usage:

module Canvas = Canvas(Minimap)

Canvas.draw world

Function

A module is just a compound data structure, that can contain functions, types, and other modules. When it is compiled, the types and other ephemeral declarations are removed, so the module representation is actually the same as the representation of a record. A functor is actually compiled as a function, parameterized by a record. It looks like, that we're not trying to parametrize our "set of functions" with any types or modules, so we can just use Occam razor, and use a record directly, without module encapsulation, e.g.,

type canvas = {
   draw : object -> unit;
   ...
}

let canvas xform = {
   draw = (fun obj -> ... );
   ...
}

And usage:

let canvas = canvas xform
canvas.draw world

Class

Classes, like modules, also encapsulate several fields into one entity. They can contain methods (i.e., functions) and instance variables. They can't contain types, however. The main difference between classes and modules is that class functions bind to each other not at the compile time, but at runtime (so called late binding). It means, that whenever you call a class method, you're actually not calling a concrete function, but you're invoking a whatever function is currently written in the slot associated with the invoked method. Basically, that means, that a class is a set of mutually recursive functions, that can be mutated during runtime. In other words, the mutual recursion is open, i.e., a user of your class can change the implementation of any method, via the inheritance. Basically, for you, it means, that you can't trust your own methods and that the type of your object is not fixed, but is open. All these together makes reasoning about classes very hard, so if you don't need open recursing, then I would suggest keeping away from classes. To protect the idea of classes, I would like to notice that they are good for implementing AST visitors, interpreters, and other algorithms that work with data structures that are inherently recursive. So here is the example:

class canvas xform = object
   method draw : obj -> unit = 
       let x,y = xform obj.coords in  
       ...
 end

Usage

 let canvas = new canvas xform
 canvas#draw world

Objects

When you define a class, you're actually defining a blueprint, that describes how to create objects of that type. But in OCaml, it is not required for an object to be derived from a base class, or to be created using a class. For an object to be recognized as an instance of a class it must provide all methods, that are specified in this class (with matching method types of course). In other words, OCaml type system is not nominal, but structural -- if an object has all the methods, and all the types of methods are included in the required type, then the object is of that type. So, when you are expressing something as a class, then you're actually anticipating that someone will inherit your class, and change methods implementation. This puts an extra restriction on your implementation, and types of your methods. So, if you're not anticipating the inheritance, it is better just to use an object (however, if you're not anticipating an inheritance, then why to use classes at all). Here is the syntax example:

let canvas xform = object
   method draw : obj -> unit = 
       let x,y = xform obj.coords in  
       ...
 end

Yep, just substitute class with let, and you will create a value, instead of a class. But this substitution will give you a big advantage in your quarrels with the type checker. Here comes the usage example:

 let canvas = canvas xform
 canvas#draw world

Summary

There are actually other mechanisms, that are more obscure, like using first class modules. But following the Occam razor principle, we will cut them of the posting. Of all the mechanisms, that I've shown so far, I would suggest you either functors or records. If you think, that at some point in time, you will abstract the coordinates representation, then I would suggest using functors. If you want to keep it simple and concrete, then use records. Don't use classes unless your functions are mutually recursive, and you want your users to hook into your methods.

Community
  • 1
  • 1
ivg
  • 34,431
  • 2
  • 35
  • 63
  • Thanks a lot, awesome answer! Actually I totally forgot about records, and they do indeed seem the simplest solution at this point, so thanks for that even more. However, I'd still like to ask you a bit more about modules/functors if I may, as there's one thing not yet clear to me, I can't wrap my head around it: is it technically possible to build a *module* parametrized based on a (first class) *function value*, i.e. put a module as the `'magic` in the pseudo-signature I wrote? (maybe via first class modules?) or is that not possible/one-way: module vals can only come from other module vals? – akavel Oct 14 '16 at 22:21
  • modules and functors can be first class values, that means, that a function can take a parameter, and then instantiate a value or a functor and use it or return it. Moreover, you can store functors of different implementations but of the same type in hashtables, so that you can create generic factories. – ivg Oct 15 '16 at 03:58