Skip to content

Commit 7b937c3

Browse files
authored
Merge pull request #566 from rescript-association/optional-record-fields
docs for optional record fields
2 parents 93dbd78 + 13ef8b7 commit 7b937c3

File tree

1 file changed

+245
-2
lines changed

1 file changed

+245
-2
lines changed

pages/docs/manual/latest/record.mdx

Lines changed: 245 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ A record needs a mandatory type declaration:
2020
```res prelude
2121
type person = {
2222
age: int,
23-
name: string
23+
name: string,
2424
}
2525
```
2626
```js
@@ -157,9 +157,247 @@ Fields not marked with `mutable` in the type declaration cannot be mutated.
157157

158158
ReScript records compile to straightforward JavaScript objects; see the various JS output tabs above.
159159

160+
## Optional Record Fields
161+
ReScript [`v10`](/blog/release-10-0-0#experimental-optional-record-fields) introduced optional record fields. This means that you can define fields that can be omitted when creating the record. It looks like this:
162+
163+
<CodeTab labels={["ReScript", "JS Output"]}>
164+
165+
```res prelude
166+
type person = {
167+
age: int,
168+
name?: string
169+
}
170+
```
171+
```js
172+
// Empty output
173+
```
174+
175+
</CodeTab>
176+
177+
Notice how `name` has a suffixed `?`. That means that the field itself is _optional_.
178+
179+
### Creation
180+
You can omit any optional fields when creating a record. Not setting an optional field will default the field's value to `None`:
181+
182+
<CodeTab labels={["ReScript", "JS Output"]}>
183+
184+
```res prelude
185+
type person = {
186+
age: int,
187+
name?: string
188+
}
189+
190+
let me = {
191+
age: 5,
192+
name: "Big ReScript"
193+
}
194+
195+
let friend = {
196+
age: 7
197+
}
198+
```
199+
```js
200+
var me = {
201+
age: 5,
202+
name: "Big ReScript"
203+
};
204+
205+
var friend = {
206+
age: 7
207+
};
208+
```
209+
210+
</CodeTab>
211+
212+
This has consequences for pattern matching, which we'll expand a bit on soon.
213+
214+
## Immutable Update
215+
Updating an optional field via an immutable update above lets you set that field value without needing to care whether it's optional or not.
216+
217+
<CodeTab labels={["ReScript", "JS Output"]}>
218+
219+
```res example
220+
type person = {
221+
age: int,
222+
name?: string
223+
}
224+
225+
let me = {
226+
age: 123,
227+
name: "Hello"
228+
}
229+
230+
let withoutName = {
231+
...me,
232+
name: "New Name"
233+
}
234+
```
235+
```js
236+
import * as Caml_obj from "./stdlib/caml_obj.js";
237+
238+
var me = {
239+
age: 123,
240+
name: "Hello"
241+
};
242+
243+
var newrecord = Caml_obj.obj_dup(me);
244+
245+
newrecord.name = "New Name";
246+
247+
var withoutName = newrecord;
248+
```
249+
250+
</CodeTab>
251+
252+
253+
However, if you want to set the field to an optional value, you prefix that value with `?`:
254+
255+
<CodeTab labels={["ReScript", "JS Output"]}>
256+
257+
```res example
258+
type person = {
259+
age: int,
260+
name?: string
261+
}
262+
263+
let me = {
264+
age: 123,
265+
name: "Hello"
266+
}
267+
268+
let maybeName = Some("My Name")
269+
270+
let withoutName = {
271+
...me,
272+
name: ?maybeName
273+
}
274+
```
275+
```js
276+
import * as Caml_obj from "./stdlib/caml_obj.js";
277+
278+
var me = {
279+
age: 123,
280+
name: "Hello"
281+
};
282+
283+
var maybeName = "My Name";
284+
285+
var newrecord = Caml_obj.obj_dup(me);
286+
287+
newrecord.name = maybeName;
288+
289+
var withoutName = newrecord;
290+
```
291+
292+
</CodeTab>
293+
294+
You can unset an optional field's value via that same mechanism by setting it to `?None`.
295+
296+
### Pattern Matching on Optional Fields
297+
[Pattern matching](pattern-matching-destructuring), one of ReScript's most important features, has two caveats when you deal with optional fields.
298+
299+
When matching on the value directly, it's an `option`. Example:
300+
301+
<CodeTab labels={["ReScript", "JS Output"]}>
302+
303+
```res
304+
type person = {
305+
age: int,
306+
name?: string,
307+
}
308+
309+
let me = {
310+
age: 123,
311+
name: "Hello",
312+
}
313+
314+
let isRescript = switch me.name {
315+
| Some("ReScript") => true
316+
| Some(_) | None => false
317+
}
318+
```
319+
```js
320+
var isRescript;
321+
322+
isRescript = "Hello" === "ReScript" ? true : false;
323+
324+
var me = {
325+
age: 123,
326+
name: "Hello"
327+
};
328+
```
329+
330+
</CodeTab>
331+
332+
But, when matching on the field as part of the general record structure, it's treated as the underlying, non-optional value:
333+
334+
<CodeTab labels={["ReScript", "JS Output"]}>
335+
336+
```res
337+
type person = {
338+
age: int,
339+
name?: string,
340+
}
341+
342+
let me = {
343+
age: 123,
344+
name: "Hello",
345+
}
346+
347+
let isRescript = switch me {
348+
| {name: "ReScript"} => true
349+
| _ => false
350+
}
351+
352+
```
353+
```js
354+
var isRescript;
355+
356+
isRescript = "Hello" === "ReScript" ? true : false;
357+
358+
var me = {
359+
age: 123,
360+
name: "Hello"
361+
};
362+
```
363+
364+
</CodeTab>
365+
366+
Sometimes you _do_ want to know whether the field was set or not. You can tell the pattern matching engine about that by prefixing your option match with `?`, like this:
367+
368+
<CodeTab labels={["ReScript", "JS Output"]}>
369+
370+
```res
371+
type person = {
372+
age: int,
373+
name?: string,
374+
}
375+
376+
let me = {
377+
age: 123,
378+
name: "Hello",
379+
}
380+
381+
let nameWasSet = switch me {
382+
| {name: ?None} => false
383+
| {name: ?Some(_)} => true
384+
}
385+
```
386+
```js
387+
var nameWasSet = false;
388+
389+
var me = {
390+
age: 123,
391+
name: "Hello"
392+
};
393+
```
394+
395+
</CodeTab>
396+
160397
## Tips & Tricks
161398

162-
**Record Types Are Found By Field Name**. With records, you **cannot** say "I'd like this function to take any record type, as long as they have the field `age`". The following **won't work as intended**:
399+
### Record Types Are Found By Field Name
400+
With records, you **cannot** say "I'd like this function to take any record type, as long as they have the field `age`". The following **won't work as intended**:
163401

164402
<CodeTab labels={["ReScript", "JS Output"]}>
165403

@@ -189,6 +427,11 @@ getAge(me) // type error!
189427

190428
The type system will complain that `me` is a `person`, and that `getAge` only works on `monster`. If you need such capability, use ReScript objects, described [here](object.md).
191429

430+
### Optional Fields in Records Can Be Useful for Bindings
431+
Many JavaScript APIs tend to have large configuration objects that can be a bit annoying to model as records, since you previously always needed to specify all record fields when creating a record.
432+
433+
Optional record fields, introduced in [`v10`](/blog/release-10-0-0#experimental-optional-record-fields), is intended to help with this. Optional fields will let you avoid having to specify all fields, and let you just specify the one's you care about. A significant improvement in ergonomics for bindings and other APIs with for example large configuration objects.
434+
192435
## Design Decisions
193436

194437
After reading the constraints in the previous sections, and if you're coming from a dynamic language background, you might be wondering why one would bother with record in the first place instead of straight using object, since the former needs explicit typing and doesn't allow different records with the same field name to be passed to the same function, etc.

0 commit comments

Comments
 (0)