2

I have a simple variant. say:

type fooVariant =
  | Foo
  | Bar
  | Baz;

And I want an efficient, type safe, immutable set. I'm thinking that Belt.Set sounds like what I wan't. However, being at the start of my ReasonML learning I can't quickly understand the Set documentation which is in BuckleScript format.

A simple example would get me started quickly and comfortably. Preferably the example would include these items:

  • define a type of hash set (sth like type mySet = Set(fooVariant) )
  • Initialize a set with 1 item (say fooVariant.Foo)
  • Make a new set with adding (say fooVariant.Bar)
  • Test the set for presence of 1 value (say fooVariant.Foo)

edit: I had HashSet in the question at the beginning, but I then realized that it is mutable and not what I wan't. I'm not absolutely sure which of the types in the Belt is best for my situation, but lets see.

glennsl
  • 28,186
  • 12
  • 57
  • 75
Peter Lamberg
  • 8,151
  • 3
  • 55
  • 69

1 Answers1

4

Belt.Set does sound like what you want. It was designed to be fast and small when compiled to JavaScript, but I'm not entirely convinced it was designed to be used by people.

Here's an example showing how it can be used with your custom type.

// Create an "Id" module, to basically give the comparison function a type
module FooComparable =
  Belt.Id.MakeComparable({
    type t = fooVariant;
    let cmp = Pervasives.compare;
  });

// Define an alias for the set type
type fooSet = Belt.Set.t(fooVariant, FooComparable.identity);

// Initialize a set with one item
let s0 = Belt.Set.fromArray([|Foo|], ~id=(module FooComparable));

// Immutably add to the set
let s1 = Belt.Set.add(s0, Bar);

// Test for the presence of a value
Belt.Set.has(s1, Foo);

Compare this with the standard set data structure, which is probably slightly slower and significantly larger when compiled to JavaScript:

// Create a specialized set module, again basically just to give the
// comparison function a type
module FooSet = Set.Make({
  type t = fooVariant;
  let compare = Pervasives.compare;
});

// Define an alias for the set type
type fooSet = FooSet.t;

// Initialize a set with one item
let s0 = FooSet.of_list([Foo]);

// Immutably add to the set
let s1 = FooSet.add(Bar, s0);

// Test for the presence of a value
FooSet.mem(Foo, s1);

You can play around with both of these on the Reason playground.

As you can see from the examples, both of these require you to create some kind of module instead of just passing the comparison function directly to the creation function. This is to make sure that the same comparison function is used for all operations on the same set. Otherwise, if for example you have two separate sets with the same type only parameterized by the type contained, and initialized with different comparison function, it would not be obvious what the union or intersection of the two sets should be.

Also note that while Pervasives.compare is conveniently used as the comparison function here, and might have been used for all sets, thereby seemingly bypassing the issue above, it is not a total function. If you try to compare two functions, for example, it will just crash, and if you try to compare a cyclic data structure it will not terminate. It is therefore necessary to be able to use, and a good idea to use, a custom comparison function for more complex types.

glennsl
  • 28,186
  • 12
  • 57
  • 75