• No results found

module type Set2Sig = sig type’a set

val empty : ’a set

val add : ’a set -> ’a -> ’a set val mem : ’a -> ’a set -> bool end;;

Implementation

module Set2 : Set2Sig = struct include Set

let add l x = Set.add x l end;;

Figure 12.7: Wrapping a module to use a new signature.

values in theSetandSet2modules are the same, and theSet2.addfunction simply

reorders the arguments before calling theSet.addfunction. There is little or no per-

formance penalty for the wrapper—in most cases the native-code OCaml compiler will

inlinetheSet2.addfunction (in other words, it will perform the argument reordering

at compile time).

12.6 Sharing constraints

There is one remaining problem with this example. In the combined program, the first library uses the originalSetmodule, and the second library usesSet2. It is likely that we will want to pass values, including sets, from one library to the other. However, as defined, the’a Set.setand’a Set2.settypes are distinct abstract types, and it is an error to use a value of type’a Set.setin a place where a value of type’a Set2.set

is expected, andvice-versa. The following error message is typical. # Set2.add Set.empty 1;;

This expression has type ’a Set.set but is here used with type ’b Set2.set

Of course, we might want the types to be distinct. But in this case, it is more likely that we want the definition to be transparent. We know that the two kinds of sets are really the same—Set2is really just a wrapper forSet. How do we establish the equivalence of’a Set.setand’a Set2.set?

The solution is called asharing constraint. The syntax for a sharing constraint uses thewithkeyword to specify a type equivalence for a module signature in the following form.

signature ::= signaturewith type typename= type

In this particular case, we wish to say that the ’a Set2.set type is equal to the

’a Set.settype, which we can do by adding a sharing constraint when theSet2mod-

ule is defined, as shown in Figure 12.8.

The constraint specifies that the types’a Set2.setand’a Set.setare the same.

In other words, theysharea common type. Since the two types are equal, set values can be freely passed between the two set implementations.

CHAPTER 12. THE OCAML MODULE SYSTEM12.6. SHARING CONSTRAINTS

Module definition module Set2 : Set2Sig

with type’a set = ’a Set.set = struct

include Set

let add l x = Set.add x l end;;

Toploop

# lets = Set2.add Set.empty 1;;

val s : int Set2.set = <abstr>

#Set.mem 1 s;;

- bool = true

12.7. EXERCISES CHAPTER 12. THE OCAML MODULE SYSTEM

12.7 Exercises

Exercise 12.1 Which of the following are legal programs? Explain your answers. 1. module A :sig val x : string end = struct let x = 1 let x = "x" end 2. module A :sig val x : string val x : string end = struct let x = "x" end 3. module a =struct let x = 1 end;; 4. module M :sig val f : int -> int val g : string -> string end = struct

let g x = x let f x = g x end

5. let module X = struct let x = 1end in X.x 6. module M =struct

let g x = h x let f x = g x let h x = x + 1 end

7. module rec M : sig val f : int -> int val h : int -> int end = struct openM let g x = h x let f x = g x let h x = x + 1 end

8. module rec M : sig val f : int -> int end = struct

let f = M.f end

9. type ’a t = { set : ’a -> unit; get : unit -> ’a } let f x =

let cell = ref xin let module M = struct

let s i = cell := i let g () = !cell

let r = { set = s; get = g } end

CHAPTER 12. THE OCAML MODULE SYSTEM 12.7. EXERCISES in

M.r 10. let f x =

let cell = ref xin let module M = struct

type ’a t = { set : ’a -> unit; get : unit -> ’a } let s i = cell := i

let g () = !cell

let r = { set = s; get = g } end

in M.r

11. module type ASig = sig types val f : int -> send module type BSig = sig typet val g : t -> intend module C : sig

module A : ASig

module B : BSig with typet = A.s end =struct

type u = string

module A = struct type s = u let f = string_of_int end module B = struct type t = u let g = int_of_string end end

include C

let i = B.g (A.f ())

12. module type ASig = sig typet end module type BSig = sig val x : int end module A : ASigwith type t = int

=struct type t = int end

module B : BSig = struct let x = 1 end module C : sig include ASig val x : t end =struct include A include B end

Exercise 12.2 In OCaml, programs are usually written “bottom-up,” meaning that pro- grams are constructed piece-by-piece, and the last function is a file is likely to be the most important. Many programmers prefer a top-down style, where the most important functions are defined first in a file, and supporting definitions are placed later in the file. Can you use the module system to allow top-down programming?

Exercise 12.3 One could argue that sharing constraints are never necessary for un- parameterized modules like the ones in this chapter. In the example of Figure 12.8, there are at least two other solutions that allow theSet2andSetmodules to share val-

ues, without having to use sharing constraints. Present two alternate solutions without sharing constraints.

Exercise 12.4 In OCaml, signatures can apparently contain multiple declarations for the same value.

12.7. EXERCISES CHAPTER 12. THE OCAML MODULE SYSTEM val f : ’a -> ’a

val f : int -> int end;;

module type ASig = sig val f : ’a -> ’a val f : int -> int end

In any structure that is given this signature, the functionfmust haveallthe types listed. Iffis not allowed to raise an exception, what is the only sensible definition for it?

Exercise 12.5 Unlikevaldeclarations,typedeclarations must have distinct names in

any structure or signature. # module type ASig = sig

type t = int type t = bool end;;

Multiple definition of the type name t.

Names must be unique in a given structure or signature.

