|
22 | 22 | * along with this program; if not, write to the Free Software
|
23 | 23 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *)
|
24 | 24 |
|
25 |
| -(** A stdlib shipped with ReScript |
| 25 | +(** The ReScript standard library. |
26 | 26 |
|
27 |
| - This stdlib is still in _beta_ but we encourage you to try it out and |
28 |
| - give us feedback. |
| 27 | +Belt is currently mostly covering collection types. It has no string or date functions yet, although Belt.String is in the works. In the meantime, use [Js.String](js/string) for string functions and [Js.Date](js/date) for date functions. |
29 | 28 |
|
30 |
| - **Motivation** |
| 29 | +## Motivation |
31 | 30 |
|
32 |
| - The motivation for creating such library is to provide ReScript users a |
33 |
| - better end-to-end user experience, since the original OCaml stdlib was not |
34 |
| - written with JS in mind. Below is a list of areas this lib aims to |
35 |
| - improve: |
36 |
| - 1. Consistency in name convention: camlCase, and arguments order |
37 |
| - 2. Exception thrown functions are all suffixed with _Exn_, e.g, _getExn_ |
38 |
| - 3. Better performance and smaller code size running on JS platform |
| 31 | +Belt provides: |
39 | 32 |
|
40 |
| - **Name Convention** |
| 33 | +- The **highest quality** immutable data structures in JavaScript. |
| 34 | +- Safety by default: A Belt function will never throw exceptions, unless it is |
| 35 | + indicated explicitly in the function name (suffix "Exn"). |
| 36 | +- Better performance and smaller code size running on the JS platform. |
| 37 | +- Ready for [Tree Shaking](https://webpack.js.org/guides/tree-shaking/). |
41 | 38 |
|
42 |
| - For higher order functions, it will be suffixed **U** if it takes uncurried |
43 |
| - callback. |
| 39 | +## Usage |
44 | 40 |
|
45 |
| - ``` |
46 |
| - val forEach : 'a t -> ('a -> unit) -> unit |
47 |
| - val forEachU : 'a t -> ('a -> unit [@bs]) -> unit |
48 |
| - ``` |
| 41 | +To use modules from Belt, either refer to them by their fully qualified name (`Belt.List`, `Belt.Array` etc.) or open the `Belt` module by putting |
49 | 42 |
|
50 |
| - In general, uncurried version will be faster, but it may be less familiar to |
51 |
| - people who have a background in functional programming. |
| 43 | +``` |
| 44 | +open Belt |
| 45 | +``` |
52 | 46 |
|
53 |
| - **A special encoding for collection safety** |
| 47 | +at the top of your source files. After opening Belt this way, `Array` will refer to `Belt.Array`, `List` will refer to `Belt.List` etc. in the subsequent code. |
54 | 48 |
|
55 |
| - When we create a collection library for a custom data type we need a way to provide a comparator |
56 |
| - function. Take _Set_ for example, suppose its element type is a pair of ints, |
57 |
| - it needs a custom _compare_ function that takes two tuples and returns their order. |
58 |
| - The _Set_ could not just be typed as `Set.t (int * int)`, its customized _compare_ function |
59 |
| - needs to manifest itself in the signature, otherwise, if the user creates another |
60 |
| - customized _compare_ function, the two collection could mix which would result in runtime error. |
| 49 | +If you want to open Belt globally for all files in your project instead, you can put |
61 | 50 |
|
62 |
| - The original OCaml stdlib solved the problem using _functor_ which creates a big |
63 |
| - closure at runtime and makes dead code elimination much harder. |
64 |
| - We use a phantom type to solve the problem: |
| 51 | +```json |
| 52 | + "bsc-flags": ["-open Belt"], |
| 53 | +``` |
65 | 54 |
|
66 |
| - ``` |
67 |
| - module Comparable1 = Belt.Id.MakeComparable(struct |
68 |
| - type t = int * int |
69 |
| - let cmp (a0, a1) (b0, b1) = |
70 |
| - match Pervasives.compare a0 b0 with |
71 |
| - | 0 -> Pervasives.compare a1 b1 |
72 |
| - | c -> c |
73 |
| - end) |
| 55 | +into your `bsconfig.json`. |
74 | 56 |
|
75 |
| - let mySet1 = Belt.Set.make ~id:(module Comparable1) |
| 57 | +**Note**: this is the **only** `open` we encourage. |
76 | 58 |
|
77 |
| - module Comparable2 = Belt.Id.MakeComparable(struct |
78 |
| - type t = int * int |
79 |
| - let cmp (a0, a1) (b0, b1) = |
80 |
| - match Pervasives.compare a0 b0 with |
81 |
| - | 0 -> Pervasives.compare a1 b1 |
82 |
| - | c -> c |
83 |
| - end) |
| 59 | +Example usage: |
84 | 60 |
|
85 |
| - let mySet2 = Belt.Set.make ~id:(module Comparable2) |
86 |
| - ``` |
| 61 | +``` |
| 62 | +let someNumbers = [1, 1, 4, 2, 3, 6, 3, 4, 2] |
87 | 63 |
|
88 |
| - Here, the compiler would infer `mySet1` and `mySet2` having different type, so |
89 |
| - e.g. a `merge` operation that tries to merge these two sets will correctly fail. |
| 64 | +let greaterThan2UniqueAndSorted = |
| 65 | + someNumbers |
| 66 | + ->Belt.Array.keep(x => x > 2) |
| 67 | + // convert to and from set to make values unique |
| 68 | + ->Belt.Set.Int.fromArray |
| 69 | + ->Belt.Set.Int.toArray // output is already sorted |
90 | 70 |
|
91 |
| - ``` |
92 |
| - val mySet1 : ((int * int), Comparable1.identity) t |
93 |
| - val mySet2 : ((int * int), Comparable2.identity) t |
94 |
| - ``` |
| 71 | +Js.log2("result", greaterThan2UniqueAndSorted) |
| 72 | +``` |
95 | 73 |
|
96 |
| - `Comparable1.identity` and `Comparable2.identity` are not the same using our encoding scheme. |
| 74 | +## Curried vs. Uncurried Callbacks |
97 | 75 |
|
98 |
| - **Collection Hierarchy** |
| 76 | +For functions taking a callback parameter, there are usually two versions |
| 77 | +available: |
99 | 78 |
|
100 |
| - In general, we provide a generic collection module, but also create specialized |
101 |
| - modules for commonly used data type. Take _Belt.Set_ for example, we provide: |
| 79 | +- curried (no suffix) |
| 80 | +- uncurried (suffixed with `U`) |
102 | 81 |
|
103 |
| - ``` |
104 |
| - Belt.Set |
105 |
| - Belt.Set.Int |
106 |
| - Belt.Set.String |
107 |
| - ``` |
| 82 | +E.g.: |
108 | 83 |
|
109 |
| - The specialized modules _Belt.Set.Int_, _Belt.Set.String_ are in general more |
110 |
| - efficient. |
| 84 | +``` |
| 85 | +let forEach: (t<'a>, 'a => unit) => unit |
111 | 86 |
|
112 |
| - Currently, both _Belt_Set_ and _Belt.Set_ are accessible to users for some |
113 |
| - technical reasons, |
114 |
| - we **strongly recommend** users stick to qualified import, _Belt.Set_, we may hide |
115 |
| - the internal, _i.e_, _Belt_Set_ in the future |
| 87 | +let forEachU: (t<'a>, (. 'a) => unit) => unit |
| 88 | +``` |
| 89 | +
|
| 90 | +The uncurried version will be faster in some cases, but for simplicity we recommend to stick with the curried version unless you need the extra performance. |
| 91 | +
|
| 92 | +The two versions can be invoked as follows: |
| 93 | +
|
| 94 | +``` |
| 95 | +["a", "b", "c"]->Belt.Array.forEach(x => Js.log(x)) |
| 96 | +
|
| 97 | +["a", "b", "c"]->Belt.Array.forEachU((. x) => Js.log(x)) |
| 98 | +``` |
| 99 | +
|
| 100 | +## Specialized Collections |
| 101 | +
|
| 102 | +For collections types like set or map, Belt provides both a generic module as well as specialized, more efficient implementations for string and int keys. |
| 103 | +
|
| 104 | +For example, Belt has the following set modules: |
| 105 | +
|
| 106 | +- [Belt.Set](belt/set) |
| 107 | +- [Belt.Set.Int](belt/set-int) |
| 108 | +- [Belt.Set.String](belt/set-string) |
| 109 | +
|
| 110 | +## Implementation Details |
| 111 | +
|
| 112 | +### Array access runtime safety |
| 113 | +
|
| 114 | +One common confusion comes from the way Belt handles array access. It differs from than the default standard library's. |
| 115 | +
|
| 116 | +``` |
| 117 | +let letters = ["a", "b", "c"] |
| 118 | +let a = letters[0] // a == "a" |
| 119 | +let capitalA = Js.String.toUpperCase(a) |
| 120 | +let k = letters[10] // Raises an exception! The 10th index doesn't exist. |
| 121 | +``` |
| 122 | +
|
| 123 | +Because Belt avoids exceptions and returns `options` instead, this code behaves differently: |
| 124 | +
|
| 125 | +``` |
| 126 | +open Belt |
| 127 | +let letters = ["a", "b", "c"] |
| 128 | +let a = letters[0] // a == Some("a") |
| 129 | +let captialA = Js.String.toUpperCase(a) // Type error! This code will not compile. |
| 130 | +let k = letters[10] // k == None |
| 131 | +``` |
| 132 | +
|
| 133 | +Although we've fixed the problem where `k` raises an exception, we now have a type error when trying to capitalize `a`. There are a few things going on here: |
| 134 | +
|
| 135 | +- Reason transforms array index access to the function `Array.get`. So `letters[0]` is the same as `Array.get(letters, 0)`. |
| 136 | +- The compiler uses whichever `Array` module is in scope. If you `open Belt`, then it uses `Belt.Array`. |
| 137 | +- `Belt.Array.get` returns values wrapped in options, so `letters[0] == Some("a")`. |
| 138 | +
|
| 139 | +Fortunately, this is easy to fix: |
| 140 | +
|
| 141 | +```res example |
| 142 | +open Belt |
| 143 | +let letters = ["a", "b", "c"] |
| 144 | +let a = letters[0] |
| 145 | +
|
| 146 | +// Use a switch statement: |
| 147 | +let capitalA = |
| 148 | + switch a { |
| 149 | + | Some(a) => Some(Js.String.toUpperCase(a)) |
| 150 | + | None => None |
| 151 | + } |
| 152 | +
|
| 153 | +let k = letters[10] // k == None |
| 154 | +``` |
| 155 | +
|
| 156 | +With that little bit of tweaking, our code now compiles successfully and is 100% free of runtime errors! |
| 157 | +
|
| 158 | +### A Special Encoding for Collection Safety |
| 159 | +
|
| 160 | +When we create a collection library for a custom data type we need a way to provide a comparator function. Take Set for example, suppose its element type is a pair of ints, it needs a custom compare function that takes two tuples and returns their order. The Set could not just be typed as Set.t (int \* int) , its customized compare function needs to manifest itself in the signature, otherwise, if the user creates another customized compare function, the two collection could mix which would result in runtime error. |
| 161 | +
|
| 162 | +We use a phantom type to solve the problem: |
| 163 | +
|
| 164 | +``` |
| 165 | +module Comparable1 = |
| 166 | + Belt.Id.MakeComparable( |
| 167 | + { |
| 168 | + type t = (int, int) |
| 169 | + let cmp = ((a0, a1), (b0, b1)) => |
| 170 | + switch Pervasives.compare(a0, b0) { |
| 171 | + | 0 => Pervasives.compare(a1, b1) |
| 172 | + | c => c |
| 173 | + } |
| 174 | + } |
| 175 | + ) |
| 176 | +
|
| 177 | +let mySet1 = Belt.Set.make(~id=module(Comparable1)) |
| 178 | +
|
| 179 | +module Comparable2 = |
| 180 | + Belt.Id.MakeComparable( |
| 181 | + { |
| 182 | + type t = (int, int) |
| 183 | + let cmp = ((a0, a1), (b0, b1)) => |
| 184 | + switch Pervasives.compare(a0, b0) { |
| 185 | + | 0 => Pervasives.compare(a1, b1) |
| 186 | + | c => c |
| 187 | + } |
| 188 | + } |
| 189 | + ) |
| 190 | +
|
| 191 | +let mySet2 = Belt.Set.make(~id=module(Comparable2)) |
| 192 | +``` |
| 193 | +
|
| 194 | +Here, the compiler would infer `mySet1` and `mySet2` having different type, so e.g. a `merge` operation that tries to merge these two sets will correctly fail. |
| 195 | +
|
| 196 | +``` |
| 197 | +let mySet1: t<(int, int), Comparable1.identity> |
| 198 | +let mySet2: t<(int, int), Comparable2.identity> |
| 199 | +``` |
| 200 | +
|
| 201 | +`Comparable1.identity` and `Comparable2.identity` are not the same using our encoding scheme. |
116 | 202 |
|
117 | 203 | *)
|
118 | 204 |
|
@@ -256,7 +342,6 @@ module Result = Belt_Result
|
256 | 342 |
|
257 | 343 | module Int = Belt_Int
|
258 | 344 |
|
259 |
| - |
260 | 345 | (** [`Belt.Float`]()
|
261 | 346 |
|
262 | 347 | Utilities for Float.
|
|
0 commit comments