From 7de37765c12eecedd94d76cb4405f462080d004d Mon Sep 17 00:00:00 2001 From: Cuihtlauac ALVARADO Date: Thu, 23 Feb 2023 15:09:37 +0100 Subject: [PATCH 1/6] Import Rewrote Set Tutorial from V2 PR @NebuPookins rewrote the set tutorial for [V2](https://github.com/ocaml/v2.ocaml.org) in 2021. The PR was neither merged nor rejected: https://github.com/ocaml/v2.ocaml.org/pull/1596 --- data/tutorials/language/3ds_03_set.md | 333 +++++++++++++++++--------- 1 file changed, 223 insertions(+), 110 deletions(-) diff --git a/data/tutorials/language/3ds_03_set.md b/data/tutorials/language/3ds_03_set.md index ad84aa5f3f..b6cbe1b0a5 100644 --- a/data/tutorials/language/3ds_03_set.md +++ b/data/tutorials/language/3ds_03_set.md @@ -6,140 +6,253 @@ description: > category: "Data Structures" --- -# Sets - -## Module Set -To make a set of strings: - -```ocaml -# module SS = Set.Make(String);; -module SS : - sig - type elt = string - type t = Set.Make(String).t - val empty : t - val is_empty : t -> bool - val mem : elt -> t -> bool - val add : elt -> t -> t - val singleton : elt -> t - val remove : elt -> t -> t - val union : t -> t -> t - val inter : t -> t -> t - val disjoint : t -> t -> bool - val diff : t -> t -> t - val compare : t -> t -> int - val equal : t -> t -> bool - val subset : t -> t -> bool - val iter : (elt -> unit) -> t -> unit - val map : (elt -> elt) -> t -> t - val fold : (elt -> 'a -> 'a) -> t -> 'a -> 'a - val for_all : (elt -> bool) -> t -> bool - val exists : (elt -> bool) -> t -> bool - val filter : (elt -> bool) -> t -> t - val filter_map : (elt -> elt option) -> t -> t - val partition : (elt -> bool) -> t -> t * t - val cardinal : t -> int - val elements : t -> elt list - val min_elt : t -> elt - val min_elt_opt : t -> elt option - val max_elt : t -> elt - val max_elt_opt : t -> elt option - val choose : t -> elt - val choose_opt : t -> elt option - val split : elt -> t -> t * bool * t - val find : elt -> t -> elt - val find_opt : elt -> t -> elt option - val find_first : (elt -> bool) -> t -> elt - val find_first_opt : (elt -> bool) -> t -> elt option - val find_last : (elt -> bool) -> t -> elt - val find_last_opt : (elt -> bool) -> t -> elt option - val of_list : elt list -> t - val to_seq_from : elt -> t -> elt Seq.t - val to_seq : t -> elt Seq.t - val to_rev_seq : t -> elt Seq.t - val add_seq : elt Seq.t -> t -> t - val of_seq : elt Seq.t -> t - end -``` +# Set + +`Set` is a functor, which means that it is a module that is parameterized +by another module. More concretely, this means you cannot directly create +a set; instead, you must first specify what type of elements your set will +contain. -To create a set you need to start somewhere so here is the empty set: +The `Set` functor provides a function `Make` which accepts a module as a +parameter, and returns a new module representing a set whose elements have +the type that you passed in. For example, if you want to work with sets of +strings, you can invoke `Set.Make(String)` which will return you a new module +which you can assign the name `SS` (short for "String Set"). Note: Be sure to +pay attention to the case; you need to type `Set.Make(String)` and not +`Set.Make(string)`. The reason behind this is explained in the +"Technical Details" section at the bottom. -```ocaml -# let s = SS.empty;; -val s : SS.t = +Doing this in the OCaml's top level will yield a lot of output: + +```ocamltop +module SS = Set.Make(String);; ``` -Alternatively if we know an element to start with we can create a set -like +What happened here is that after assigning your newly created module to the name +`SS`, OCaml's top level then displayed the module, which in this case contains +a large number of convenience functions for working with sets (for example `is_empty` +for checking if you set is empty, `add` to add an element to your set, `remove` to +remove an element from your set, and so on). + +Note also that this module defines two types: `type elt = String.t` representing +the type of the elements, and `type t = Set.Make(String).t` representing the type of +the set itself. It's important to note this, because these types are used in the +signatures of many of the functions defined in this module. + +For example, the `add` function has the signature `elt -> t -> t`, which means +that it expects an element (a String), and a set of strings, and will return to you +a set of strings. As you gain more experience in OCaml and other function languages, +the type signature of functions are often the most convenient form of documentation +on how to use those functions. + +## Creating a Set + +You've created your module representing a set of strings, but now you actually want +to create an instance of a set of strings. So how do we go about doing this? Well, you +could search through the documentation for the original `Set` functor to try and +find what function or value you should use to do this, but this is an excellent +opportunity to practice reading the type signatures and inferring the answer from them. + +You want to create a new set (as opposed to modifying an existing set). So you should +look for functions whose return result has type `t` (the type representing the set), +and which *does not* require a parameter of type `t`. -```ocaml -# let s = SS.singleton "hello";; -val s : SS.t = +Skimming through the list of functions in the module, there's only a handful of functions +that match that criteria: `empty: t`, `singleton : elt -> t`, `of_list : elt list -> t` +and `of_seq : elt Seq.t -> t`. + +Perhaps you already know how to work with lists and sequences in OCaml or +perhaps you don't. For now, let's assume you don't know, and so we'll focus +our attention on the first two functions in that list: `empty` and `singleton`. + +The type signature for `empty` says that it simply returns `t`, i.e. an instance +of our set, without requiring any parameters at all. By intuition, you might +guess that the only reasonable set that a library function could return when +given zero parameters is the empty set. And the fact that the function is named +`empty` reinforces this theory. + +Is there a way to test this theory? Perhaps if we had a function which +could print out the size of a set, then we could check if the set we get +from `empty` has a size of zero. In other words, we want a function which +receives a set as a parameter, and returns an integer as a result. Again, +skimming through the list of functions in the module, we see there is a +function which matches this signature: `cardinal : t -> int`. If you're +not familiar with the word "cardinal", you can look it up on Wikipedia +and notice that it basically refers to the size of sets, so this reinforces +the idea that this is exactly the function we want. + +So let's test our hypothesis: + +```ocamltop +let s = SS.empty;; +SS.cardinal s;; ``` -To add some elements to the set we can do. +Excellent, it looks like `SS.empty` does indeed create an empty set, +and `SS.cardinal` does indeed print out the size of a set. + +What about that other function we saw, `singleton : elt -> t`? Again, +using our intuition, if we provide the function with a single element, +and the function returns a set, then probably the function will return +a set containing that element (or else what else would it do with the +parameter we gave it?). The name of the function is `singleton`, and +again if you're unfamiliar with what word, you can look it up on +Wikipedia and see that the word means "a set with exactly one element". +It sounds like we're on the right track again. Let's test our theory. -```ocaml -# let s = - List.fold_right SS.add ["hello"; "world"; "community"; "manager"; - "stuff"; "blue"; "green"] s;; -val s : SS.t = +```ocamltop +let s = SS.singleton "hello";; +SS.cardinal s;; ``` -Now if we are playing around with sets we will probably want to see what -is in the set that we have created. To do this we can write a function -that will print the set out. +It looks like we were right again! + +## Working with Sets + +Now let's say we want to build bigger and more complex sets. Specifically, +let's say we want to add another element to our existing set. So we're +looking for a function with two parameters: One of the parameters should +be the element we wish to add, and the other parameter should be the set +that we're adding to. For the return value, we would expect it to either +return unit (if the function modifies the set in place), or it returns a +new set representing the result of adding the new element. So we're +looking for signatures that look something like `elt -> t -> unit` or +`t -> elt -> unit` (since we don't know what order the two parameters +should appear in), or `elt -> t -> t` or `t -> elt -> t`. -```ocaml -# let print_set s = - SS.iter print_endline s;; -val print_set : SS.t -> unit = +Skimming through the list, we see 2 functions with matching signatures: +`add : elt -> t -> t` and `remove : elt -> t -> t`. Based on their names, +`add` is probably the function we're looking for. `remove` probably removes +an element from a set, and using our intuition again, it does seem like +the type signature makes sense: To remove an element from a set, you need +to tell it what set you want to perform the removal on and what element +you want to remove; and the return result will be the resulting set after +the removal. + +Furthermore, because we see that these functions return `t` and not `unit`, +we can infer that these functions do not modify the set in place, but +instead return a new set. Again, we can test this theory: + +```ocamltop +let firstSet = SS.singleton "hello";; +let secondSet = SS.add "world" firstSet;; +SS.cardinal firstSet;; +SS.cardinal secondSet;; ``` -If we want to remove a specific element of a set there is a remove -function. However if we want to remove several elements at once we could -think of it as doing a 'filter'. Let's filter out all words that are -longer than 5 characters. +It looks like our theories were correct! -This can be written as: +## Sets of With Custom Comparators -```ocaml -# let my_filter str = - String.length str <= 5;; -val my_filter : string -> bool = -# let s2 = SS.filter my_filter s;; -val s2 : SS.t = +The `SS` module we created uses the built-in comparison function provided +by the `String` module, which performs a case-sensitive comparison. We +can test that with the following code: + +```ocamltop +let firstSet = SS.singleton "hello";; +let secondSet = SS.add "HELLO" firstSet;; +SS.cardinal firstSet;; +SS.cardinal secondSet;; ``` -or using an anonymous function: +As we can see, the `secondSet` has a cardinality of 2, indicating that +`"hello"` and `"HELLO"` are considered two distinct elements. + +Let's say we want to create a set which performs a case-insensitive +comparison instead. To do this, we simply have to change the parameter +that we pass to the `Set.Make` function. -```ocaml -# let s2 = SS.filter (fun str -> String.length str <= 5) s;; -val s2 : SS.t = +The `Set.Make` function expects a struct with two fields: a type `t` +that represents the type of the element, and a function `compare` +whose signature is `t -> t -> int` and essentially returns 0 if two +values are equal, and non-zero if they are non-equal. It just so happens +that the `String` module matches that structure, which is why we could +directly pass `String` as a parameter to `Set.Make`. Incidentally, many +other modules also have that structure, including `Int` and `Float`, +and so they too can be directly passed into `Set.Make` to construct a +set of integers, or a set of floating point numbers. + +For our use case, we still want our elements to be of type string, but +we want to change the comparison function to ignore the case of the +strings. We can accomplish this by directly passing in a literal struct +to the `Set.Make` function: + +```ocamltop +module CISS = Set.Make(struct + type t = string + let compare a b = compare (String.lowercase_ascii a) (String.lowercase_ascii b) +end);; ``` -If we want to check and see if an element is in the set it might look -like this. +We name the resulting module CISS (short for "Case Insensitive String Set"). +We can now test whether this module has the desired behavior: -```ocaml -# SS.mem "hello" s2;; -- : bool = true + +```ocamltop +let firstSet = CISS.singleton "hello";; +let secondSet = CISS.add "HELLO" firstSet;; +CISS.cardinal firstSet;; +CISS.cardinal secondSet;; ``` -The Set module also provides the set theoretic operations union, -intersection and difference. For example, the difference of the original -set and the set with short strings (≤ 5 characters) is the set of long -strings: +Success! `secondSet` has a cardinality of 1, showing that `"hello"` +and `"HELLO"` are now considered to be the same element in this set. +We now have a set of strings whose compare function performs a case +insensitive comparison. + +Note that this technique can also be used to allow arbitrary types +to be used as the element type for set, as long as you can define a +meaningful compare operation: -```ocaml -# print_set (SS.diff s s2);; -community -manager -- : unit = () +```ocamltop +type color = Red | Green | Blue;; + +module SC = Set.Make(struct + type t = color + let compare a b = + match (a, b) with + | (Red, Red) -> 0 + | (Red, Green) -> 1 + | (Red, Blue) -> 1 + | (Green, Red) -> -1 + | (Green, Green) -> 0 + | (Green, Blue) -> 1 + | (Blue, Red) -> -1 + | (Blue, Green) -> -1 + | (Blue, Blue) -> 0 +end);; ``` -Note that the Set module provides a purely functional data structure: -removing an element from a set does not alter that set but, rather, -returns a new set that is very similar to (and shares much of its -internals with) the original set. +## Technical Details + +### Set.Make, types and modules + +As mentioned in a previous section, the `Set.Make` function accepts a structure +with two specific fields, `t` and `compare`. Modules have structure, and thus +it's possible (but not guaranteed) for a module to have the structure that +`Set.Make` expects. On the other hand, types do not have structure, and so you +can never pass a type to the `Set.Make` function. In OCaml, modules start with +an upper case letter and types start with a lower case letter. This is why +when creating a set of strings, you have to use `Set.Make(String)` (passing in +the module named `String`), and not `Set.Make(string)` (which would be attempting +to pass in the type named `string`, which will not work). + +### Purely Functional Data Structures + +The data structure implemented by the Set functor is a purely functional one. +What exactly that means is a big topic in itself (feel free to search for +"Purely Functional Data Structure" in Google or Wikipedia to learn more). As a +short oversimplification, this means that all instances of the data structure +that you create are immutable. The functions like `add` and `remove` do not +actually modify the set you pass in, but instead return a new set representing +the results of having performed the corresponding operation. + +### Full API documentation + +This tutorial focused on teaching how to quickly find a function that does what +you want by looking at the type signature. This is often the quickest and most +convenient way to discover useful functions. However, sometimes you do want to +see the formal documentation for the API provided by a module. For sets, the +API documentation you probably want to look at is at +https://ocaml.org/api/Set.Make.html From caf82b1f3bef05bcd8b2fe15ea7f98cf8a0f0f4e Mon Sep 17 00:00:00 2001 From: Cuihtlauac Alvarado Date: Fri, 24 Feb 2023 09:01:48 +0100 Subject: [PATCH 2/6] Apply suggestions from code review Co-authored-by: Christine Rose --- data/tutorials/language/3ds_03_set.md | 66 +++++++++++++-------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/data/tutorials/language/3ds_03_set.md b/data/tutorials/language/3ds_03_set.md index b6cbe1b0a5..292303ebc6 100644 --- a/data/tutorials/language/3ds_03_set.md +++ b/data/tutorials/language/3ds_03_set.md @@ -22,26 +22,26 @@ pay attention to the case; you need to type `Set.Make(String)` and not `Set.Make(string)`. The reason behind this is explained in the "Technical Details" section at the bottom. -Doing this in the OCaml's top level will yield a lot of output: +Doing this in the OCaml's toplevel will yield a lot of output: ```ocamltop module SS = Set.Make(String);; ``` -What happened here is that after assigning your newly created module to the name -`SS`, OCaml's top level then displayed the module, which in this case contains +What happened here is that after assigning your newly-created module to the name +`SS`, OCaml's toplevel displayed the module, which in this case contains a large number of convenience functions for working with sets (for example `is_empty` -for checking if you set is empty, `add` to add an element to your set, `remove` to +to check if your set is empty, `add` to add an element to your set, `remove` to remove an element from your set, and so on). -Note also that this module defines two types: `type elt = String.t` representing -the type of the elements, and `type t = Set.Make(String).t` representing the type of -the set itself. It's important to note this, because these types are used in the +Note also that this module defines two types: `type elt = String.t`, representing +the type of the elements, and `type t = Set.Make(String).t`, representing the type of +the set itself. It's important to note this because these types are used in the signatures of many of the functions defined in this module. For example, the `add` function has the signature `elt -> t -> t`, which means that it expects an element (a String), and a set of strings, and will return to you -a set of strings. As you gain more experience in OCaml and other function languages, +a set of strings. As you gain more experience in OCaml and other functional languages, the type signature of functions are often the most convenient form of documentation on how to use those functions. @@ -54,7 +54,7 @@ find what function or value you should use to do this, but this is an excellent opportunity to practice reading the type signatures and inferring the answer from them. You want to create a new set (as opposed to modifying an existing set). So you should -look for functions whose return result has type `t` (the type representing the set), +look for functions whose return result has type `t` (the type representing the set) and which *does not* require a parameter of type `t`. Skimming through the list of functions in the module, there's only a handful of functions @@ -62,22 +62,22 @@ that match that criteria: `empty: t`, `singleton : elt -> t`, `of_list : elt lis and `of_seq : elt Seq.t -> t`. Perhaps you already know how to work with lists and sequences in OCaml or -perhaps you don't. For now, let's assume you don't know, and so we'll focus +perhaps you don't. For now, let's assume you don't know, so we'll focus our attention on the first two functions in that list: `empty` and `singleton`. -The type signature for `empty` says that it simply returns `t`, i.e. an instance +The type signature for `empty` says that it simply returns `t`, i.e., an instance of our set, without requiring any parameters at all. By intuition, you might guess that the only reasonable set that a library function could return when given zero parameters is the empty set. And the fact that the function is named `empty` reinforces this theory. -Is there a way to test this theory? Perhaps if we had a function which +Is there a way to test this theory? Perhaps if we had a function that could print out the size of a set, then we could check if the set we get from `empty` has a size of zero. In other words, we want a function which -receives a set as a parameter, and returns an integer as a result. Again, +receives a set as a parameter and returns an integer as a result. Again, skimming through the list of functions in the module, we see there is a function which matches this signature: `cardinal : t -> int`. If you're -not familiar with the word "cardinal", you can look it up on Wikipedia +not familiar with the word "cardinal," you can look it up on Wikipedia and notice that it basically refers to the size of sets, so this reinforces the idea that this is exactly the function we want. @@ -88,7 +88,7 @@ let s = SS.empty;; SS.cardinal s;; ``` -Excellent, it looks like `SS.empty` does indeed create an empty set, +Excellent! It looks like `SS.empty` does indeed create an empty set, and `SS.cardinal` does indeed print out the size of a set. What about that other function we saw, `singleton : elt -> t`? Again, @@ -96,9 +96,9 @@ using our intuition, if we provide the function with a single element, and the function returns a set, then probably the function will return a set containing that element (or else what else would it do with the parameter we gave it?). The name of the function is `singleton`, and -again if you're unfamiliar with what word, you can look it up on -Wikipedia and see that the word means "a set with exactly one element". -It sounds like we're on the right track again. Let's test our theory. +if you're unfamiliar with what word, you can look it up on +Wikipedia and see that the word means "a set with exactly one element." +It sounds like we're on the right track, so let's test our theory. ```ocamltop let s = SS.singleton "hello";; @@ -110,12 +110,12 @@ It looks like we were right again! ## Working with Sets Now let's say we want to build bigger and more complex sets. Specifically, -let's say we want to add another element to our existing set. So we're -looking for a function with two parameters: One of the parameters should +let's say we want to add another element to our existing set, so we're +looking for a function with two parameters. One of the parameters should be the element we wish to add, and the other parameter should be the set that we're adding to. For the return value, we would expect it to either return unit (if the function modifies the set in place), or it returns a -new set representing the result of adding the new element. So we're +new set representing the result of adding the new element. We're looking for signatures that look something like `elt -> t -> unit` or `t -> elt -> unit` (since we don't know what order the two parameters should appear in), or `elt -> t -> t` or `t -> elt -> t`. @@ -124,9 +124,9 @@ Skimming through the list, we see 2 functions with matching signatures: `add : elt -> t -> t` and `remove : elt -> t -> t`. Based on their names, `add` is probably the function we're looking for. `remove` probably removes an element from a set, and using our intuition again, it does seem like -the type signature makes sense: To remove an element from a set, you need +the type signature makes sense. To remove an element from a set, you need to tell it what set you want to perform the removal on and what element -you want to remove; and the return result will be the resulting set after +you want to remove. The return result will be the resulting set after the removal. Furthermore, because we see that these functions return `t` and not `unit`, @@ -163,16 +163,16 @@ comparison instead. To do this, we simply have to change the parameter that we pass to the `Set.Make` function. The `Set.Make` function expects a struct with two fields: a type `t` -that represents the type of the element, and a function `compare` +that represents the type of the element and a function `compare`, whose signature is `t -> t -> int` and essentially returns 0 if two values are equal, and non-zero if they are non-equal. It just so happens that the `String` module matches that structure, which is why we could directly pass `String` as a parameter to `Set.Make`. Incidentally, many other modules also have that structure, including `Int` and `Float`, -and so they too can be directly passed into `Set.Make` to construct a -set of integers, or a set of floating point numbers. +so they too can be directly passed into `Set.Make` to construct a +set of integers or a set of floating point numbers. -For our use case, we still want our elements to be of type string, but +For our use case, we still want our elements to be a string, but we want to change the comparison function to ignore the case of the strings. We can accomplish this by directly passing in a literal struct to the `Set.Make` function: @@ -225,16 +225,16 @@ end);; ## Technical Details -### Set.Make, types and modules +### `Set.Make`, Types, and Modules As mentioned in a previous section, the `Set.Make` function accepts a structure -with two specific fields, `t` and `compare`. Modules have structure, and thus +with two specific fields, `t` and `compare`. Modules have structure, so it's possible (but not guaranteed) for a module to have the structure that -`Set.Make` expects. On the other hand, types do not have structure, and so you +`Set.Make` expects. On the other hand, types do not have structure, so you can never pass a type to the `Set.Make` function. In OCaml, modules start with -an upper case letter and types start with a lower case letter. This is why +an upper case letter, and types start with a lower case letter. So when creating a set of strings, you have to use `Set.Make(String)` (passing in -the module named `String`), and not `Set.Make(string)` (which would be attempting +the module named `String`) and not `Set.Make(string)` (which would be attempting to pass in the type named `string`, which will not work). ### Purely Functional Data Structures @@ -247,7 +247,7 @@ that you create are immutable. The functions like `add` and `remove` do not actually modify the set you pass in, but instead return a new set representing the results of having performed the corresponding operation. -### Full API documentation +### Full API Documentation This tutorial focused on teaching how to quickly find a function that does what you want by looking at the type signature. This is often the quickest and most From 60615d9e6a1e310af37c9fcfebeb72e85cf511c1 Mon Sep 17 00:00:00 2001 From: Cuihtlauac ALVARADO Date: Fri, 24 Feb 2023 09:31:46 +0100 Subject: [PATCH 3/6] Minor Fixes As pointed by @Octachron --- data/tutorials/language/3ds_03_set.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/tutorials/language/3ds_03_set.md b/data/tutorials/language/3ds_03_set.md index 292303ebc6..e72cfa3e54 100644 --- a/data/tutorials/language/3ds_03_set.md +++ b/data/tutorials/language/3ds_03_set.md @@ -42,7 +42,7 @@ signatures of many of the functions defined in this module. For example, the `add` function has the signature `elt -> t -> t`, which means that it expects an element (a String), and a set of strings, and will return to you a set of strings. As you gain more experience in OCaml and other functional languages, -the type signature of functions are often the most convenient form of documentation +the type signature of functions are often a basic but very convenient form of documentation on how to use those functions. ## Creating a Set @@ -142,7 +142,7 @@ SS.cardinal secondSet;; It looks like our theories were correct! -## Sets of With Custom Comparators +## Sets With Custom Comparators The `SS` module we created uses the built-in comparison function provided by the `String` module, which performs a case-sensitive comparison. We From f382ccd39af834e94260f3fde03790c94816e527 Mon Sep 17 00:00:00 2001 From: Christine Rose Date: Thu, 4 Jan 2024 01:10:56 -0800 Subject: [PATCH 4/6] line editing --- data/tutorials/language/3ds_03_set.md | 103 +++++++++++++------------- 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/data/tutorials/language/3ds_03_set.md b/data/tutorials/language/3ds_03_set.md index e72cfa3e54..f43825d565 100644 --- a/data/tutorials/language/3ds_03_set.md +++ b/data/tutorials/language/3ds_03_set.md @@ -8,17 +8,18 @@ category: "Data Structures" # Set -`Set` is a functor, which means that it is a module that is parameterized +`Set` is a functor, which means that it is a module that is parameterised by another module. More concretely, this means you cannot directly create -a set; instead, you must first specify what type of elements your set will +a set. Instead, you must first specify what type of elements your set will contain. -The `Set` functor provides a function `Make` which accepts a module as a -parameter, and returns a new module representing a set whose elements have -the type that you passed in. For example, if you want to work with sets of -strings, you can invoke `Set.Make(String)` which will return you a new module -which you can assign the name `SS` (short for "String Set"). Note: Be sure to -pay attention to the case; you need to type `Set.Make(String)` and not +The `Set` functor provides a function `Make` that accepts a module as a +parameter. Then it returns a new module representing a set whose elements have +the type you passed in. For example, if you want to work with sets of +strings, you can invoke `Set.Make(String)` that will return a new module, +which you can assign the name `SS` (short for "String Set"). + +Note: Pay attention to the case. You need to type `Set.Make(String)` and not `Set.Make(string)`. The reason behind this is explained in the "Technical Details" section at the bottom. @@ -28,19 +29,19 @@ Doing this in the OCaml's toplevel will yield a lot of output: module SS = Set.Make(String);; ``` -What happened here is that after assigning your newly-created module to the name +After assigning your newly-created module to the name `SS`, OCaml's toplevel displayed the module, which in this case contains a large number of convenience functions for working with sets (for example `is_empty` -to check if your set is empty, `add` to add an element to your set, `remove` to -remove an element from your set, and so on). +checks if the set is empty, `add` adds an element to the set, `remove` +removes an element, and so on). Note also that this module defines two types: `type elt = String.t`, representing the type of the elements, and `type t = Set.Make(String).t`, representing the type of -the set itself. It's important to note this because these types are used in the -signatures of many of the functions defined in this module. +the set itself. It's important to note this because these types are used in many of +the functions' signatures defined in this module. For example, the `add` function has the signature `elt -> t -> t`, which means -that it expects an element (a String), and a set of strings, and will return to you +that it expects an element (a string) and a set of strings. Then it will return a set of strings. As you gain more experience in OCaml and other functional languages, the type signature of functions are often a basic but very convenient form of documentation on how to use those functions. @@ -48,9 +49,9 @@ on how to use those functions. ## Creating a Set You've created your module representing a set of strings, but now you actually want -to create an instance of a set of strings. So how do we go about doing this? Well, you +to create an instance of a set of strings. To do this, you could search through the documentation for the original `Set` functor to try and -find what function or value you should use to do this, but this is an excellent +find what function or value to use. Alternatively, this is an excellent opportunity to practice reading the type signatures and inferring the answer from them. You want to create a new set (as opposed to modifying an existing set). So you should @@ -58,17 +59,17 @@ look for functions whose return result has type `t` (the type representing the s and which *does not* require a parameter of type `t`. Skimming through the list of functions in the module, there's only a handful of functions -that match that criteria: `empty: t`, `singleton : elt -> t`, `of_list : elt list -> t` +that match that criteria: `empty: t`, `singleton : elt -> t`, `of_list : elt list -> t`, and `of_seq : elt Seq.t -> t`. -Perhaps you already know how to work with lists and sequences in OCaml or -perhaps you don't. For now, let's assume you don't know, so we'll focus +Perhaps you already know how to work with lists and sequences in OCaml, but +for now, let's assume you don't. We'll focus our attention on the first two functions in that list: `empty` and `singleton`. The type signature for `empty` says that it simply returns `t`, i.e., an instance of our set, without requiring any parameters at all. By intuition, you might -guess that the only reasonable set that a library function could return when -given zero parameters is the empty set. And the fact that the function is named +guess that the only reasonable set a library function could return when +given zero parameters is the empty set. The fact that the function is named `empty` reinforces this theory. Is there a way to test this theory? Perhaps if we had a function that @@ -76,8 +77,8 @@ could print out the size of a set, then we could check if the set we get from `empty` has a size of zero. In other words, we want a function which receives a set as a parameter and returns an integer as a result. Again, skimming through the list of functions in the module, we see there is a -function which matches this signature: `cardinal : t -> int`. If you're -not familiar with the word "cardinal," you can look it up on Wikipedia +function that matches this signature: `cardinal : t -> int`. If you're +not familiar with the word ["cardinal," you can look it up on Wikipedia](https://en.wikipedia.org/wiki/Cardinal_number) and notice that it basically refers to the size of sets, so this reinforces the idea that this is exactly the function we want. @@ -89,15 +90,14 @@ SS.cardinal s;; ``` Excellent! It looks like `SS.empty` does indeed create an empty set, -and `SS.cardinal` does indeed print out the size of a set. +and `SS.cardinal` prints out the size of a set. -What about that other function we saw, `singleton : elt -> t`? Again, -using our intuition, if we provide the function with a single element, +What about that other function we saw, `singleton : elt -> t`? Using +our intuition, if we provide the function with a single element and the function returns a set, then probably the function will return a set containing that element (or else what else would it do with the -parameter we gave it?). The name of the function is `singleton`, and -if you're unfamiliar with what word, you can look it up on -Wikipedia and see that the word means "a set with exactly one element." +parameter we gave it?). The name of the function is [`singleton`](https://en.wikipedia.org/wiki/Singleton_pattern), +which Wikipedia defines as "a set with exactly one element." It sounds like we're on the right track, so let's test our theory. ```ocamltop @@ -107,31 +107,31 @@ SS.cardinal s;; It looks like we were right again! -## Working with Sets +## Working With Sets Now let's say we want to build bigger and more complex sets. Specifically, let's say we want to add another element to our existing set, so we're looking for a function with two parameters. One of the parameters should be the element we wish to add, and the other parameter should be the set that we're adding to. For the return value, we would expect it to either -return unit (if the function modifies the set in place), or it returns a +return unit (if the function modifies the set in place) or return a new set representing the result of adding the new element. We're looking for signatures that look something like `elt -> t -> unit` or `t -> elt -> unit` (since we don't know what order the two parameters -should appear in), or `elt -> t -> t` or `t -> elt -> t`. +should appear), or `elt -> t -> t` or `t -> elt -> t`. Skimming through the list, we see 2 functions with matching signatures: `add : elt -> t -> t` and `remove : elt -> t -> t`. Based on their names, -`add` is probably the function we're looking for. `remove` probably removes +`add` is probably the function we're looking for. `remove` likely removes an element from a set, and using our intuition again, it does seem like -the type signature makes sense. To remove an element from a set, you need -to tell it what set you want to perform the removal on and what element +the type signature makes sense. To remove an element from a set, +tell it what set you want to perform the removal on and what element you want to remove. The return result will be the resulting set after the removal. Furthermore, because we see that these functions return `t` and not `unit`, we can infer that these functions do not modify the set in place, but -instead return a new set. Again, we can test this theory: +instead they return a new set. Again, we can test this theory: ```ocamltop let firstSet = SS.singleton "hello";; @@ -158,18 +158,18 @@ SS.cardinal secondSet;; As we can see, the `secondSet` has a cardinality of 2, indicating that `"hello"` and `"HELLO"` are considered two distinct elements. -Let's say we want to create a set which performs a case-insensitive -comparison instead. To do this, we simply have to change the parameter -that we pass to the `Set.Make` function. +Let's say we want to create a set that performs a case-insensitive +comparison instead. To do this, simply change the parameter +that passed to the `Set.Make` function. The `Set.Make` function expects a struct with two fields: a type `t` -that represents the type of the element and a function `compare`, -whose signature is `t -> t -> int` and essentially returns 0 if two -values are equal, and non-zero if they are non-equal. It just so happens -that the `String` module matches that structure, which is why we could +that represents the element type and a function `compare`, +whose signature is `t -> t -> int`. It essentially returns 0 if two +values are equal and non-zero if they are non-equal. The +`String` module matches that structure, so we could directly pass `String` as a parameter to `Set.Make`. Incidentally, many other modules also have that structure, including `Int` and `Float`, -so they too can be directly passed into `Set.Make` to construct a +so they too can be directly passed into `Set.Make` in order to construct a set of integers or a set of floating point numbers. For our use case, we still want our elements to be a string, but @@ -233,18 +233,18 @@ it's possible (but not guaranteed) for a module to have the structure that `Set.Make` expects. On the other hand, types do not have structure, so you can never pass a type to the `Set.Make` function. In OCaml, modules start with an upper case letter, and types start with a lower case letter. So -when creating a set of strings, you have to use `Set.Make(String)` (passing in -the module named `String`) and not `Set.Make(string)` (which would be attempting +when creating a set of strings, you must use `Set.Make(String)` (passing in +the module named `String`) and not `Set.Make(string)` (this would be attempting to pass in the type named `string`, which will not work). ### Purely Functional Data Structures -The data structure implemented by the Set functor is a purely functional one. +The data structure implemented by the `Set` functor is a purely functional one. What exactly that means is a big topic in itself (feel free to search for "Purely Functional Data Structure" in Google or Wikipedia to learn more). As a short oversimplification, this means that all instances of the data structure -that you create are immutable. The functions like `add` and `remove` do not -actually modify the set you pass in, but instead return a new set representing +created are immutable. The functions like `add` and `remove` do not +actually modify the set passed in, but instead they return a new set representing the results of having performed the corresponding operation. ### Full API Documentation @@ -252,7 +252,6 @@ the results of having performed the corresponding operation. This tutorial focused on teaching how to quickly find a function that does what you want by looking at the type signature. This is often the quickest and most convenient way to discover useful functions. However, sometimes you do want to -see the formal documentation for the API provided by a module. For sets, the -API documentation you probably want to look at is at -https://ocaml.org/api/Set.Make.html +see the formal documentation for the API provided by a module. For sets, refer to [this +API documentation[(https://ocaml.org/api/Set.Make.html) From 1fbf58ce98006176a98631926e9a42d4d2938771 Mon Sep 17 00:00:00 2001 From: Sabine Schmaltz Date: Wed, 10 Jan 2024 20:52:41 +0100 Subject: [PATCH 5/6] shorten a lot and present common operations instead of explaining functors --- data/tutorials/language/3ds_03_set.md | 331 +++++++++++++------------- 1 file changed, 159 insertions(+), 172 deletions(-) diff --git a/data/tutorials/language/3ds_03_set.md b/data/tutorials/language/3ds_03_set.md index f43825d565..ffc622c48f 100644 --- a/data/tutorials/language/3ds_03_set.md +++ b/data/tutorials/language/3ds_03_set.md @@ -8,206 +8,220 @@ category: "Data Structures" # Set +## Introduction + `Set` is a functor, which means that it is a module that is parameterised by another module. More concretely, this means you cannot directly create a set. Instead, you must first specify what type of elements your set will contain. The `Set` functor provides a function `Make` that accepts a module as a -parameter. Then it returns a new module representing a set whose elements have -the type you passed in. For example, if you want to work with sets of -strings, you can invoke `Set.Make(String)` that will return a new module, -which you can assign the name `SS` (short for "String Set"). - -Note: Pay attention to the case. You need to type `Set.Make(String)` and not -`Set.Make(string)`. The reason behind this is explained in the -"Technical Details" section at the bottom. +parameter. Then it returns a new module representing a set of elements of the given type. -Doing this in the OCaml's toplevel will yield a lot of output: - -```ocamltop -module SS = Set.Make(String);; +For example, if you want to work with sets of +strings, you can invoke `Set.Make(String)` that will return a new module, +which you can assign a name, for example, `StringSet`: +```ocaml +# module StringSet = Set.Make(String);; +module StringSet : + sig + type elt = string + type t = Set.Make(String).t + val empty : t + val add : elt -> t -> t + val singleton : elt -> t + val remove : elt -> t -> t + val union : t -> t -> t + val inter : t -> t -> t +... + end ``` After assigning your newly-created module to the name -`SS`, OCaml's toplevel displayed the module, which in this case contains +`StringSet`, OCaml's toplevel displays the type signature of the module. In this case, it contains a large number of convenience functions for working with sets (for example `is_empty` checks if the set is empty, `add` adds an element to the set, `remove` removes an element, and so on). -Note also that this module defines two types: `type elt = String.t`, representing +This module also defines two types: `type elt = String.t`, representing the type of the elements, and `type t = Set.Make(String).t`, representing the type of -the set itself. It's important to note this because these types are used in many of -the functions' signatures defined in this module. +the set itself. These types are used by many of +the functions defined in this module. -For example, the `add` function has the signature `elt -> t -> t`, which means +For example, the `add` function has the type `elt -> t -> t`, which means that it expects an element (a string) and a set of strings. Then it will return -a set of strings. As you gain more experience in OCaml and other functional languages, -the type signature of functions are often a basic but very convenient form of documentation -on how to use those functions. +a set of strings. ## Creating a Set -You've created your module representing a set of strings, but now you actually want -to create an instance of a set of strings. To do this, you -could search through the documentation for the original `Set` functor to try and -find what function or value to use. Alternatively, this is an excellent -opportunity to practice reading the type signatures and inferring the answer from them. - -You want to create a new set (as opposed to modifying an existing set). So you should -look for functions whose return result has type `t` (the type representing the set) -and which *does not* require a parameter of type `t`. - -Skimming through the list of functions in the module, there's only a handful of functions -that match that criteria: `empty: t`, `singleton : elt -> t`, `of_list : elt list -> t`, -and `of_seq : elt Seq.t -> t`. - -Perhaps you already know how to work with lists and sequences in OCaml, but -for now, let's assume you don't. We'll focus -our attention on the first two functions in that list: `empty` and `singleton`. - -The type signature for `empty` says that it simply returns `t`, i.e., an instance -of our set, without requiring any parameters at all. By intuition, you might -guess that the only reasonable set a library function could return when -given zero parameters is the empty set. The fact that the function is named -`empty` reinforces this theory. - -Is there a way to test this theory? Perhaps if we had a function that -could print out the size of a set, then we could check if the set we get -from `empty` has a size of zero. In other words, we want a function which -receives a set as a parameter and returns an integer as a result. Again, -skimming through the list of functions in the module, we see there is a -function that matches this signature: `cardinal : t -> int`. If you're -not familiar with the word ["cardinal," you can look it up on Wikipedia](https://en.wikipedia.org/wiki/Cardinal_number) -and notice that it basically refers to the size of sets, so this reinforces -the idea that this is exactly the function we want. - -So let's test our hypothesis: - -```ocamltop -let s = SS.empty;; -SS.cardinal s;; +1. We can create an empty set using `StringSet.empty`: +```ocaml +# StringSet.empty ;; +- : StringSet.t = + +# StringSet.empty |> StringSet.to_list;; +- : string list = [] ``` -Excellent! It looks like `SS.empty` does indeed create an empty set, -and `SS.cardinal` prints out the size of a set. +For `StringSet.empty`, you can see that the OCaml toplevel displays `` instead of a displaying the actual value. This is a limitation of the toplevel. However, you can see that converting the string set to a list using `StringSet.to_list` results in the empty list. -What about that other function we saw, `singleton : elt -> t`? Using -our intuition, if we provide the function with a single element -and the function returns a set, then probably the function will return -a set containing that element (or else what else would it do with the -parameter we gave it?). The name of the function is [`singleton`](https://en.wikipedia.org/wiki/Singleton_pattern), -which Wikipedia defines as "a set with exactly one element." -It sounds like we're on the right track, so let's test our theory. +2. A set with a single element is created using `StringSet.singleton`: +```ocaml +# StringSet.singleton "hello";; +- : StringSet.t = -```ocamltop -let s = SS.singleton "hello";; -SS.cardinal s;; +# StringSet.singleton "hello" |> StringSet.to_list;; +- : string list = ["hello"] ``` -It looks like we were right again! +3. Converting a list into a set using `StringSet.of_list`: +```ocaml +# StringSet.of_list ["hello"; "hi"];; +- : StringSet.t = +``` + +There's another relevant function `StringSet.of_seq: string Seq.t -> StringSet.t` which creates a list from a [sequence](/doc/sequences). ## Working With Sets -Now let's say we want to build bigger and more complex sets. Specifically, -let's say we want to add another element to our existing set, so we're -looking for a function with two parameters. One of the parameters should -be the element we wish to add, and the other parameter should be the set -that we're adding to. For the return value, we would expect it to either -return unit (if the function modifies the set in place) or return a -new set representing the result of adding the new element. We're -looking for signatures that look something like `elt -> t -> unit` or -`t -> elt -> unit` (since we don't know what order the two parameters -should appear), or `elt -> t -> t` or `t -> elt -> t`. - -Skimming through the list, we see 2 functions with matching signatures: -`add : elt -> t -> t` and `remove : elt -> t -> t`. Based on their names, -`add` is probably the function we're looking for. `remove` likely removes -an element from a set, and using our intuition again, it does seem like -the type signature makes sense. To remove an element from a set, -tell it what set you want to perform the removal on and what element -you want to remove. The return result will be the resulting set after -the removal. - -Furthermore, because we see that these functions return `t` and not `unit`, -we can infer that these functions do not modify the set in place, but -instead they return a new set. Again, we can test this theory: - -```ocamltop -let firstSet = SS.singleton "hello";; -let secondSet = SS.add "world" firstSet;; -SS.cardinal firstSet;; -SS.cardinal secondSet;; +Let's look at a few basic functions for working with sets. + +### Adding an Element to a Set + +```ocaml +# let my_set = ["hello"; "hi"] |> StringSet.of_list;; +- : StringSet.t = + +# my_set |> StringSet.add "good morning" |> StringSet.to_list;; +- : string list = ["good morning"; "hello"; "hi"] ``` -It looks like our theories were correct! +The function `StringSet.add` with type `string -> StringSet.t -> StringSet.t` takes a string and a string set. It returns a new string set. Sets created with the `Set` functor in OCaml are immutable, so every time you add or remove an element from a set, a new set is created - the old value is unchanged. -## Sets With Custom Comparators +### Removing an Element from a Set -The `SS` module we created uses the built-in comparison function provided -by the `String` module, which performs a case-sensitive comparison. We -can test that with the following code: +```ocaml +# let my_set = ["hello"; "hi"] |> StringSet.of_list;; +- : StringSet.t = -```ocamltop -let firstSet = SS.singleton "hello";; -let secondSet = SS.add "HELLO" firstSet;; -SS.cardinal firstSet;; -SS.cardinal secondSet;; +# my_set |> StringSet.remove "hello" |> StringSet.to_list;; +- : string list = ["hi"] ``` -As we can see, the `secondSet` has a cardinality of 2, indicating that -`"hello"` and `"HELLO"` are considered two distinct elements. +The function `StringSet.remove` with type `string -> StringSet.t -> StringSet.t` takes a string and a string set. It returns a new string set without the given string. + +### Union of Two Sets + +```ocaml +# let first_set = ["hello"; "hi"] |> StringSet.of_list;; +- : StringSet.t = + +# let second_set = ["good morning"; "hi"] |> StringSet.of_list;; +- : StringSet.t = + +# StringSet.union first_set second_set |> StringSet.to_list;; +- : string list = ["good morning"; "hello"; "hi"] +``` -Let's say we want to create a set that performs a case-insensitive -comparison instead. To do this, simply change the parameter -that passed to the `Set.Make` function. +With the function `StringSet.union`, we can compute the union of two sets. + +### Intersection of Two Sets + +```ocaml +# let first_set = ["hello"; "hi"] |> StringSet.of_list;; +- : StringSet.t = + +# let second_set = ["good morning"; "hi"] |> StringSet.of_list;; +- : StringSet.t = + +# StringSet.inter first_set second_set |> StringSet.to_list;; +- : string list = ["hi"] +``` -The `Set.Make` function expects a struct with two fields: a type `t` +With the function `StringSet.inter`, we can compute the intersection of two sets. + +### Subtracting a Set from Another + +```ocaml +# let first_set = ["hello"; "hi"] |> StringSet.of_list;; +- : StringSet.t = + +# let second_set = ["good morning"; "hi"] |> StringSet.of_list;; +- : StringSet.t = + +# StringSet.diff first_set second_set |> StringSet.to_list;; +- : string list = ["hello"] +``` + +With the function `StringSet.diff`, we can remove the elements from one set from another. + +### Filtering a Set + +```ocaml +# ["good morning"; "hello"; "hi"] + |> StringSet.of_list + |> StringSet.filter (fun str -> String.length str <= 5) + |> StringSet.to_list;; +- : string list = ["hello"; "hi"] +``` + +The function `StringSet.filter` of type `(string -> bool) -> StringSet.t -> StringSet.t` allows us to apply a predicate to all elements of a set to create a new set. + +### Checking if an Element is Contained in a Set + +```ocaml +# ["good morning"; "hello"; "hi"] + |> StringSet.of_list + |> StringSet.mem "hello";; +- : bool = true +``` + +To check if an element is contained in a given set, we can use the `StringSet.mem` function. + +## Sets With Custom Comparators + +The `Set.Make` functor expects a module with two fields: a type `t` that represents the element type and a function `compare`, -whose signature is `t -> t -> int`. It essentially returns 0 if two -values are equal and non-zero if they are non-equal. The +whose signature is `t -> t -> int`. The `String` module matches that structure, so we could -directly pass `String` as a parameter to `Set.Make`. Incidentally, many +directly pass `String` as an argument to `Set.Make`. Incidentally, many other modules also have that structure, including `Int` and `Float`, so they too can be directly passed into `Set.Make` in order to construct a set of integers or a set of floating point numbers. -For our use case, we still want our elements to be a string, but -we want to change the comparison function to ignore the case of the -strings. We can accomplish this by directly passing in a literal struct -to the `Set.Make` function: +The `StringSet` module we created uses the built-in `compare` function provided by the `String` module. + +Let's say we want to create a set of strings that performs a case-insensitive +comparison instead of the case-sensitive comparison provided by `String.compare`. + +We can accomplish this by passing an ad-hoc module to the `Set.Make` function: -```ocamltop -module CISS = Set.Make(struct +```ocaml +# module CISS = Set.Make(struct type t = string let compare a b = compare (String.lowercase_ascii a) (String.lowercase_ascii b) end);; ``` -We name the resulting module CISS (short for "Case Insensitive String Set"). -We can now test whether this module has the desired behavior: +We name the resulting module `CISS` (short for "Case Insensitive String Set"). +You can see that this module has the intended behavior: -```ocamltop -let firstSet = CISS.singleton "hello";; -let secondSet = CISS.add "HELLO" firstSet;; -CISS.cardinal firstSet;; -CISS.cardinal secondSet;; +```ocaml +# CISS.singleton "hello" |> CISS.add "HELLO" |> CISS.to_list;; +- : string list = ["hello"] ``` - -Success! `secondSet` has a cardinality of 1, showing that `"hello"` -and `"HELLO"` are now considered to be the same element in this set. -We now have a set of strings whose compare function performs a case -insensitive comparison. +The value `"HELLO"` is not added to the set because it is considered equal to the value `"hello"` that is already contained in the set. Note that this technique can also be used to allow arbitrary types to be used as the element type for set, as long as you can define a meaningful compare operation: -```ocamltop -type color = Red | Green | Blue;; +```ocaml +# type color = Red | Green | Blue;; +type color = Red | Green | Blue -module SC = Set.Make(struct +# module SC = Set.Make(struct type t = color let compare a b = match (a, b) with @@ -221,37 +235,10 @@ module SC = Set.Make(struct | (Blue, Green) -> -1 | (Blue, Blue) -> 0 end);; +... ``` -## Technical Details - -### `Set.Make`, Types, and Modules - -As mentioned in a previous section, the `Set.Make` function accepts a structure -with two specific fields, `t` and `compare`. Modules have structure, so -it's possible (but not guaranteed) for a module to have the structure that -`Set.Make` expects. On the other hand, types do not have structure, so you -can never pass a type to the `Set.Make` function. In OCaml, modules start with -an upper case letter, and types start with a lower case letter. So -when creating a set of strings, you must use `Set.Make(String)` (passing in -the module named `String`) and not `Set.Make(string)` (this would be attempting -to pass in the type named `string`, which will not work). - -### Purely Functional Data Structures - -The data structure implemented by the `Set` functor is a purely functional one. -What exactly that means is a big topic in itself (feel free to search for -"Purely Functional Data Structure" in Google or Wikipedia to learn more). As a -short oversimplification, this means that all instances of the data structure -created are immutable. The functions like `add` and `remove` do not -actually modify the set passed in, but instead they return a new set representing -the results of having performed the corresponding operation. - -### Full API Documentation - -This tutorial focused on teaching how to quickly find a function that does what -you want by looking at the type signature. This is often the quickest and most -convenient way to discover useful functions. However, sometimes you do want to -see the formal documentation for the API provided by a module. For sets, refer to [this -API documentation[(https://ocaml.org/api/Set.Make.html) +## Conclusion + +We gave an overview of the `Set` module in OCaml by creating a `StringSet` module using the `Set.Make` functor. Further, we looked at how to create sets based on a custom compare function. For more information, refer to [Set in the Standard Library documentation](https://ocaml.org/api/Set.Make.html) From 15a9b57c69771307ebc4d0844132af9a215d843c Mon Sep 17 00:00:00 2001 From: Cuihtlauac ALVARADO Date: Thu, 11 Jan 2024 15:08:23 +0100 Subject: [PATCH 6/6] Review edits --- data/tutorials/language/3ds_03_set.md | 103 +++++++++----------------- 1 file changed, 34 insertions(+), 69 deletions(-) diff --git a/data/tutorials/language/3ds_03_set.md b/data/tutorials/language/3ds_03_set.md index ffc622c48f..7be76358f4 100644 --- a/data/tutorials/language/3ds_03_set.md +++ b/data/tutorials/language/3ds_03_set.md @@ -10,17 +10,9 @@ category: "Data Structures" ## Introduction -`Set` is a functor, which means that it is a module that is parameterised -by another module. More concretely, this means you cannot directly create -a set. Instead, you must first specify what type of elements your set will -contain. +`Set` provides the functor `Set.Make`. You must start by passing `Set.Make` a module. It specifies the element type for your set. In return, you get another module with those elements' set operations. -The `Set` functor provides a function `Make` that accepts a module as a -parameter. Then it returns a new module representing a set of elements of the given type. - -For example, if you want to work with sets of -strings, you can invoke `Set.Make(String)` that will return a new module, -which you can assign a name, for example, `StringSet`: +If you need to work with string sets, you must invoke `Set.Make(String)`. That returns a new module. ```ocaml # module StringSet = Set.Make(String);; module StringSet : @@ -37,20 +29,11 @@ module StringSet : end ``` -After assigning your newly-created module to the name -`StringSet`, OCaml's toplevel displays the type signature of the module. In this case, it contains -a large number of convenience functions for working with sets (for example `is_empty` -checks if the set is empty, `add` adds an element to the set, `remove` -removes an element, and so on). - -This module also defines two types: `type elt = String.t`, representing -the type of the elements, and `type t = Set.Make(String).t`, representing the type of -the set itself. These types are used by many of -the functions defined in this module. +After naming the newly-created module `StringSet`, OCaml's toplevel displays the module's signature. Since it contains a large number of functions, the output copied here is shortened for brevity (`...`). -For example, the `add` function has the type `elt -> t -> t`, which means -that it expects an element (a string) and a set of strings. Then it will return -a set of strings. +This module also defines two types: +- `type elt = string` for the elements, and +- `type t = Set.Make(String).t` for the sets. ## Creating a Set @@ -63,7 +46,7 @@ a set of strings. - : string list = [] ``` -For `StringSet.empty`, you can see that the OCaml toplevel displays `` instead of a displaying the actual value. This is a limitation of the toplevel. However, you can see that converting the string set to a list using `StringSet.to_list` results in the empty list. +For `StringSet.empty`, you can see that the OCaml toplevel displays the placeholder `` instead of the actual value. However, converting the string set to a list using `StringSet.to_list` results in an empty list. 2. A set with a single element is created using `StringSet.singleton`: ```ocaml @@ -78,47 +61,45 @@ For `StringSet.empty`, you can see that the OCaml toplevel displays `` in ```ocaml # StringSet.of_list ["hello"; "hi"];; - : StringSet.t = + +# StringSet.of_list ["hello"; "hi"] |> StringSet.to_list;; +- : string list = ["hello"; "hi"] ``` -There's another relevant function `StringSet.of_seq: string Seq.t -> StringSet.t` which creates a list from a [sequence](/doc/sequences). +There's another relevant function `StringSet.of_seq: string Seq.t -> StringSet.t` that creates a set from a [sequence](/doc/sequences). ## Working With Sets -Let's look at a few basic functions for working with sets. +Let's look at a few functions for working with sets using these two sets. +```ocaml +# let first_set = ["hello"; "hi"] |> StringSet.of_list;; +- : StringSet.t = + +# let second_set = ["good morning"; "hi"] |> StringSet.of_list;; +- : StringSet.t = +``` ### Adding an Element to a Set ```ocaml -# let my_set = ["hello"; "hi"] |> StringSet.of_list;; -- : StringSet.t = - -# my_set |> StringSet.add "good morning" |> StringSet.to_list;; +# first_set |> StringSet.add "good morning" |> StringSet.to_list;; - : string list = ["good morning"; "hello"; "hi"] ``` -The function `StringSet.add` with type `string -> StringSet.t -> StringSet.t` takes a string and a string set. It returns a new string set. Sets created with the `Set` functor in OCaml are immutable, so every time you add or remove an element from a set, a new set is created - the old value is unchanged. +The function `StringSet.add` with type `string -> StringSet.t -> StringSet.t` takes both a string and a string set. It returns a new string set. Sets created with the `Set.Make` functor in OCaml are immutable, so every time you add or remove an element from a set, a new set is created. The old value is unchanged. ### Removing an Element from a Set ```ocaml -# let my_set = ["hello"; "hi"] |> StringSet.of_list;; -- : StringSet.t = - -# my_set |> StringSet.remove "hello" |> StringSet.to_list;; +# first_set |> StringSet.remove "hello" |> StringSet.to_list;; - : string list = ["hi"] ``` -The function `StringSet.remove` with type `string -> StringSet.t -> StringSet.t` takes a string and a string set. It returns a new string set without the given string. +The function `StringSet.remove` with type `string -> StringSet.t -> StringSet.t` takes both a string and a string set. It returns a new string set without the given string. ### Union of Two Sets ```ocaml -# let first_set = ["hello"; "hi"] |> StringSet.of_list;; -- : StringSet.t = - -# let second_set = ["good morning"; "hi"] |> StringSet.of_list;; -- : StringSet.t = - # StringSet.union first_set second_set |> StringSet.to_list;; - : string list = ["good morning"; "hello"; "hi"] ``` @@ -128,12 +109,6 @@ With the function `StringSet.union`, we can compute the union of two sets. ### Intersection of Two Sets ```ocaml -# let first_set = ["hello"; "hi"] |> StringSet.of_list;; -- : StringSet.t = - -# let second_set = ["good morning"; "hi"] |> StringSet.of_list;; -- : StringSet.t = - # StringSet.inter first_set second_set |> StringSet.to_list;; - : string list = ["hi"] ``` @@ -143,17 +118,11 @@ With the function `StringSet.inter`, we can compute the intersection of two sets ### Subtracting a Set from Another ```ocaml -# let first_set = ["hello"; "hi"] |> StringSet.of_list;; -- : StringSet.t = - -# let second_set = ["good morning"; "hi"] |> StringSet.of_list;; -- : StringSet.t = - # StringSet.diff first_set second_set |> StringSet.to_list;; - : string list = ["hello"] ``` -With the function `StringSet.diff`, we can remove the elements from one set from another. +With the function `StringSet.diff`, we can remove the elements of the second set from the first set. ### Filtering a Set @@ -165,7 +134,7 @@ With the function `StringSet.diff`, we can remove the elements from one set from - : string list = ["hello"; "hi"] ``` -The function `StringSet.filter` of type `(string -> bool) -> StringSet.t -> StringSet.t` allows us to apply a predicate to all elements of a set to create a new set. +The function `StringSet.filter` of type `(string -> bool) -> StringSet.t -> StringSet.t` creates a new set by keeping the elements that satisfy a predicate from an existing set. ### Checking if an Element is Contained in a Set @@ -176,18 +145,17 @@ The function `StringSet.filter` of type `(string -> bool) -> StringSet.t -> Stri - : bool = true ``` -To check if an element is contained in a given set, we can use the `StringSet.mem` function. +To check if an element is contained in a set, use the `StringSet.mem` function. ## Sets With Custom Comparators -The `Set.Make` functor expects a module with two fields: a type `t` -that represents the element type and a function `compare`, -whose signature is `t -> t -> int`. The +The `Set.Make` functor expects a module with two definitions: a type `t` +that represents the element type and the function `compare`, +whose signature is `t -> t -> int`. The `String` module matches that structure, so we could directly pass `String` as an argument to `Set.Make`. Incidentally, many other modules also have that structure, including `Int` and `Float`, -so they too can be directly passed into `Set.Make` in order to construct a -set of integers or a set of floating point numbers. +so they too can be directly passed into `Set.Make` to construct a corresponding set module. The `StringSet` module we created uses the built-in `compare` function provided by the `String` module. @@ -211,12 +179,9 @@ You can see that this module has the intended behavior: # CISS.singleton "hello" |> CISS.add "HELLO" |> CISS.to_list;; - : string list = ["hello"] ``` -The value `"HELLO"` is not added to the set because it is considered equal to the value `"hello"` that is already contained in the set. - -Note that this technique can also be used to allow arbitrary types -to be used as the element type for set, as long as you can define a -meaningful compare operation: +The value `"HELLO"` is not added to the set because it is considered equal to the value `"hello"`, which is already contained in the set. +You can use any type for elements, as long as you define a meaningful `compare` operation: ```ocaml # type color = Red | Green | Blue;; type color = Red | Green | Blue @@ -224,7 +189,7 @@ type color = Red | Green | Blue # module SC = Set.Make(struct type t = color let compare a b = - match (a, b) with + match a, b with | (Red, Red) -> 0 | (Red, Green) -> 1 | (Red, Blue) -> 1 @@ -240,5 +205,5 @@ end);; ## Conclusion -We gave an overview of the `Set` module in OCaml by creating a `StringSet` module using the `Set.Make` functor. Further, we looked at how to create sets based on a custom compare function. For more information, refer to [Set in the Standard Library documentation](https://ocaml.org/api/Set.Make.html) +We gave an overview of the `Set` module in OCaml by creating a `StringSet` module using the `Set.Make` functor. Further, we looked at how to create sets based on a custom comparison function. For more information, refer to [Set](https://ocaml.org/api/Set.Make.html) in the Standard Library documentation.