Changing module behavior in OCaml
I have a set of ints whose input I would like to constrain. I would like it to behave like this:
# RestrictedIntSet.add 15 (RestrictedIntSet.make 0 10)
Exception: 15 out of acceptable range [0 .. 10]
How can I implement this? In Java, it might look something like this:
Set<Integer> restrictedSet = new HashSet<Integer>() {
public boolean add(Integer i) {
if (i < lowerBound || i > upperBound) {
throw new IllegalArgumentException("out of bounds");
}
return super.add(i);
}
Or, to be less abusive in inheritance:
public class RestrictedSet {
private int lowerBound;
private int upperBound;
private Set elems = Sets.newHashSet();
public RestrictedSet(int lowerBound, int upperBound) {
this.lowerBound = lowerBound;
this.upperBound = upperBound;
}
public boolean add(Integer i) {
if (i < lowerBound || i > upperBound) {
throw new IllegalArgumentException("out of bounds");
}
return elems.add(i);
}
/* fill in other forwarded Set calls as needed */
}
What's the equivalent, idiomatic way to do this in OCaml?
source to share
Well, it depends which library set
you are using?
Using the Set module of the standard library, you can do the following:
module type RestrictedOrderedType = sig
type t
val compare : t -> t -> int
val lower_bound : t
val upper_bound : t
end
module RestrictedSet (Elem : RestrictedOrderedType) = struct
include Set.Make(Elem)
exception Not_in_range of Elem.t
let check e =
if Elem.compare e Elem.lower_bound < 0
|| Elem.compare e Elem.upper_bound > 0
then raise (Not_in_range e)
else e
(* redefine a new 'add' in term of the one in Set.Make(Elem) *)
let add e s = add (check e) s
let singleton e = singleton (check e)
end
(* test *)
module MySet = RestrictedSet(struct
type t = int
let compare = compare
let lower_bound = 0
let upper_bound = 10
end)
let test1 = MySet.singleton 3
let test2 = MySet.add (-3) test1
(* Exception: Not_in_range (-3) *)
source to share
I love @gasches.
As a quick add-on: the OCaml Set module is designed to instantiate the OrderedType module, which means you cannot use native OCaml directly int
.
Therefore, you must use a module that matches the requested signature. gasche defines the signature RestrictedOrderedType and it gracefully includes bottom and top bounding boxes. A rougher approach would be to use OCaml Int32 or Int64 modules that match the requested OrderedType signature and hard-code the bounds in the module MySet
.
Below is a slight reformulation of the gasche example to illustrate this point.
module MySet = struct
include Set.Make(Int32)
exception Not_in_range of Int32.t
let lower_bound = Int32.of_int 5
let upper_bound = Int32.of_int 10
let add elt set =
if (elt < lower_bound)||(elt > upper_bound)
then raise (Not_in_range elt)
else add elt set
end;;
source to share