While this particular example may seem silly, the real problem is that all modules included withincludemust have disjoint type names.

# module type XSig = sig type t

val x : t end;;

# module A : XSig = struct type t = int

let x = 0 end;;

# module B : XSig = struct type t = int let x = 1 end;; # module C = struct include A include B end;;

Multiple definition of the type name t.

Names must be unique in a given structure or signature.

Is this a problem? If it is not, argue that conflicting includes should not be allowed in practice. If it is, propose a possible solution to the problem (possibly by changing the language).

Chapter 13

Functors

Modules often refer to other modules. The modules we saw in Chapter 12 referred to other modules by name. Thus, all the module references we’ve seen up to this point have been to specific, constant modules.

It’s also possible in OCaml to write modules that are parameterized by other mod- ules. To be used, functors are instantiated by supplying actual module arguments for the functor’s module parameters (similar to supplying arguments in a function call).

To illustrate the use of a parameterized module, let’s return to the set implementa- tion we have been using in the previous two chapters. One of the problems with that implementation is that the elements are compared using the OCaml built-in equality function =. What if, for example, we want a set of strings where equality is case- insensitive, or a set of floating-point numbers where equality is to within a small con- stant? Rather than re-implementing a new set for each of new case, we can implement it as a functor, where the equality function is provided as a parameter. An example is shown in Figure 13.1, where we represent the set as a list of elements.

In this example, the moduleMakeSetis a functor that takes another moduleEqual

with signatureEqualSigas an argument. TheEqualmodule provides two things—a type of elements, and a functionequal to compare two elements. The body of the

functorMakeSetis much the same as the previous set implementations we have seen,

except now the elements are compared using the functionequal x x’instead of the

builtin-equalityx = x’. The expressionList.exists f sis true iff the predicatef

is true for some element of the lists. TheList.find f sexpression returns the first

element ofsfor which the predicatefis true, or raisesNot_foundif there is no such

element.

To construct a specific set, we first build a module that implements the equality function (in this case, the moduleStringCaseEqual), then apply theMakeSetfunctor module to construct the set module (in this case, the moduleSSet).

In many ways, functors are just like functions at the module level, and they can be used just like functions. However, there are a few things to keep in mind.

1. A functor parameter, like (Equal : EqualSig)must be a module, or another functor. It is not legal to pass non-module values (like strings, lists, or integers).

CHAPTER 13. FUNCTORS

Set functor

module type EqualSig = sig type t

val equal : t -> t -> bool end;;

module MakeSet (Equal : EqualSig) = struct open Equal

type elt = Equal.t type t = elt list let empty = []

let mem x s = List.exists (equal x) s let add = (::)

let find x s = List.find (equal x) s end;;

Building a specific case module StringCaseEqual = struct

type t = string let equal s1 s2 =

String.lowercase s1 = String.lowercase s2 end;;

module SSet = MakeSet (StringCaseEqual);; Using the set

# lets = SSet.add "Great Expectations" SSet.empty; val s : string list = ["Great Expectations"] #SSet.mem "great eXpectations" s;;

- : bool = true

#SSet.find "great eXpectations" s;; - StringCaseEqual.t = "Great Expectations"

CHAPTER 13. FUNCTORS 13.1. SHARING CONSTRAINTS 2. Syntactically, module and functor identifiers must always be capitalized. Functor parameters, like (Equal : EqualSig), must be enclosed in paren-

theses, and the signature is required. For functor applications, like

MakeSet (StringCaseEqual), the argument must be enclosed in parenthesis. 3. Modules and functors are not first class. That is, they can’t be stored in data

structures or passed as arguments like other values, and module definitions can- not occur in function bodies.

Another point to keep in mind is that the new set implementation is no longer polymorphic—it is now defined for a specific type of elements defined by theEqual

module. This loss of polymorphism occurs frequently when modules are parameter- ized, because the goal of parameterizing is to define different behaviors for different types of elements. While the loss of polymorphism is inconvenient, in practice it is rarely an issue because modules can be constructed for each specific type of parameter by using a functor application.

13.1 Sharing constraints

In theMakeSetexample of Figure 13.1, we omitted the signature for sets. This leaves the set implementation visible (for example, the SSet.add function returns a string list). We can define a signature that hides the implementation, preventing the rest of the program from depending on these details. Functor signatures are defined the usual way, by specifying the signature after a colon, as shown in Figure 13.2.

Thesharing constraintSetSig with type elt = Equal.tis an important part of the construction. In the SetSig signature, the type eltis abstract. If the MakeSet

functor were to return a module with the plain signatureSetSig, the typeSSet.elt

would be abstract, and the set would be useless. If we repeat the construction without the sharing constraint, the compiler produces an error message when we try to use it.

# moduleMakeSet (Equal : EqualSig) : SetSig = struct ... end # moduleSSet = MakeSet (StringCaseCompare);;

#SSet.add "The Magic Mountain" SSet.empty;;

Characters 9-29:

SSet.add "The Magic Mountain" SSet.empty;; ^^^^^^^^^^^^^^^^^^^^

This expression has type string but is here used with type SSet.elt = MakeSet(StringCaseEqual).elt

The message indicates that the types string and SSet.elt are not the same—and in fact, the only property known is that the types SSet.elt and

MakeSet(StringCaseEqual).eltare equal.

13.2 Module sharing constraints

The sharing constraints that we have seen so far apply to types in a module definition. It is also possible to specify sharing constraints on entire modules. The effect of a module