Melange for React Developers Book
Installation
make install
make install
Commands
Serve the site at http://localhost:5173/
make serve
make serve
diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/404.html b/404.html new file mode 100644 index 00000000..6e806f73 --- /dev/null +++ b/404.html @@ -0,0 +1,20 @@ + + +
+ + +404
But if you don't change your direction, and if you keep looking, you may end up where you are heading.
make install
make install
Serve the site at http://localhost:5173/
make serve
make serve
Our second widget will be one that takes a temperature value in Celsius and converts it to Fahrenheit. Create a new file called CelsiusConverter.re
:
let convert = celsius => 9.0 /. 5.0 *. celsius +. 32.0;
+
+[@react.component]
+let make = () => {
+ let (celsius, setCelsius) = React.useState(() => "");
+
+ <div>
+ <input
+ value=celsius
+ onChange={evt => {
+ let newCelsius = ReactEvent.Form.target(evt)##value;
+ setCelsius(_ => newCelsius);
+ }}
+ />
+ {React.string({js|°C = |js})}
+ {React.string({js|?°F|js})}
+ </div>;
+};
let convert = celsius => 9.0 /. 5.0 *. celsius +. 32.0;
+
+[@react.component]
+let make = () => {
+ let (celsius, setCelsius) = React.useState(() => "");
+
+ <div>
+ <input
+ value=celsius
+ onChange={evt => {
+ let newCelsius = ReactEvent.Form.target(evt)##value;
+ setCelsius(_ => newCelsius);
+ }}
+ />
+ {React.string({js|°C = |js})}
+ {React.string({js|?°F|js})}
+ </div>;
+};
Inside the input
’s onChange
handler, we get the event target using ReactEvent.Form.target
, which has the type ReactEvent.Form.t => {_.. }
. What is {_.. }
? It’s shorthand for the Js.t({..})
type[1], which consists of two parts:
Js.t
refers to JavaScript objects in Melange{..}
refers to polymorphic objects which can contain any fields or methodsOnce we have a Js.t
object, we can use the ##
operator to access its fields. But beware, because the compiler knows nothing about the types of those fields. For example, we could write ReactEvent.Form.target(evt)##value + 1
, treating it as if it were an integer, and the compiler wouldn’t complain.
Js.t
Instead of trusting ourselves to always use ReactEvent.Form.target(evt)##value
correctly, it’s a good idea to wrap functions that return polymorphic objects into type-annotated helper functions. For example:
let getValueFromEvent = (evt): string =>
+ ReactEvent.Form.target(evt)##value;
+
+let convert = celsius => 9.0 /. 5.0 *. celsius +. 32.0;
+
+[@react.component]
+let make = () => {
+ let (celsius, setCelsius) = React.useState(() => "");
+
+ <div>
+ <input
+ value=celsius
+ onChange={evt => {
+ let newCelsius = getValueFromEvent(evt);
+ setCelsius(_ => newCelsius);
+ }}
+ />
+ {React.string({js|°C = |js})}
+ {React.string({js|?°F|js})}
+ </div>;
+};
let getValueFromEvent = (evt): string =>
+ ReactEvent.Form.target(evt)##value;
+
+let convert = celsius => 9.0 /. 5.0 *. celsius +. 32.0;
+
+[@react.component]
+let make = () => {
+ let (celsius, setCelsius) = React.useState(() => "");
+
+ <div>
+ <input
+ value=celsius
+ onChange={evt => {
+ let newCelsius = getValueFromEvent(evt);
+ setCelsius(_ => newCelsius);
+ }}
+ />
+ {React.string({js|°C = |js})}
+ {React.string({js|?°F|js})}
+ </div>;
+};
The : string
after the argument list tells the compiler that this function must return a string
. Using the getValueFromEvent
function ensures that the value
field can’t be used as anything other than a string.
Another thing to note about onChange
is that after the evt
argument, the body of the callback function is surrounded by braces ({}
). OCaml functions are like JavaScript’s arrow functions—if they contain more than one line, they need to be enclosed by braces.
|>
) Let’s change the render logic to update the Fahrenheit display based on the value of celsius:
<div>
+ <input /* ... */ />
+ {React.string({js|°C = |js})}
+ {celsius |> float_of_string |> convert |> string_of_float |> React.string}
+</div>;
<div>
+ <input /* ... */ />
+ {React.string({js|°C = |js})}
+ {celsius |> float_of_string |> convert |> string_of_float |> React.string}
+</div>;
The pipe last operator (|>
) is very handy here, allowing us to convert a string to float, then convert that float to another float (Celsius -> Fahrenheit), convert back to string, and finally convert the string to React.element
, all in one line.
++
) We should probably put °F after the Fahrenheit value so that it’s clear to the user what unit of measure they’re seeing. We can do so using the string concatenation operator (++
):
<div>
+ <input /* ... */ />
+ {React.string({js|°C = |js})}
+ {(celsius |> float_of_string |> convert |> string_of_float)
+ ++ {js|°F|js}
+ |> React.string}
+</div>;
<div>
+ <input /* ... */ />
+ {React.string({js|°C = |js})}
+ {(celsius |> float_of_string |> convert |> string_of_float)
+ ++ {js|°F|js}
+ |> React.string}
+</div>;
However, there’s a bug in this code: it will crash if you enter anything into the input that can’t be converted to a float. We can remedy this by catching the exception using a switch
expression:
<div>
+ <input /* ... */ />
+ {React.string({js|°C = |js})}
+ {(
+ switch (celsius |> float_of_string |> convert |> string_of_float) {
+ | exception _ => "error"
+ | fahrenheit => fahrenheit ++ {js|°F|js}
+ }
+ )
+ |> React.string}
+</div>;
<div>
+ <input /* ... */ />
+ {React.string({js|°C = |js})}
+ {(
+ switch (celsius |> float_of_string |> convert |> string_of_float) {
+ | exception _ => "error"
+ | fahrenheit => fahrenheit ++ {js|°F|js}
+ }
+ )
+ |> React.string}
+</div>;
The | exception _
branch will execute if there is any exception. The underscore (_
) is a wildcard, meaning it will match any exception. If we wanted to be specific about which exception we want to catch, we could instead write
<div>
+ <input /* ... */ />
+ {React.string({js|°C = |js})}
+ {(
+ switch (celsius |> float_of_string |> convert |> string_of_float) {
+ | exception (Failure(_)) => "error"
+ | fahrenheit => fahrenheit ++ {js|°F|js}
+ }
+ )
+ |> React.string}
+</div>;
<div>
+ <input /* ... */ />
+ {React.string({js|°C = |js})}
+ {(
+ switch (celsius |> float_of_string |> convert |> string_of_float) {
+ | exception (Failure(_)) => "error"
+ | fahrenheit => fahrenheit ++ {js|°F|js}
+ }
+ )
+ |> React.string}
+</div>;
Right now it correctly renders “error” when you enter an invalid value, but it also renders “error” if the input is blank. It might be bit more user-friendly to instead show “?°F” like before. We can do that by wrapping the switch expression in a ternary expression:
<div>
+ <input /* ... */ />
+ {React.string({js|°C = |js})}
+ {(
+ celsius == ""
+ ? {js|?°F|js}
+ : (
+ switch (celsius |> float_of_string |> convert |> string_of_float) {
+ | exception _ => "error"
+ | fahrenheit => fahrenheit ++ {js|°F|js}
+ }
+ )
+ )
+ |> React.string}
+</div>;
<div>
+ <input /* ... */ />
+ {React.string({js|°C = |js})}
+ {(
+ celsius == ""
+ ? {js|?°F|js}
+ : (
+ switch (celsius |> float_of_string |> convert |> string_of_float) {
+ | exception _ => "error"
+ | fahrenheit => fahrenheit ++ {js|°F|js}
+ }
+ )
+ )
+ |> React.string}
+</div>;
The ternary expression (condition ? a : b
) works the same as in JavaScript. But in OCaml, it’s also shorthand for an if-else expression (if (condition) { a; } else { b; }
). So we could rewrite it as this:
<div>
+ <input /* ... */ />
+ {React.string({js|°C = |js})}
+ {(
+ if (celsius == "") {
+ {js|?°F|js};
+ } else {
+ switch (celsius |> float_of_string |> convert |> string_of_float) {
+ | exception _ => "error"
+ | fahrenheit => fahrenheit ++ {js|°F|js}
+ };
+ }
+ )
+ |> React.string}
+</div>;
<div>
+ <input /* ... */ />
+ {React.string({js|°C = |js})}
+ {(
+ if (celsius == "") {
+ {js|?°F|js};
+ } else {
+ switch (celsius |> float_of_string |> convert |> string_of_float) {
+ | exception _ => "error"
+ | fahrenheit => fahrenheit ++ {js|°F|js}
+ };
+ }
+ )
+ |> React.string}
+</div>;
Unlike in JavaScript, the if-else construct is an expression and always yields a value. Both branches must return a value of the same type or you’ll get a compilation error. In practice, if-else expressions aren’t very common in OCaml code because in simple cases you can use ternary, and in more complex cases you can use switch. But it’s a nice, familiar fallback you can rely on when you haven’t quite gotten used to OCaml syntax yet.
If we enter a value with a lot of decimals in it, e.g. 21.1223456
, we’ll get a Fahrenheit value with a lot of decimals in it as well. We can limit the number of decimals in the converted value using Js.Float.toFixedWithPrecision:
<div>
+ <input /* ... */ />
+ {React.string({js|°C = |js})}
+ {(
+ celsius == ""
+ ? {js|?°F|js}
+ : (
+ switch (celsius |> float_of_string |> convert) {
+ | exception _ => "error"
+ | fahrenheit =>
+ Js.Float.toFixedWithPrecision(fahrenheit, ~digits=2)
+ ++ {js|°F|js}
+ }
+ )
+ )
+ |> React.string}
+</div>;
<div>
+ <input /* ... */ />
+ {React.string({js|°C = |js})}
+ {(
+ celsius == ""
+ ? {js|?°F|js}
+ : (
+ switch (celsius |> float_of_string |> convert) {
+ | exception _ => "error"
+ | fahrenheit =>
+ Js.Float.toFixedWithPrecision(fahrenheit, ~digits=2)
+ ++ {js|°F|js}
+ }
+ )
+ )
+ |> React.string}
+</div>;
Js.Float.toFixedWithPrecision
is a function that has one positional argument and one labeled argument. In this case, the labeled argument is named digits
and it’s receiving a value of 2
. It’s not possible to pass in the value of a labeled argument without using the ~label=value
syntax. We’ll see more of labeled arguments in the following chapters when we introduce props.
You might have noticed that the function chain feeding the switch expression got a bit shorter, from
celsius |> float_of_string |> convert |> string_of_float
celsius |> float_of_string |> convert |> string_of_float
to
celsius |> float_of_string |> convert
celsius |> float_of_string |> convert
This happened because string_of_float
, which takes a single argument, was replaced by Js.Float.toFixedWithPrecision
, which takes two arguments, and functions chained using |>
can only take a single argument. But this one-argument restriction actually doesn’t prevent us from putting Js.Float.toFixedWithPrecision
in the chain! We can take advantage of OCaml’s partial application feature to create a one-argument function by writing Js.Float.toFixedWithPrecision(~digits=2)
. Then our switch expression becomes
<div>
+ <input /* ... */ />
+ {React.string({js|°C = |js})}
+ {(
+ celsius == ""
+ ? {js|?°F|js}
+ : (
+ switch (
+ celsius
+ |> float_of_string
+ |> convert
+ |> Js.Float.toFixedWithPrecision(~digits=2)
+ ) {
+ | exception _ => "error"
+ | fahrenheit => fahrenheit ++ {js|°F|js}
+ }
+ )
+ )
+ |> React.string}
+</div>;
<div>
+ <input /* ... */ />
+ {React.string({js|°C = |js})}
+ {(
+ celsius == ""
+ ? {js|?°F|js}
+ : (
+ switch (
+ celsius
+ |> float_of_string
+ |> convert
+ |> Js.Float.toFixedWithPrecision(~digits=2)
+ ) {
+ | exception _ => "error"
+ | fahrenheit => fahrenheit ++ {js|°F|js}
+ }
+ )
+ )
+ |> React.string}
+</div>;
We have a working component now, but catching exceptions isn’t The OCaml Way! In the next chapter, you’ll see how to rewrite the logic using option
.
1. Try changing {js|°C = |js}
to "°C = "
. What happens?
2. It’s possible to rewrite the onChange
callback so that it contains a single expression:
<input
+ value=celsius
+ onChange={evt => setCelsius(_ => getValueFromEvent(evt))}
+/>;
<input
+ value=celsius
+ onChange={evt => setCelsius(_ => getValueFromEvent(evt))}
+/>;
This compiles, but it now contains a hidden bug. Do you know what silent error might occur?
3. It’s possible to use partial application with most functions in OCaml, even operators. Take a look at the following program:
let addFive = (+)(5);
+Js.log(addFive(2));
+Js.log(addFive(7));
+Js.log(addFive(10));
let addFive = (+)(5);
+Js.log(addFive(2));
+Js.log(addFive(7));
+Js.log(addFive(10));
What do you think it outputs? Run it in Melange Playground to confirm your hypothesis.
4. Use the pipe last operator (|>
) and partial application to write a function that takes an integer argument x
, subtracts x
from 10, and converts that result to binary. Hint: Use the Js.Int.toStringWithRadix function.
Js.t
objects (with the type Js.t({..})
) can have fields of any name and type. Js.t
object using the ##
operator.++
operator.1. Changing it to "°C = "
will result in a bit of gibberish being rendered: “°C”. We can’t rely on OCaml strings to deal with Unicode correctly, so any string that contains non-ASCII text must be delimited using {js||js}
.
TIP
Note that quoted string literals using the js
identifier are specific to Melange and are not available in native OCaml.
2. Rewriting onChange
the handler to use a single expression creates a potential problem with stale values coming from the event object:
<input
+ value=celsius
+ onChange={evt =>
+ setCelsius(_ => {
+ // we are no longer in the scope of onChange
+ getValueFromEvent(evt)
+ })
+ }
+/>;
<input
+ value=celsius
+ onChange={evt =>
+ setCelsius(_ => {
+ // we are no longer in the scope of onChange
+ getValueFromEvent(evt)
+ })
+ }
+/>;
Inside of onChange
, we can expect the function getValueFromEvent(evt)
to return the latest value of the input
. However, we are now calling getValueFromEvent(evt)
from a different function—the callback we pass to setCelsius
! By the time that setCelsius
’s callback is invoked, the evt
object might have been recycled and no longer have the same value as when onChange
was initially invoked. For more details about this, see Using Event Values with useState in the ReasonReact docs.
3. Playground: Define an addFive function using partial application
4. Playground: Define a function that subtracts from 10 and converts to binary
Source code for this chapter can be found in the Melange for React Developers repo.
See Using Js.t objects for more details. ↩︎
After all the changes we made in the last chapter, your CelsiusConverter.re
might look something like this:
let getValueFromEvent = (evt): string => ReactEvent.Form.target(evt)##value;
+
+let convert = celsius => 9.0 /. 5.0 *. celsius +. 32.0;
+
+[@react.component]
+let make = () => {
+ let (celsius, setCelsius) = React.useState(() => "");
+
+ <div>
+ <input
+ value=celsius
+ onChange={evt => {
+ let newCelsius = getValueFromEvent(evt);
+ setCelsius(_ => newCelsius);
+ }}
+ />
+ {React.string({js|°C = |js})}
+ {(
+ celsius == ""
+ ? {js|?°F|js}
+ : (
+ switch (
+ celsius
+ |> float_of_string
+ |> convert
+ |> Js.Float.toFixedWithPrecision(~digits=2)
+ ) {
+ | exception _ => "error"
+ | fahrenheit => fahrenheit ++ {js|°F|js}
+ }
+ )
+ )
+ |> React.string}
+ </div>;
+};
let getValueFromEvent = (evt): string => ReactEvent.Form.target(evt)##value;
+
+let convert = celsius => 9.0 /. 5.0 *. celsius +. 32.0;
+
+[@react.component]
+let make = () => {
+ let (celsius, setCelsius) = React.useState(() => "");
+
+ <div>
+ <input
+ value=celsius
+ onChange={evt => {
+ let newCelsius = getValueFromEvent(evt);
+ setCelsius(_ => newCelsius);
+ }}
+ />
+ {React.string({js|°C = |js})}
+ {(
+ celsius == ""
+ ? {js|?°F|js}
+ : (
+ switch (
+ celsius
+ |> float_of_string
+ |> convert
+ |> Js.Float.toFixedWithPrecision(~digits=2)
+ ) {
+ | exception _ => "error"
+ | fahrenheit => fahrenheit ++ {js|°F|js}
+ }
+ )
+ )
+ |> React.string}
+ </div>;
+};
What happens if you forget the | exception _
branch of your switch expression? Your program will crash when invalid input is entered. The compiler won’t warn you to add an exception branch because it doesn’t keep track of which functions throw exceptions. Next, we’ll show you a better way which completely avoids functions that can fail in unexpected ways.
Refactor the switch expression to use float_of_string_opt
instead. This function has the type signature string => option(float)
. It takes a string
argument and returns Some(number)
if it succeeds and None
if it fails—meaning that even if this function fails, no exception is raised.
(
+ switch (celsius |> float_of_string_opt) {
+ | None => "error"
+ | Some(fahrenheit) =>
+ (fahrenheit |> convert |> Js.Float.toFixedWithPrecision(~digits=2))
+ ++ {js|°F|js}
+ }
+)
(
+ switch (celsius |> float_of_string_opt) {
+ | None => "error"
+ | Some(fahrenheit) =>
+ (fahrenheit |> convert |> Js.Float.toFixedWithPrecision(~digits=2))
+ ++ {js|°F|js}
+ }
+)
In terms of functionality, this does exactly what the previous version did. But a critical difference is that if you comment out the | None
branch, the compiler will refuse to accept it:
File "CelsiusConverter.re", lines 21-32, characters 11-10:
+21 | ...........(
+22 | switch (celsius |> float_of_string_opt) {
+23 | // | None => "error"
+24 | | Some(fahrenheit) =>
+25 | (
+...
+29 | )
+30 | ++ {js| °F|js}
+31 | }
+32 | )
+Error (warning 8 [partial-match]): this pattern-matching is not exhaustive.
+Here is an example of a case that is not matched:
+None
File "CelsiusConverter.re", lines 21-32, characters 11-10:
+21 | ...........(
+22 | switch (celsius |> float_of_string_opt) {
+23 | // | None => "error"
+24 | | Some(fahrenheit) =>
+25 | (
+...
+29 | )
+30 | ++ {js| °F|js}
+31 | }
+32 | )
+Error (warning 8 [partial-match]): this pattern-matching is not exhaustive.
+Here is an example of a case that is not matched:
+None
You would get a similar error if you left off the | Some(_)
branch. Having an option
value be the input for a switch expression means that you can’t forget to handle the failure case, much less the success case. There’s another advantage: The | Some(fahrenheit)
branch gives you access to the float
that was successfully converted from the string
, and only this branch has access to that value. So you can be reasonably sure that the success case is handled here and not somewhere else. You are starting to experience the power of pattern matching in OCaml.
It’s a shame we had to give up the long chain of function calls from when we were still using float_of_string
:
celsius
+|> float_of_string
+|> convert
+|> Js.Float.toFixedWithPrecision(~digits=2)
celsius
+|> float_of_string
+|> convert
+|> Js.Float.toFixedWithPrecision(~digits=2)
Actually, we can still use a very similar chain of functions with float_of_string_opt
if we make a couple of small additions:
celsius
+|> float_of_string_opt
+|> Option.map(convert)
+|> Option.map(Js.Float.toFixedWithPrecision(~digits=2))
celsius
+|> float_of_string_opt
+|> Option.map(convert)
+|> Option.map(Js.Float.toFixedWithPrecision(~digits=2))
Option.map
takes a function and an option
value, and only invokes the function if the option
was Some(_)
. Hovering over it, you can see that its type signature is:
('a => 'b, option('a)) => option('b)
('a => 'b, option('a)) => option('b)
Breaking the type signature down:
'a => 'b
, is function which accepts a value of type 'a
(placeholder for any type) and returns a value of type 'b
(also a placeholder for any type, though it may be a different type than 'a
).option('a)
, is an option
that wraps around a value of type 'a
.Option.map
is option('b)
, which is an option
that wraps around a value of type 'b
.The implementation of Option.map
is fairly straightforward, consisting of a single switch expression:
let map = (func, option) =>
+ switch (option) {
+ | None => None
+ | Some(v) => Some(func(v))
+ };
let map = (func, option) =>
+ switch (option) {
+ | None => None
+ | Some(v) => Some(func(v))
+ };
You may be interested in browsing the many other helper functions related to option
in the standard library’s Option module.
At this point, your switch expression might look like this:
(
+ switch (
+ celsius
+ |> float_of_string_opt
+ |> Option.map(convert)
+ |> Option.map(Js.Float.toFixedWithPrecision(~digits=2))
+ ) {
+ | None => "error"
+ | Some(fahrenheit) => fahrenheit ++ {js|°F|js}
+ }
+)
(
+ switch (
+ celsius
+ |> float_of_string_opt
+ |> Option.map(convert)
+ |> Option.map(Js.Float.toFixedWithPrecision(~digits=2))
+ ) {
+ | None => "error"
+ | Some(fahrenheit) => fahrenheit ++ {js|°F|js}
+ }
+)
What if we wanted to render a message of complaint when the temperature goes above 212° F (the boiling point of water) and not even bother to render the converted number? It could look like this:
(
+ switch (celsius |> float_of_string_opt |> Option.map(convert)) {
+ | None => "error"
+ | Some(fahrenheit) =>
+ fahrenheit > 212.0
+ ? {js|Unreasonably hot🥵|js}
+ : Js.Float.toFixedWithPrecision(fahrenheit, ~digits=2) ++ {js| °F|js}
+ }
+)
(
+ switch (celsius |> float_of_string_opt |> Option.map(convert)) {
+ | None => "error"
+ | Some(fahrenheit) =>
+ fahrenheit > 212.0
+ ? {js|Unreasonably hot🥵|js}
+ : Js.Float.toFixedWithPrecision(fahrenheit, ~digits=2) ++ {js| °F|js}
+ }
+)
This works, but OCaml gives you a construct that allows you to do the float comparison without using a nested ternary expression:
(
+ switch (celsius |> float_of_string_opt |> Option.map(convert)) {
+ | None => "error"
+ | Some(fahrenheit) when fahrenheit > 212.0 => {js|Unreasonably hot🥵|js}
+ | Some(fahrenheit) =>
+ Js.Float.toFixedWithPrecision(fahrenheit, ~digits=2) ++ {js| °F|js}
+ }
+)
(
+ switch (celsius |> float_of_string_opt |> Option.map(convert)) {
+ | None => "error"
+ | Some(fahrenheit) when fahrenheit > 212.0 => {js|Unreasonably hot🥵|js}
+ | Some(fahrenheit) =>
+ Js.Float.toFixedWithPrecision(fahrenheit, ~digits=2) ++ {js| °F|js}
+ }
+)
The when guard allows you to add extra conditions to a switch expression branch, keeping nesting of conditionals to a minimum and making your code more readable.
Hooray! Our Celsius converter is finally complete. Later, we’ll see how to create a component that can convert back and forth between Celsius and Fahrenheit. But first, we’ll explore Dune, the build system used by Melange.
1. If you enter a space in the input, it’ll unintuitively produce a Fahrenheit value of 32.00 degrees (because float_of_string_opt(" ") == Some(0.)
). Handle this case correctly by showing “? °F” instead. Hint: Use the String.trim
function.
2. Add another branch with a when
guard that renders “Unreasonably cold🥶” if the temperature is less than -128.6°F (the lowest temperature ever recorded on Earth).
3. Use Js.Float.fromString
instead of float_of_string_opt
to parse a string to float. Hint: Use Js.Float.isNaN
.
option
over those that throw exceptions. option
, the compiler can helpfully remind you to handle the error case.Option.map
is very useful when chaining functions that return option
.when
guard to make your switch expression more expressive without nesting conditionals.1. To prevent a space from producing 32.00 degrees Fahrenheit, just add a call to String.trim
in your render logic:
(
+ String.trim(celsius) == ""
+ ? {js|?°F|js}
+ : (
+ switch (celsius |> float_of_string_opt |> Option.map(convert)) {
+ | None => "error"
+ | Some(fahrenheit) when fahrenheit > 212.0 => {js|Unreasonably hot🥵|js}
+ | Some(fahrenheit) =>
+ Js.Float.toFixedWithPrecision(fahrenheit, ~digits=2) ++ {js| °F|js}
+ }
+ )
+)
(
+ String.trim(celsius) == ""
+ ? {js|?°F|js}
+ : (
+ switch (celsius |> float_of_string_opt |> Option.map(convert)) {
+ | None => "error"
+ | Some(fahrenheit) when fahrenheit > 212.0 => {js|Unreasonably hot🥵|js}
+ | Some(fahrenheit) =>
+ Js.Float.toFixedWithPrecision(fahrenheit, ~digits=2) ++ {js| °F|js}
+ }
+ )
+)
2. To render “Unreasonably cold” when the temperature is less than -128.6°F, you can add another Some(fahrenheit)
branch with a when
guard:
(
+ switch (celsius |> float_of_string_opt |> Option.map(convert)) {
+ | None => "error"
+ | Some(fahrenheit) when fahrenheit < (-128.6) => {js|Unreasonably cold🥶|js}
+ | Some(fahrenheit) when fahrenheit > 212.0 => {js|Unreasonably hot🥵|js}
+ | Some(fahrenheit) =>
+ Js.Float.toFixedWithPrecision(fahrenheit, ~digits=2) ++ {js|°F|js}
+ }
+)
(
+ switch (celsius |> float_of_string_opt |> Option.map(convert)) {
+ | None => "error"
+ | Some(fahrenheit) when fahrenheit < (-128.6) => {js|Unreasonably cold🥶|js}
+ | Some(fahrenheit) when fahrenheit > 212.0 => {js|Unreasonably hot🥵|js}
+ | Some(fahrenheit) =>
+ Js.Float.toFixedWithPrecision(fahrenheit, ~digits=2) ++ {js|°F|js}
+ }
+)
3. To use Js.Float.fromString
instead of float_of_string_opt
, you can define a new helper function that takes a string
and returns option
:
let floatFromString = text => {
+ let value = Js.Float.fromString(text);
+ Js.Float.isNaN(value) ? None : Some(value);
+};
let floatFromString = text => {
+ let value = Js.Float.fromString(text);
+ Js.Float.isNaN(value) ? None : Some(value);
+};
Then substitute float_of_string_opt
with floatFromString
in your switch expression:
(
+ switch (celsius |> floatFromString |> Option.map(convert)) {
+ | None => "error"
+ | Some(fahrenheit) when fahrenheit < (-128.6) => {js|Unreasonably cold🥶|js}
+ | Some(fahrenheit) when fahrenheit > 212.0 => {js|Unreasonably hot🥵|js}
+ | Some(fahrenheit) =>
+ Js.Float.toFixedWithPrecision(fahrenheit, ~digits=2) ++ {js|°F|js}
+ }
+)
(
+ switch (celsius |> floatFromString |> Option.map(convert)) {
+ | None => "error"
+ | Some(fahrenheit) when fahrenheit < (-128.6) => {js|Unreasonably cold🥶|js}
+ | Some(fahrenheit) when fahrenheit > 212.0 => {js|Unreasonably hot🥵|js}
+ | Some(fahrenheit) =>
+ Js.Float.toFixedWithPrecision(fahrenheit, ~digits=2) ++ {js|°F|js}
+ }
+)
Source code for this chapter can be found in the Melange for React Developers repo.
`,48),e=[p];function t(c,r,y,E,i,F){return n(),a("div",null,e)}const h=s(o,[["render",t]]);export{D as __pageData,h as default}; diff --git a/assets/celsius-converter-option_index.md.df50f66c.lean.js b/assets/celsius-converter-option_index.md.df50f66c.lean.js new file mode 100644 index 00000000..e085f3af --- /dev/null +++ b/assets/celsius-converter-option_index.md.df50f66c.lean.js @@ -0,0 +1 @@ +import{_ as s,o as n,c as a,Q as l}from"./chunks/framework.0e8ae64e.js";const D=JSON.parse('{"title":"Celsius Converter using Option","description":"","frontmatter":{},"headers":[],"relativePath":"celsius-converter-option/index.md","filePath":"celsius-converter-option/index.md"}'),o={name:"celsius-converter-option/index.md"},p=l("",48),e=[p];function t(c,r,y,E,i,F){return n(),a("div",null,e)}const h=s(o,[["render",t]]);export{D as __pageData,h as default}; diff --git a/assets/chunks/@localSearchIndexroot.ae053162.js b/assets/chunks/@localSearchIndexroot.ae053162.js new file mode 100644 index 00000000..eb9b8f24 --- /dev/null +++ b/assets/chunks/@localSearchIndexroot.ae053162.js @@ -0,0 +1 @@ +const e='{"documentCount":78,"nextId":78,"documentIds":{"0":"/README.html#melange-for-react-developers-book","1":"/README.html#installation","2":"/README.html#commands","3":"/celsius-converter-exception/#celsius-converter","4":"/celsius-converter-exception/#wrap-functions-that-return-js-t","5":"/celsius-converter-exception/#apply-pipe-last","6":"/celsius-converter-exception/#string-concatenation","7":"/celsius-converter-exception/#catch-exception-with-switch","8":"/celsius-converter-exception/#ternary-expression","9":"/celsius-converter-exception/#if-else-expression","10":"/celsius-converter-exception/#labeled-argument","11":"/celsius-converter-exception/#partial-application","12":"/celsius-converter-exception/#exercises","13":"/celsius-converter-exception/#overview","14":"/celsius-converter-exception/#solutions","15":"/celsius-converter-option/#celsius-converter-using-option","16":"/celsius-converter-option/#float-of-string-opt","17":"/celsius-converter-option/#option-map","18":"/celsius-converter-option/#when-guard","19":"/celsius-converter-option/#exercises","20":"/celsius-converter-option/#overview","21":"/celsius-converter-option/#solutions","22":"/counter/#counter","23":"/counter/#components-are-modules","24":"/counter/#the-make-function","25":"/counter/#wrap-strings-with-react-string","26":"/counter/#using-the-app-component","27":"/counter/#the-counter-component","28":"/counter/#the-pipe-last-operator","29":"/counter/#basic-styling","30":"/counter/#exercises","31":"/counter/#overview","32":"/counter/#solutions","33":"/installation/#installation","34":"/installation/#prerequisites","35":"/installation/#opam","36":"/installation/#download-and-run-the-starter-project","37":"/installation/#visual-studio-code-extension","38":"/intro-to-dune/#introduction-to-dune","39":"/intro-to-dune/#dune-project-file","40":"/intro-to-dune/#dune-files","41":"/intro-to-dune/#melange-emit-stanza","42":"/intro-to-dune/#counter-app-directory","43":"/intro-to-dune/#why-makefile","44":"/intro-to-dune/#anatomy-of-makefile","45":"/intro-to-dune/#root-directory-makefile","46":"/intro-to-dune/#cleanup","47":"/intro-to-dune/#rationale-for-monorepo-structure","48":"/intro-to-dune/#exercises","49":"/intro-to-dune/#overview","50":"/intro-to-dune/#solutions","51":"/intro/#melange-for-react-developers","52":"/intro/#motivation","53":"/intro/#audience","54":"/intro/#chapters-and-topics","55":"/numeric-types/#numeric-types","56":"/numeric-types/#melange-playground","57":"/numeric-types/#sharing-code-snippets","58":"/numeric-types/#comparison-operators","59":"/numeric-types/#arithmetic-operators","60":"/numeric-types/#they-re-just-number","61":"/numeric-types/#widgets-in-the-playground","62":"/numeric-types/#exercises","63":"/numeric-types/#overview","64":"/numeric-types/#solutions","65":"/order-confirmation/#order-confirmation","66":"/order-confirmation/#variant-type-item-t","67":"/order-confirmation/#wildcard-in-switch-expressions","68":"/order-confirmation/#a-fun-syntax-for-switch","69":"/order-confirmation/#item-make","70":"/order-confirmation/#order-component","71":"/order-confirmation/#react-array","72":"/order-confirmation/#index-re","73":"/order-confirmation/#js-array-mapi","74":"/order-confirmation/#exercises","75":"/order-confirmation/#overview","76":"/order-confirmation/#solutions","77":"/todo.html#content-not-yet-unavailable"},"fieldIds":{"title":0,"titles":1,"text":2},"fieldLength":{"0":[5,1,1],"1":[1,5,4],"2":[1,5,11],"3":[2,1,121],"4":[6,2,106],"5":[6,2,52],"6":[4,2,44],"7":[4,2,73],"8":[2,2,66],"9":[3,2,98],"10":[2,2,89],"11":[2,2,110],"12":[1,2,106],"13":[1,2,77],"14":[1,2,147],"15":[4,1,112],"16":[4,4,151],"17":[2,4,126],"18":[2,4,118],"19":[1,4,71],"20":[1,4,43],"21":[1,4,106],"22":[1,1,119],"23":[3,1,49],"24":[3,1,55],"25":[5,1,35],"26":[4,1,77],"27":[3,1,84],"28":[4,1,55],"29":[2,1,90],"30":[1,1,45],"31":[1,1,56],"32":[1,1,146],"33":[1,1,1],"34":[1,1,43],"35":[1,1,70],"36":[6,1,79],"37":[4,1,61],"38":[3,1,68],"39":[3,3,126],"40":[2,3,170],"41":[3,3,112],"42":[3,3,167],"43":[2,3,43],"44":[3,3,89],"45":[3,3,101],"46":[1,3,35],"47":[4,3,94],"48":[1,3,59],"49":[1,3,68],"50":[1,3,150],"51":[4,1,8],"52":[1,4,41],"53":[1,4,66],"54":[3,4,95],"55":[2,1,54],"56":[2,2,119],"57":[3,2,52],"58":[2,2,97],"59":[2,2,69],"60":[4,2,41],"61":[4,2,141],"62":[1,2,72],"63":[1,2,74],"64":[1,2,102],"65":[2,1,81],"66":[4,2,117],"67":[4,2,86],"68":[5,2,59],"69":[2,2,63],"70":[2,2,97],"71":[2,2,148],"72":[2,2,57],"73":[3,2,124],"74":[1,2,70],"75":[1,2,96],"76":[1,2,218],"77":[4,1,19]},"averageFieldLength":[2.4487179487179485,2.1538461538461537,82.62820512820512],"storedFields":{"0":{"title":"Melange for React Developers Book","titles":[]},"1":{"title":"Installation","titles":["Melange for React Developers Book"]},"2":{"title":"Commands","titles":["Melange for React Developers Book"]},"3":{"title":"Celsius Converter","titles":[]},"4":{"title":"Wrap functions that return Js.t","titles":["Celsius Converter"]},"5":{"title":"Apply pipe last (|>)","titles":["Celsius Converter"]},"6":{"title":"String concatenation (++)","titles":["Celsius Converter"]},"7":{"title":"Catch exception with switch","titles":["Celsius Converter"]},"8":{"title":"Ternary expression","titles":["Celsius Converter"]},"9":{"title":"If-else expression","titles":["Celsius Converter"]},"10":{"title":"Labeled argument","titles":["Celsius Converter"]},"11":{"title":"Partial application","titles":["Celsius Converter"]},"12":{"title":"Exercises","titles":["Celsius Converter"]},"13":{"title":"Overview","titles":["Celsius Converter"]},"14":{"title":"Solutions","titles":["Celsius Converter"]},"15":{"title":"Celsius Converter using Option","titles":[]},"16":{"title":"float_of_string_opt","titles":["Celsius Converter using Option"]},"17":{"title":"Option.map","titles":["Celsius Converter using Option"]},"18":{"title":"when guard","titles":["Celsius Converter using Option"]},"19":{"title":"Exercises","titles":["Celsius Converter using Option"]},"20":{"title":"Overview","titles":["Celsius Converter using Option"]},"21":{"title":"Solutions","titles":["Celsius Converter using Option"]},"22":{"title":"Counter","titles":[]},"23":{"title":"Components are modules","titles":["Counter"]},"24":{"title":"The make function","titles":["Counter"]},"25":{"title":"Wrap strings with React.string","titles":["Counter"]},"26":{"title":"Using the App component","titles":["Counter"]},"27":{"title":"The Counter component","titles":["Counter"]},"28":{"title":"The pipe last operator","titles":["Counter"]},"29":{"title":"Basic styling","titles":["Counter"]},"30":{"title":"Exercises","titles":["Counter"]},"31":{"title":"Overview","titles":["Counter"]},"32":{"title":"Solutions","titles":["Counter"]},"33":{"title":"Installation","titles":[]},"34":{"title":"Prerequisites","titles":["Installation"]},"35":{"title":"Opam","titles":["Installation"]},"36":{"title":"Download and run the starter project","titles":["Installation"]},"37":{"title":"Visual Studio Code Extension","titles":["Installation"]},"38":{"title":"Introduction to Dune","titles":[]},"39":{"title":"dune-project file","titles":["Introduction to Dune"]},"40":{"title":"dune files","titles":["Introduction to Dune"]},"41":{"title":"melange.emit stanza","titles":["Introduction to Dune"]},"42":{"title":"Counter app directory","titles":["Introduction to Dune"]},"43":{"title":"Why Makefile","titles":["Introduction to Dune"]},"44":{"title":"Anatomy of Makefile","titles":["Introduction to Dune"]},"45":{"title":"Root directory Makefile","titles":["Introduction to Dune"]},"46":{"title":"Cleanup","titles":["Introduction to Dune"]},"47":{"title":"Rationale for monorepo structure","titles":["Introduction to Dune"]},"48":{"title":"Exercises","titles":["Introduction to Dune"]},"49":{"title":"Overview","titles":["Introduction to Dune"]},"50":{"title":"Solutions","titles":["Introduction to Dune"]},"51":{"title":"Melange for React Developers","titles":[]},"52":{"title":"Motivation","titles":["Melange for React Developers"]},"53":{"title":"Audience","titles":["Melange for React Developers"]},"54":{"title":"Chapters and topics","titles":["Melange for React Developers"]},"55":{"title":"Numeric Types","titles":[]},"56":{"title":"Melange Playground","titles":["Numeric Types"]},"57":{"title":"Sharing code snippets","titles":["Numeric Types"]},"58":{"title":"Comparison operators","titles":["Numeric Types"]},"59":{"title":"Arithmetic operators","titles":["Numeric Types"]},"60":{"title":"They’re just Number","titles":["Numeric Types"]},"61":{"title":"Widgets in the playground","titles":["Numeric Types"]},"62":{"title":"Exercises","titles":["Numeric Types"]},"63":{"title":"Overview","titles":["Numeric Types"]},"64":{"title":"Solutions","titles":["Numeric Types"]},"65":{"title":"Order Confirmation","titles":[]},"66":{"title":"Variant type Item.t","titles":["Order Confirmation"]},"67":{"title":"Wildcard in switch expressions","titles":["Order Confirmation"]},"68":{"title":"A fun syntax for switch","titles":["Order Confirmation"]},"69":{"title":"Item.make","titles":["Order Confirmation"]},"70":{"title":"Order component","titles":["Order Confirmation"]},"71":{"title":"React.array","titles":["Order Confirmation"]},"72":{"title":"Index.re","titles":["Order Confirmation"]},"73":{"title":"Js.Array.mapi","titles":["Order Confirmation"]},"74":{"title":"Exercises","titles":["Order Confirmation"]},"75":{"title":"Overview","titles":["Order Confirmation"]},"76":{"title":"Solutions","titles":["Order Confirmation"]},"77":{"title":"Content not yet unavailable","titles":[]}},"dirtCount":0,"index":[["🍲",{"2":{"76":1}}],["🥫",{"2":{"76":1}}],["^^^^",{"2":{"50":2}}],["^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",{"2":{"71":2}}],["^^^^^^^^^^^",{"2":{"32":2}}],["\\tnpx",{"2":{"45":2}}],["\\tapp=counter",{"2":{"42":2,"44":2}}],["└─",{"2":{"42":2,"65":2}}],["├─",{"2":{"42":6,"65":6}}],["`preprocess`",{"2":{"40":2,"42":2}}],["`",{"2":{"40":6,"42":6}}],["`target`",{"2":{"40":2,"42":2}}],["`melange",{"2":{"40":4,"42":4}}],["`dune`",{"2":{"40":2}}],["`dirs`",{"2":{"40":2}}],["$",{"2":{"35":2,"45":8}}],["66",{"2":{"58":2,"59":6}}],["6",{"2":{"21":4,"50":2,"58":2,"59":6,"71":2}}],["6°f",{"2":{"19":1,"21":1}}],["8080",{"2":{"22":1,"36":1,"45":1}}],["8",{"2":{"16":2,"32":2,"39":2}}],["keys",{"2":{"76":3}}],["keystrokes",{"2":{"61":2}}],["key=",{"2":{"73":2,"76":2}}],["key",{"2":{"22":1,"50":1,"73":4}}],["keeping",{"2":{"18":1}}],["keep",{"2":{"15":1,"39":1}}],["knowledge",{"2":{"53":1}}],["known",{"2":{"39":1}}],["know",{"2":{"12":1,"23":1,"27":1,"34":1,"50":1,"53":2}}],["knows",{"2":{"3":1}}],["↩︎",{"2":{"14":1,"32":1,"50":2,"54":1,"76":5}}],["â°c",{"2":{"14":1}}],["x",{"2":{"12":2,"71":4}}],["44",{"2":{"58":2}}],["42l",{"2":{"64":2}}],["42",{"2":{"50":3,"55":8,"56":19,"58":4,"59":4,"60":4,"62":2}}],["4",{"2":{"12":1,"14":1,"58":2,"73":1}}],["7",{"2":{"12":2}}],["33",{"2":{"58":2}}],["3d",{"2":{"57":2}}],["39",{"2":{"17":8,"26":2,"40":4,"42":4,"61":2,"72":2,"73":8}}],["31",{"2":{"16":2}}],["30",{"2":{"16":2}}],["3",{"2":{"12":1,"14":1,"19":1,"21":1,"30":1,"32":5,"39":2,"48":1,"50":1,"58":2,"62":1,"64":1,"71":1,"74":1,"76":1}}],["32",{"2":{"3":2,"4":2,"15":2,"16":4,"19":1,"21":1}}],["°c",{"2":{"12":1,"14":1}}],["°f",{"2":{"6":1,"8":1,"19":1}}],["°f|js",{"2":{"3":2,"4":2,"8":2,"9":2,"10":2,"11":2,"15":2,"16":2,"18":4,"21":4}}],["~items",{"2":{"70":3,"76":2}}],["~item",{"2":{"69":3,"76":2}}],["~textalign=",{"2":{"61":2}}],["~minwidth=",{"2":{"61":2}}],["~borderradius=",{"2":{"61":2}}],["~border=",{"2":{"61":2}}],["~alignitems=",{"2":{"61":2}}],["~fontsize=",{"2":{"61":4}}],["~",{"2":{"35":2}}],["~gridgap=",{"2":{"29":2,"61":4,"64":2}}],["~display=",{"2":{"29":2,"61":4,"64":2}}],["~digits=2",{"2":{"10":2,"11":3,"15":2,"16":2,"17":6,"18":4,"21":6,"69":2,"70":2,"71":2,"74":1,"76":2}}],["~padding=",{"2":{"29":2,"61":6,"64":2}}],["~label=value",{"2":{"10":1}}],["2em",{"2":{"61":4}}],["27",{"2":{"32":4}}],["29",{"2":{"16":2}}],["25",{"2":{"16":2}}],["24",{"2":{"16":2}}],["23",{"2":{"16":2}}],["22",{"2":{"16":2}}],["2",{"2":{"10":1,"12":3,"14":1,"19":1,"21":1,"30":1,"32":1,"48":1,"50":4,"56":2,"62":1,"64":1,"66":1,"74":1,"76":1}}],["212",{"2":{"18":4,"21":6}}],["212°",{"2":{"18":1}}],["21",{"2":{"10":1,"16":4}}],["query",{"2":{"57":1}}],["queryselector",{"2":{"26":3,"42":2,"61":2,"72":2}}],["quite",{"2":{"9":1,"52":1}}],["quoted",{"2":{"14":1}}],["quot",{"2":{"3":4,"4":4,"7":8,"8":8,"9":8,"10":8,"11":8,"12":2,"14":2,"15":12,"16":12,"17":4,"18":8,"19":2,"21":16,"22":4,"26":10,"27":8,"29":20,"32":4,"35":4,"37":16,"42":8,"50":6,"60":8,"61":80,"64":20,"70":4,"71":20,"72":12,"73":8,"76":8}}],["y",{"2":{"35":1}}],["yet",{"0":{"77":1},"2":{"9":1}}],["yields",{"2":{"9":1}}],["yourself",{"2":{"55":1}}],["yourpackage",{"2":{"46":1}}],["your",{"2":{"12":1,"15":3,"17":1,"18":1,"20":1,"21":2,"22":1,"29":1,"32":3,"35":1,"36":1,"37":1,"38":1,"39":3,"40":1,"42":1,"45":2,"47":1,"49":2,"53":1,"57":2,"58":1,"61":1,"62":1,"63":2,"66":2,"67":1,"68":1,"72":1,"73":1,"74":1,"75":3,"76":2}}],["you",{"2":{"7":1,"8":1,"9":5,"11":2,"12":2,"13":2,"15":3,"16":7,"17":2,"18":3,"19":1,"20":2,"21":2,"22":4,"23":1,"24":1,"25":2,"26":1,"27":1,"29":1,"30":2,"32":6,"34":2,"35":3,"36":2,"37":2,"38":2,"39":4,"40":2,"41":1,"42":3,"43":1,"44":1,"45":2,"46":2,"47":4,"48":3,"49":3,"50":2,"53":3,"55":2,"56":4,"57":1,"58":4,"59":3,"61":1,"62":1,"64":1,"65":3,"66":4,"67":5,"68":1,"71":3,"73":4,"75":5,"76":8,"77":1}}],["row",{"2":{"69":1}}],["root",{"0":{"45":1},"2":{"22":1,"26":9,"29":1,"39":1,"40":1,"42":9,"44":1,"45":2,"46":2,"47":1,"61":8,"72":8}}],["rule",{"2":{"44":2,"45":2,"48":1,"50":1}}],["rules",{"2":{"40":4,"42":4,"49":1,"76":1}}],["rundown",{"2":{"60":1}}],["runtime",{"2":{"30":1}}],["running",{"2":{"22":1,"34":1,"35":1,"36":2,"44":1,"56":1,"71":1}}],["run",{"0":{"36":1},"2":{"12":1,"22":2,"31":1,"35":1,"41":2,"44":1,"45":4,"49":1,"72":1}}],["rationale",{"0":{"47":1}}],["raw",{"2":{"35":2}}],["raised",{"2":{"16":1}}],["right",{"2":{"8":1,"28":1,"30":1,"56":2}}],["requested",{"2":{"77":1}}],["requires",{"2":{"70":1}}],["reitems",{"2":{"73":1}}],["retype",{"2":{"66":1,"70":1,"76":2}}],["returns",{"2":{"16":1,"17":1,"21":1,"22":1,"24":1,"26":3,"71":1}}],["return",{"0":{"4":1},"2":{"4":2,"9":1,"13":1,"14":1,"17":1,"20":2,"66":1}}],["renaming",{"2":{"32":1}}],["rename",{"2":{"30":1,"74":1}}],["rendering",{"2":{"76":1}}],["rendered",{"2":{"14":1,"74":1}}],["renders",{"2":{"8":2,"19":1,"22":1,"42":1,"69":1}}],["render",{"2":{"5":1,"18":2,"21":2,"26":3,"42":3,"61":4,"63":1,"69":2,"70":1,"72":3}}],["reduce",{"2":{"24":1,"70":5,"71":2,"75":2,"76":2}}],["remember",{"2":{"32":1,"65":1}}],["remedy",{"2":{"7":1}}],["removing",{"2":{"32":1,"75":1}}],["removed",{"2":{"66":1}}],["remove",{"2":{"30":1}}],["remodule",{"2":{"22":1,"27":1,"42":1,"61":2,"72":1}}],["remind",{"2":{"20":1}}],["referred",{"2":{"66":1}}],["refer",{"2":{"60":1}}],["references",{"2":{"76":1}}],["referenced",{"2":{"41":1,"76":1}}],["reference",{"2":{"39":1,"40":1}}],["refers",{"2":{"3":2}}],["refuse",{"2":{"16":1}}],["refactor",{"2":{"16":1,"71":1}}],["represented",{"2":{"64":1}}],["represent",{"2":{"64":1}}],["representation",{"2":{"62":1}}],["replacing",{"2":{"50":1,"54":1}}],["replace",{"2":{"48":1}}],["replaced",{"2":{"11":1}}],["repeatedly",{"2":{"74":1}}],["repeat",{"2":{"48":1}}],["repo",{"2":{"14":1,"21":1,"32":1,"47":1,"50":1,"64":1,"76":1}}],["recall",{"2":{"76":1}}],["recommends",{"2":{"47":1}}],["recorded",{"2":{"19":1}}],["recipe",{"2":{"44":1,"45":1,"48":2,"50":1}}],["recent",{"2":{"34":1}}],["receiving",{"2":{"10":1}}],["recycled",{"2":{"14":1}}],["rewriting",{"2":{"14":1}}],["rewrite",{"2":{"9":1,"11":1,"12":1,"67":1,"68":1}}],["resrc",{"2":{"65":1}}],["restaurant",{"2":{"65":1,"76":1}}],["restauranteur",{"2":{"65":1}}],["rest",{"2":{"39":1}}],["restriction",{"2":{"11":1,"58":1}}],["results",{"2":{"32":1}}],["result",{"2":{"12":1,"14":1,"32":1,"50":2,"64":4}}],["reality",{"2":{"71":1}}],["ready",{"2":{"45":1,"69":1}}],["reading",{"2":{"44":1}}],["readthedocs",{"2":{"40":2,"42":2}}],["read",{"2":{"28":1}}],["readable",{"2":{"18":1,"24":1,"62":1}}],["reasonjs",{"2":{"58":3,"59":2}}],["reasonml",{"2":{"40":2}}],["reason",{"2":{"28":1,"37":2,"40":8,"42":4,"48":1,"50":2,"54":1,"71":1,"76":1}}],["reasonably",{"2":{"16":1}}],["reasonreact",{"2":{"14":1,"22":2,"23":1,"29":2,"31":2,"40":2,"53":1,"54":2,"61":1,"63":1,"73":1,"75":1}}],["reasonlet",{"2":{"12":1,"17":1,"55":2,"56":2,"58":1,"60":1,"62":1,"64":1,"67":3}}],["reasoncelsius",{"2":{"11":2,"17":2}}],["reactdomstyle",{"2":{"29":4,"61":4,"64":2}}],["reactdom",{"2":{"26":4,"42":4,"61":4,"72":4}}],["reactevent",{"2":{"3":5,"4":3,"15":2}}],["react",{"0":{"0":1,"25":1,"51":1,"71":1},"1":{"1":1,"2":1,"52":1,"53":1,"54":1},"2":{"3":8,"4":8,"5":5,"6":4,"7":8,"8":4,"9":4,"10":4,"11":4,"14":1,"15":8,"21":1,"22":8,"24":4,"25":4,"26":3,"27":13,"28":4,"29":7,"30":1,"32":3,"36":5,"37":1,"39":2,"40":6,"42":8,"48":1,"50":4,"53":1,"61":18,"64":11,"69":6,"70":8,"71":28,"72":6,"73":2,"74":1,"75":3,"76":16}}],["relevant",{"2":{"66":1,"75":1}}],["relet",{"2":{"3":1,"4":1,"15":1,"21":1,"26":1,"66":1,"68":2,"71":2,"76":1}}],["related",{"2":{"17":1,"43":1}}],["rely",{"2":{"9":1,"14":1}}],["re",{"0":{"60":1,"72":1},"2":{"3":1,"5":1,"6":2,"7":2,"8":1,"9":1,"10":1,"11":1,"12":1,"14":1,"15":1,"16":3,"17":1,"18":2,"21":3,"22":2,"27":2,"29":1,"30":1,"32":4,"36":2,"39":1,"40":2,"41":1,"42":8,"43":1,"44":2,"45":1,"46":1,"47":1,"49":2,"50":3,"57":1,"61":1,"64":2,"65":4,"66":1,"67":1,"69":2,"70":1,"71":2,"72":1,"76":4}}],["violates",{"2":{"76":1}}],["via",{"2":{"63":1,"70":1}}],["visual",{"0":{"37":1},"2":{"34":1,"37":2}}],["v2",{"2":{"40":2,"57":1}}],["variants",{"2":{"70":1}}],["variant",{"0":{"66":1},"2":{"66":2,"74":1,"75":2,"76":1}}],["variables",{"2":{"32":1,"37":1,"56":2}}],["variable",{"2":{"30":1,"32":2,"44":1,"45":1,"65":1}}],["var",{"2":{"32":2,"56":4}}],["values",{"2":{"13":1,"14":2,"58":2,"62":1,"63":1,"64":2}}],["value=celsius",{"2":{"3":2,"4":2,"12":2,"14":2,"15":2}}],["value",{"2":{"3":4,"4":4,"5":1,"6":1,"8":1,"9":2,"10":5,"14":2,"15":2,"16":2,"17":5,"19":1,"21":6,"31":1,"32":2,"41":2,"45":1,"50":2,"62":1,"65":1,"70":1,"74":1,"76":4}}],["ve",{"2":{"22":1,"29":1,"38":1,"39":2,"42":1,"59":1,"65":1,"73":1}}],["verify",{"2":{"35":1,"37":1}}],["versionopam",{"2":{"35":1}}],["version",{"2":{"16":1,"34":1,"35":1,"39":4,"49":2,"67":1}}],["very",{"2":{"5":1,"9":1,"17":1,"20":1,"32":1,"47":1,"50":1,"67":1}}],["v",{"2":{"17":4,"27":8,"29":8,"61":16,"64":8}}],["url",{"2":{"57":1,"63":1}}],["uppercase",{"2":{"76":1}}],["updating",{"2":{"66":1}}],["update",{"2":{"5":1,"61":1,"65":1,"74":1}}],["up",{"2":{"17":1,"41":1,"53":1,"65":1,"68":1,"70":1,"76":1}}],["unavailable",{"0":{"77":1}}],["unstyled",{"2":{"76":1}}],["unsigned",{"2":{"64":1}}],["underlying",{"2":{"71":1}}],["underneath",{"2":{"60":1}}],["under",{"2":{"44":1}}],["underscores",{"2":{"62":1}}],["underscore",{"2":{"7":1,"32":1,"67":1}}],["unbound",{"2":{"32":2,"50":2}}],["unless",{"2":{"32":1,"58":1,"75":1}}],["unlike",{"2":{"9":1,"13":1,"25":1,"29":1,"58":1,"73":1}}],["unused",{"2":{"32":4}}],["unreasonably",{"2":{"19":1,"21":1}}],["unexpected",{"2":{"15":1}}],["union",{"2":{"76":1}}],["unique",{"2":{"73":2}}],["unix",{"2":{"34":1}}],["unintuitively",{"2":{"19":1}}],["unicode",{"2":{"14":1}}],["unit",{"2":{"6":1,"24":1}}],["usage",{"2":{"43":1}}],["usable",{"2":{"32":1}}],["us",{"2":{"5":1,"11":1,"41":1}}],["uses",{"2":{"27":1,"28":1,"52":1,"59":1,"64":1}}],["usestate",{"2":{"3":2,"4":2,"14":1,"15":2,"27":3,"61":2,"64":2}}],["useful",{"2":{"20":1,"28":1,"38":1,"75":1}}],["users",{"2":{"34":1}}],["user",{"2":{"6":1,"8":1,"37":1}}],["used",{"2":{"4":1,"9":1,"13":1,"18":1,"32":1,"39":2,"40":2,"42":2,"44":1,"49":1,"74":2}}],["use",{"2":{"3":1,"4":1,"9":2,"12":3,"13":2,"14":1,"16":1,"17":1,"19":3,"20":1,"21":1,"26":1,"32":1,"34":1,"39":5,"40":4,"41":1,"42":2,"45":1,"47":3,"49":1,"54":1,"61":4,"62":2,"68":2,"73":1,"75":1,"76":2}}],["using",{"0":{"15":1,"26":1},"1":{"16":1,"17":1,"18":1,"19":1,"20":1,"21":1},"2":{"3":1,"4":1,"6":1,"7":1,"10":2,"11":2,"13":2,"14":5,"17":1,"18":1,"22":1,"29":1,"39":7,"40":4,"42":3,"43":1,"49":3,"53":1,"54":1,"64":1,"65":1,"68":3,"70":1,"74":1,"75":1,"76":1}}],["|sandwich",{"2":{"72":2}}],["|",{"0":{"5":1},"2":{"5":9,"6":8,"7":25,"8":12,"9":12,"10":10,"11":23,"12":1,"15":13,"16":37,"17":26,"18":18,"21":34,"26":4,"28":4,"29":4,"30":1,"32":3,"42":4,"50":2,"61":12,"64":4,"66":9,"67":10,"68":8,"69":10,"70":10,"71":24,"72":4,"73":4,"74":2,"76":42}}],["|js",{"2":{"3":2,"4":2,"5":2,"6":2,"7":4,"8":2,"9":2,"10":2,"11":2,"12":1,"15":2}}],["etc",{"2":{"76":1}}],["equivalent",{"2":{"68":1,"76":1}}],["empty",{"2":{"65":1}}],["empowered",{"2":{"61":1}}],["emojis",{"2":{"76":1}}],["emoji",{"2":{"65":1,"66":2,"76":1}}],["emit",{"0":{"41":1},"2":{"40":4,"41":3,"42":5,"46":1,"47":3,"48":1,"49":1}}],["emit`",{"2":{"40":4,"42":4}}],["efficient",{"2":{"53":1}}],["effectively",{"2":{"69":1}}],["effect",{"2":{"22":1,"44":1}}],["ecosystems",{"2":{"52":1}}],["ecosystem",{"2":{"52":1}}],["es6",{"2":{"40":4,"42":4}}],["editors",{"2":{"34":1,"50":1}}],["editor",{"2":{"34":1,"37":4,"56":1,"57":1}}],["either",{"2":{"31":1}}],["each",{"2":{"38":1,"39":1,"47":2,"52":1,"70":1,"73":2}}],["easy",{"2":{"31":1}}],["easier",{"2":{"24":1}}],["earth",{"2":{"19":1}}],["e",{"2":{"10":1,"26":1,"64":1,"75":1}}],["elemarray",{"2":{"71":10}}],["elements",{"2":{"71":1,"75":1,"76":1}}],["elementfile",{"2":{"71":1}}],["element",{"2":{"5":1,"22":1,"24":2,"25":2,"26":8,"28":1,"29":1,"42":2,"61":2,"71":12,"72":2,"73":2,"75":1,"76":1}}],["else",{"0":{"9":1},"2":{"9":6,"13":2,"16":1,"40":1,"54":1,"74":1}}],["errors",{"2":{"30":1,"48":1}}],["error",{"2":{"7":4,"8":4,"9":3,"10":2,"11":2,"12":1,"15":2,"16":7,"17":2,"18":4,"20":1,"21":6,"26":3,"32":8,"39":2,"42":2,"50":4,"56":3,"58":1,"63":1,"71":4,"72":2}}],["existing",{"2":{"53":1,"65":1}}],["excellent",{"2":{"57":1}}],["exceptions",{"2":{"11":1,"13":1,"15":1,"20":1}}],["exception",{"0":{"7":1},"2":{"7":9,"8":2,"9":2,"10":2,"11":2,"15":4,"16":1,"54":2}}],["exclude",{"2":{"40":2,"49":1}}],["extensions",{"2":{"37":1}}],["extension",{"0":{"37":1},"2":{"37":2,"39":2}}],["extra",{"2":{"18":1}}],["exhaustive",{"2":{"16":2,"32":2}}],["exactly",{"2":{"16":1,"25":1}}],["examples",{"2":{"59":1}}],["example",{"2":{"3":1,"4":1,"16":2,"23":1,"32":2,"40":1,"41":1,"42":2,"49":1,"55":1,"57":1,"71":1}}],["export",{"2":{"56":2}}],["explicitly",{"2":{"61":1,"67":1}}],["explain",{"2":{"43":1}}],["explore",{"2":{"18":1,"54":1}}],["express",{"2":{"26":1}}],["expressive",{"2":{"20":1}}],["expressions",{"0":{"67":1},"2":{"9":1,"13":4,"32":1,"39":2,"67":1}}],["expression",{"0":{"8":1,"9":1},"2":{"7":1,"8":2,"9":3,"11":2,"12":1,"14":1,"15":1,"16":2,"17":2,"18":2,"20":2,"21":1,"26":1,"30":1,"39":1,"54":2,"56":4,"58":4,"66":1,"68":2,"71":4,"75":2}}],["experience",{"2":{"16":1}}],["expected",{"2":{"56":2,"58":2,"71":3}}],["expects",{"2":{"50":1,"71":1}}],["expect",{"2":{"14":1,"58":1,"63":1,"74":1,"76":1}}],["executables",{"2":{"40":1}}],["executed",{"2":{"44":1}}],["execute",{"2":{"7":1}}],["exercises",{"0":{"12":1,"19":1,"30":1,"48":1,"62":1,"74":1}}],["enums",{"2":{"76":1}}],["enough",{"2":{"64":1}}],["end",{"2":{"41":1,"59":1,"63":1}}],["entire",{"2":{"68":1,"75":1}}],["entry",{"2":{"41":3,"45":4}}],["entered",{"2":{"15":1}}],["enter",{"2":{"7":1,"8":1,"10":1,"19":1}}],["en",{"2":{"40":4,"42":2}}],["enable",{"2":{"37":1,"39":1,"42":2}}],["enables",{"2":{"31":1}}],["environment",{"2":{"44":1,"45":1,"56":1,"65":1}}],["env",{"2":{"35":2}}],["encoding",{"2":{"39":1}}],["encouraged",{"2":{"34":1}}],["enclosed",{"2":{"4":1}}],["ensures",{"2":{"4":1}}],["eval",{"2":{"35":2}}],["every",{"2":{"41":1,"47":1,"48":1,"50":1}}],["everything",{"2":{"36":1,"76":1}}],["ever",{"2":{"19":1}}],["even",{"2":{"12":1,"16":1,"18":1}}],["event",{"2":{"3":1,"14":2}}],["evt",{"2":{"3":5,"4":10,"12":4,"14":7,"15":8,"27":4,"29":4,"30":2,"32":4,"61":8,"64":4}}],["nifty",{"2":{"56":1}}],["nice",{"2":{"9":1}}],["numeric",{"0":{"55":1},"1":{"56":1,"57":1,"58":1,"59":1,"60":1,"61":1,"62":1,"63":1,"64":1},"2":{"54":1,"55":1}}],["numbers",{"2":{"61":1,"64":1}}],["number",{"0":{"60":1},"2":{"10":1,"16":1,"18":1,"28":1,"50":1,"54":1,"60":5,"61":4,"62":1,"63":1,"64":1}}],["npm",{"2":{"41":1,"43":1,"45":1}}],["native",{"2":{"14":1}}],["names",{"2":{"76":1}}],["name",{"2":{"13":1,"23":2,"27":2,"32":1,"39":5,"44":1,"49":1,"76":1}}],["named",{"2":{"10":1,"23":1,"66":1,"69":1,"70":1,"75":1}}],["node",{"2":{"26":6,"30":1,"34":1,"40":4,"41":2,"42":4,"72":4}}],["normal",{"2":{"25":1}}],["no",{"2":{"14":3,"16":1,"47":1,"56":1,"58":1,"76":1}}],["noneerror",{"2":{"32":1}}],["nonefile",{"2":{"16":1}}],["none",{"2":{"16":7,"17":6,"18":4,"21":8,"26":4,"30":1,"31":1,"32":3,"42":2,"61":2,"72":2}}],["non",{"2":{"14":1}}],["now",{"2":{"8":1,"11":1,"12":1,"14":1,"22":1,"23":1,"24":1,"27":1,"30":1,"32":1,"45":1,"56":1,"61":2,"65":1,"67":1,"69":1,"76":1}}],["notice",{"2":{"55":1,"59":1}}],["noticed",{"2":{"11":1,"48":1,"71":1}}],["notable",{"2":{"23":1}}],["not",{"0":{"77":1},"2":{"10":1,"14":1,"16":5,"18":1,"32":4,"40":1,"44":2,"52":1,"53":1,"56":2,"67":1,"73":1}}],["note",{"2":{"4":1,"14":1,"27":1,"39":1,"40":1,"41":1,"55":1,"56":1,"61":1,"62":1,"69":1,"70":1}}],["nothing",{"2":{"3":1}}],["necessary",{"2":{"39":1,"44":1}}],["nesting",{"2":{"18":1,"20":1}}],["nested",{"2":{"18":1}}],["next",{"2":{"11":1,"15":1,"61":1,"73":1}}],["needed",{"2":{"75":1}}],["need",{"2":{"4":1,"23":1,"24":1,"27":1,"32":1,"35":1,"40":2,"41":1,"42":2,"45":1,"46":1,"50":1,"53":1,"66":1,"69":1,"71":1}}],["newbie",{"2":{"32":1}}],["newcelsius",{"2":{"3":4,"4":4,"15":4}}],["new",{"2":{"3":1,"21":1,"23":1,"27":2,"42":3,"45":2,"47":3,"61":1,"65":2,"66":1,"70":1,"72":1,"76":1}}],["personal",{"2":{"68":1}}],["period",{"2":{"59":1}}],["phony",{"2":{"42":2,"44":5,"45":2}}],["pps",{"2":{"40":2,"42":2,"50":1}}],["ppx",{"2":{"32":1,"40":8,"42":4,"48":1,"50":2}}],["please",{"2":{"77":1}}],["plugin",{"2":{"39":2,"49":1}}],["plain",{"2":{"73":1}}],["play",{"2":{"63":1,"71":1}}],["playground",{"0":{"56":1,"61":1},"2":{"12":1,"14":2,"54":3,"56":1,"57":2,"58":1,"59":1,"61":3,"62":1,"63":1,"71":1}}],["platform",{"2":{"35":1,"37":3}}],["place",{"2":{"32":1}}],["placeholder",{"2":{"17":2}}],["puked",{"2":{"76":1}}],["pure",{"2":{"56":2}}],["purposes",{"2":{"38":1}}],["public",{"2":{"50":1}}],["putting",{"2":{"11":1,"50":1}}],["put",{"2":{"6":1,"32":1,"38":1,"39":1,"40":2,"42":2,"47":1}}],["pane",{"2":{"56":2}}],["panel",{"2":{"56":2}}],["paste",{"2":{"56":1,"61":1}}],["passing",{"2":{"76":1}}],["passes",{"2":{"73":1}}],["passed",{"2":{"70":1}}],["pass",{"2":{"10":1,"14":1,"49":1}}],["page",{"2":{"36":2}}],["package",{"2":{"35":1,"45":1,"47":1,"50":1}}],["parameter",{"2":{"57":1}}],["parse",{"2":{"19":1}}],["particular",{"2":{"53":1}}],["partial",{"0":{"11":1},"2":{"11":1,"12":2,"13":1,"14":1,"16":2,"32":2,"54":1}}],["part",{"2":{"32":1}}],["parts",{"2":{"3":1,"66":1,"75":1}}],["pattern",{"2":{"16":3,"32":2,"67":1,"75":1}}],["potoffood",{"2":{"76":1}}],["potential",{"2":{"14":1}}],["pop",{"2":{"65":1}}],["points",{"2":{"36":1}}],["pointed",{"2":{"22":1}}],["point",{"2":{"17":1,"18":1}}],["power",{"2":{"16":1,"39":1}}],["possible",{"2":{"10":1,"12":2,"39":1,"40":1}}],["positional",{"2":{"10":1,"13":1}}],["polymorphic",{"2":{"3":1,"4":1,"54":1}}],["prices",{"2":{"70":1}}],["price",{"2":{"66":1,"67":2,"76":1}}],["prints",{"2":{"59":2,"60":4,"64":2}}],["primarily",{"2":{"49":1,"71":1}}],["primary",{"2":{"38":1,"66":1,"70":1}}],["precision",{"2":{"64":1}}],["press",{"2":{"50":1}}],["prefix",{"2":{"76":1}}],["prefixed",{"2":{"48":1}}],["prefer",{"2":{"20":1}}],["pretty",{"2":{"41":1}}],["preprocess",{"2":{"40":2,"41":1,"42":2,"50":1}}],["preprocessors",{"2":{"40":2,"42":2}}],["prerequisites",{"0":{"34":1}}],["preview",{"2":{"61":4}}],["previous",{"2":{"16":1,"61":1,"62":1,"65":1}}],["prevent",{"2":{"11":1,"21":1}}],["promotion",{"2":{"67":1}}],["prompt",{"2":{"35":1}}],["progress",{"2":{"51":1}}],["programs",{"2":{"61":1,"63":1}}],["programming",{"2":{"39":1,"53":1}}],["program",{"2":{"12":1,"15":1,"58":1,"62":1,"63":1,"74":1}}],["provided",{"2":{"45":1}}],["process",{"2":{"40":2}}],["profile",{"2":{"35":2}}],["prop",{"2":{"29":1,"31":1,"69":1,"70":1,"73":2}}],["props",{"2":{"10":1,"73":1,"75":1}}],["projects",{"2":{"38":1,"43":2,"47":2,"49":1,"65":1}}],["project",{"0":{"36":1,"39":1},"2":{"22":3,"36":2,"37":1,"38":1,"39":9,"40":5,"41":1,"47":4,"49":3,"50":4,"52":1}}],["producing",{"2":{"21":1}}],["produce",{"2":{"19":1,"40":2,"42":2}}],["problems",{"2":{"56":3}}],["problem",{"2":{"14":1}}],["probably",{"2":{"6":1,"59":1}}],["practice",{"2":{"9":1}}],["pipe",{"0":{"5":1,"28":1},"2":{"5":1,"12":1,"28":1,"31":1,"54":1}}],["jellobutter",{"2":{"65":1,"66":1,"67":1,"76":1}}],["just",{"0":{"60":1},"2":{"21":1,"22":1,"23":1,"24":1,"32":1,"39":1,"42":1,"43":1,"44":1,"59":1,"71":1,"76":1}}],["javascriptconsole",{"2":{"71":1}}],["javascript",{"2":{"3":1,"4":1,"9":2,"23":1,"32":1,"40":8,"41":5,"42":6,"49":1,"52":1,"53":1,"55":1,"56":1,"58":1,"60":2,"61":1,"62":1,"63":3,"64":1,"70":1,"71":2,"73":1,"75":2,"76":1}}],["jswebpack",{"2":{"41":1}}],["json",{"2":{"37":2,"45":1,"46":1,"47":1,"50":1}}],["jsx",{"2":{"22":1,"32":1,"40":6,"42":2,"50":1,"54":1,"61":1}}],["js",{"0":{"4":1,"73":1},"2":{"3":3,"10":4,"11":5,"12":7,"13":5,"14":2,"15":2,"16":2,"17":6,"18":4,"19":2,"21":11,"26":2,"34":1,"41":3,"42":2,"45":4,"50":1,"55":8,"56":8,"58":11,"59":7,"60":8,"61":2,"62":2,"64":2,"69":2,"70":9,"71":19,"72":2,"73":5,"74":1,"75":5,"76":6}}],["js|🌭|js",{"2":{"76":2}}],["js|🍔|js",{"2":{"68":2,"76":2}}],["js|🥪|js",{"2":{"68":2,"76":2}}],["js|unreasonably",{"2":{"18":4,"21":10}}],["js||js",{"2":{"14":1}}],["js|°f|js",{"2":{"6":2,"7":4,"8":2,"9":2,"10":2,"11":2,"15":2,"16":2,"17":2,"21":4}}],["js|°c",{"2":{"3":2,"4":2,"5":2,"6":2,"7":4,"8":2,"9":2,"10":2,"11":2,"12":1,"15":2}}],["js|",{"2":{"3":2,"4":2,"8":2,"9":2,"10":2,"11":2,"15":2,"16":2,"18":4,"21":4}}],["1px",{"2":{"61":2}}],["16l",{"2":{"64":2}}],["16",{"2":{"59":4}}],["15",{"2":{"56":2,"66":2,"68":2,"76":2}}],["142",{"2":{"56":1}}],["19",{"2":{"32":2}}],["1em",{"2":{"29":4,"61":10,"64":4}}],["12",{"2":{"71":6}}],["128",{"2":{"19":1,"21":5}}],["1223456",{"2":{"10":1}}],["11",{"2":{"16":2}}],["10",{"2":{"12":3,"14":1,"16":2,"66":2,"67":8,"68":2,"76":2}}],["1",{"2":{"3":2,"12":1,"14":1,"19":1,"21":1,"26":1,"27":4,"29":4,"30":1,"32":1,"39":6,"47":1,"48":1,"50":1,"53":1,"55":4,"56":9,"57":1,"61":8,"62":2,"64":3,"66":1,"74":1,"76":1}}],["glimpse",{"2":{"76":1}}],["ground",{"2":{"53":1}}],["great",{"2":{"47":1,"54":1,"63":1,"66":1,"73":1}}],["grabbing",{"2":{"36":1}}],["guess",{"2":{"59":1}}],["guide",{"2":{"53":1,"54":1}}],["guided",{"2":{"52":1}}],["guidelines",{"2":{"47":1}}],["guard",{"0":{"18":1},"2":{"18":1,"19":1,"20":1,"21":1,"54":1}}],["generated",{"2":{"56":3}}],["generate",{"2":{"40":4,"42":4,"44":1,"76":1}}],["generic",{"2":{"29":1,"31":1}}],["gets",{"2":{"50":1}}],["getvaluefromevent",{"2":{"4":5,"12":2,"14":4,"15":4}}],["get",{"2":{"3":1,"9":1,"10":1,"13":1,"16":1,"48":1,"58":1,"60":1,"71":3,"74":1}}],["github",{"2":{"36":2,"40":2}}],["githubusercontent",{"2":{"35":2}}],["git",{"2":{"34":1}}],["given",{"2":{"56":1,"66":1}}],["give",{"2":{"17":1,"41":1,"65":1}}],["gives",{"2":{"16":1,"18":1}}],["gibberish",{"2":{"14":1}}],["g",{"2":{"10":1,"64":1,"75":1}}],["go",{"2":{"22":2,"24":1,"35":1,"45":1}}],["going",{"2":{"22":1,"47":3,"70":1}}],["goes",{"2":{"18":1,"53":1}}],["got",{"2":{"11":1,"73":1}}],["gotten",{"2":{"9":1}}],["good",{"2":{"4":1,"53":1}}],["gt",{"0":{"5":1},"2":{"3":17,"4":18,"5":16,"6":14,"7":36,"8":18,"9":18,"10":16,"11":29,"12":7,"14":6,"15":30,"16":17,"17":33,"18":22,"21":42,"22":6,"24":1,"25":1,"26":6,"27":28,"28":4,"29":28,"32":4,"42":10,"58":4,"61":66,"64":32,"66":6,"67":14,"68":8,"69":24,"70":38,"71":54,"72":18,"73":12,"74":2,"76":84}}],["wunderbar",{"2":{"73":1}}],["w00t",{"2":{"61":1}}],["watch",{"2":{"22":2}}],["water",{"2":{"18":1}}],["warning",{"2":{"16":2,"32":4,"51":1,"73":3}}],["warn",{"2":{"15":1}}],["ways",{"2":{"15":1,"35":1}}],["way",{"2":{"11":1,"15":1,"28":1,"29":1,"31":1,"57":1,"58":1,"63":1,"67":1,"73":1,"76":1}}],["was",{"2":{"11":1,"14":1,"16":1,"17":1,"36":1,"56":2,"58":2,"71":2,"76":1}}],["wants",{"2":{"32":1}}],["want",{"2":{"7":1,"32":2,"35":2,"44":1,"45":1,"47":1,"61":1,"68":1,"75":1}}],["wanted",{"2":{"7":1,"18":1,"41":1}}],["world",{"2":{"36":1,"49":1}}],["worry",{"2":{"32":1}}],["work",{"2":{"51":1,"55":2,"77":1}}],["worked",{"2":{"37":1}}],["working",{"2":{"11":1}}],["works",{"2":{"9":1,"18":1,"36":1,"54":1}}],["would",{"2":{"16":1,"50":1,"55":1,"64":1,"66":2,"67":3,"74":1}}],["wouldn",{"2":{"3":1}}],["wondering",{"2":{"43":1}}],["won",{"2":{"15":1,"46":1,"54":1,"59":2}}],["wrote",{"2":{"28":1}}],["writing",{"2":{"11":1,"77":1}}],["write",{"2":{"3":1,"7":1,"12":1,"50":1,"55":1}}],["wrapped",{"2":{"26":1}}],["wrapping",{"2":{"8":1}}],["wraps",{"2":{"17":2}}],["wrap",{"0":{"4":1,"25":1},"2":{"4":1,"22":1,"25":1}}],["why",{"0":{"43":1},"2":{"43":1}}],["whole",{"2":{"39":1}}],["whose",{"2":{"31":1,"41":1,"68":1,"75":1}}],["white",{"2":{"61":2}}],["while",{"2":{"35":1,"36":2,"56":1,"76":1}}],["which",{"2":{"3":3,"7":1,"11":2,"15":2,"17":2,"28":2,"34":1,"36":1,"39":4,"40":6,"41":2,"42":3,"44":1,"45":1,"49":4,"50":1,"53":1,"54":1,"56":1,"61":1,"69":1,"70":3,"71":2,"76":1}}],["whether",{"2":{"68":1}}],["where",{"2":{"32":2,"40":2,"41":1,"47":1,"50":1,"64":1,"73":1}}],["when",{"0":{"18":1},"2":{"8":1,"9":1,"10":1,"14":1,"15":1,"17":1,"18":4,"19":1,"20":3,"21":12,"37":1,"44":1,"50":1,"54":1,"56":1,"63":1,"73":1,"75":3}}],["what",{"2":{"3":1,"6":1,"12":3,"15":1,"16":1,"18":1,"25":1,"30":3,"31":1,"32":2,"48":2,"56":1,"59":3,"62":1,"71":1}}],["website",{"2":{"65":1}}],["webpack",{"2":{"22":1,"42":1,"45":3,"50":1}}],["welcome",{"2":{"22":2}}],["well",{"2":{"10":1,"40":2,"42":2}}],["were",{"2":{"3":1,"17":1}}],["we",{"2":{"3":4,"6":2,"7":4,"8":1,"9":1,"10":5,"11":2,"13":1,"14":6,"15":2,"17":4,"18":3,"22":4,"24":1,"25":1,"26":2,"27":1,"28":1,"29":2,"31":1,"35":1,"38":1,"39":3,"41":5,"43":2,"44":3,"45":4,"47":4,"48":1,"49":1,"50":1,"52":1,"53":1,"54":2,"57":1,"61":3,"62":1,"65":1,"68":2,"69":2,"70":2,"71":5,"73":4,"74":1,"76":3,"77":1}}],["windows",{"2":{"34":2}}],["window",{"2":{"22":1}}],["wildcards",{"2":{"67":1,"75":2}}],["wildcard",{"0":{"67":1},"2":{"7":1,"67":1}}],["will",{"2":{"3":1,"7":3,"14":1,"15":1,"16":1,"22":1,"32":2,"35":1,"40":4,"42":5,"44":1,"47":3,"50":3,"57":1,"59":1,"66":2,"69":1}}],["without",{"2":{"10":1,"18":1,"20":1,"66":1}}],["with",{"0":{"7":1,"25":1},"2":{"10":2,"12":1,"13":1,"14":3,"17":1,"19":1,"21":2,"22":3,"23":1,"24":1,"27":3,"32":2,"35":2,"38":2,"40":8,"41":1,"42":4,"44":1,"45":2,"47":1,"48":1,"50":2,"53":1,"54":2,"56":1,"57":2,"59":1,"63":2,"66":1,"73":1,"76":2}}],["widgets",{"0":{"61":1}}],["widget",{"2":{"3":1,"65":1}}],["h1",{"2":{"72":4}}],["huzzah",{"2":{"47":1}}],["html",{"2":{"40":2,"42":2,"50":1}}],["https",{"2":{"35":2,"36":2,"40":6,"42":2,"57":1}}],["http",{"2":{"2":1,"22":1,"36":1,"45":3}}],["hotdog",{"2":{"66":4,"76":9}}],["hot",{"2":{"65":1}}],["hot🥵|js",{"2":{"18":4,"21":6}}],["hooks",{"2":{"27":1}}],["hook",{"2":{"27":1}}],["hooray",{"2":{"18":1}}],["hover",{"2":{"25":1,"37":1,"56":1,"63":1,"73":1}}],["hovering",{"2":{"17":1}}],["how",{"2":{"11":1,"18":1,"29":1,"31":1,"34":1,"38":1,"50":1,"53":2,"60":1,"62":1,"73":1}}],["however",{"2":{"7":1,"14":1,"39":1,"59":1,"67":1}}],["high",{"2":{"64":2}}],["highlighted",{"2":{"37":1}}],["hints",{"2":{"63":1}}],["hint",{"2":{"12":1,"19":2,"62":1}}],["hidden",{"2":{"12":1}}],["hypothesis",{"2":{"12":1}}],["her",{"2":{"76":1}}],["here",{"2":{"5":1,"16":3,"32":2,"40":5,"41":1,"42":2,"44":2,"50":2,"57":1,"59":1,"61":1,"67":1,"70":1}}],["hello",{"2":{"36":1}}],["helps",{"2":{"75":1}}],["helpful",{"2":{"63":1}}],["helpfully",{"2":{"20":1}}],["helper",{"2":{"4":1,"17":1,"21":1,"66":1,"74":2,"76":1}}],["happy",{"2":{"71":1}}],["happens",{"2":{"12":1,"15":1,"30":3,"48":1,"56":1}}],["happened",{"2":{"11":1,"76":1}}],["hard",{"2":{"55":1,"77":1}}],["had",{"2":{"17":1}}],["having",{"2":{"16":1}}],["haven",{"2":{"9":1}}],["have",{"2":{"3":1,"11":2,"13":3,"14":2,"34":1,"38":3,"39":3,"47":2,"48":1,"55":1,"58":1,"64":1,"71":1,"73":2,"75":1,"76":4}}],["handling",{"2":{"54":2}}],["handled",{"2":{"16":1}}],["handle",{"2":{"16":1,"19":1,"20":1,"32":1}}],["handler",{"2":{"3":1,"14":1}}],["handy",{"2":{"5":1}}],["has",{"2":{"3":1,"10":1,"16":2,"24":1,"31":1,"38":1,"42":1,"56":4,"58":2,"63":1,"65":1,"69":2,"70":2,"71":2,"73":2,"75":1}}],["d",{"2":{"41":1,"50":1,"71":1}}],["data",{"2":{"28":1,"39":1}}],["dune",{"0":{"38":1,"39":1,"40":1},"1":{"39":1,"40":1,"41":1,"42":1,"43":1,"44":1,"45":1,"46":1,"47":1,"48":1,"49":1,"50":1},"2":{"18":1,"38":2,"39":14,"40":23,"41":2,"42":11,"46":1,"47":4,"48":1,"49":6,"50":3,"65":3}}],["deeper",{"2":{"53":1}}],["deleting",{"2":{"50":1}}],["delete",{"2":{"46":2,"48":1}}],["delimited",{"2":{"14":1}}],["detour",{"2":{"43":1}}],["detail",{"2":{"22":1,"73":1}}],["details",{"2":{"14":2,"24":1,"32":1,"41":1,"50":1}}],["describe",{"2":{"49":1}}],["describes",{"2":{"49":1}}],["designed",{"2":{"38":1}}],["despite",{"2":{"32":1}}],["default`",{"2":{"40":2,"42":2}}],["defaultformatter",{"2":{"37":2}}],["default",{"2":{"36":1,"40":2,"41":4,"42":2,"45":5}}],["defined",{"2":{"23":1,"50":1,"61":1}}],["define",{"2":{"14":2,"21":1,"27":1,"68":1,"69":1}}],["depends",{"2":{"45":1}}],["dependencies",{"2":{"36":1,"40":4,"42":4,"47":1}}],["depending",{"2":{"35":1,"38":1}}],["decides",{"2":{"66":1,"67":1}}],["decide",{"2":{"65":1}}],["decimals",{"2":{"10":3}}],["decrement",{"2":{"62":1}}],["decremented",{"2":{"54":1}}],["declaring",{"2":{"44":1}}],["declare",{"2":{"32":1}}],["decorate",{"2":{"22":1,"24":1}}],["developed",{"2":{"47":1}}],["developers",{"0":{"0":1,"51":1},"1":{"1":1,"2":1,"52":1,"53":1,"54":1},"2":{"14":1,"21":1,"32":1,"50":1,"64":1,"76":1}}],["development",{"2":{"41":2,"45":2}}],["developing",{"2":{"22":1}}],["dev",{"2":{"22":1,"42":1,"45":1,"73":1}}],["devs",{"2":{"22":1,"36":5,"37":1,"39":2}}],["degrees",{"2":{"19":1,"21":1}}],["deal",{"2":{"14":1}}],["doing",{"2":{"47":1}}],["docendo",{"2":{"77":1}}],["documentation",{"2":{"47":1}}],["docs",{"2":{"14":1,"40":2,"60":1}}],["done",{"2":{"56":1,"76":1}}],["don",{"2":{"32":2,"38":1,"40":2,"74":1}}],["dom",{"2":{"26":4,"42":1}}],["downloading",{"2":{"36":1}}],["download",{"0":{"36":1}}],["down",{"2":{"17":1,"26":1}}],["does",{"2":{"16":1,"25":1,"26":1,"74":1}}],["doesn",{"2":{"11":1,"15":1,"26":1,"29":1,"31":1,"39":1,"64":1,"71":1}}],["do",{"2":{"6":1,"8":1,"12":2,"18":1,"35":2,"38":1,"48":1,"53":1,"62":1,"67":1,"76":1}}],["disco",{"2":{"77":1}}],["discover",{"2":{"59":1}}],["discuss",{"2":{"73":1}}],["distinction",{"2":{"55":1}}],["displays",{"2":{"25":1}}],["display",{"2":{"5":1,"28":1,"45":1,"63":3}}],["digestible",{"2":{"52":1}}],["digits",{"2":{"10":1}}],["dirs",{"2":{"40":3}}],["directories",{"2":{"40":4,"49":2}}],["directory",{"0":{"42":1,"45":1},"2":{"22":1,"39":1,"40":1,"41":1,"42":4,"44":3,"45":3,"46":2,"47":4,"65":2}}],["differences",{"2":{"22":2}}],["difference",{"2":{"16":1,"23":1}}],["differently",{"2":{"55":1}}],["different",{"2":{"14":1,"17":1,"63":1,"74":1,"76":1}}],["didn",{"2":{"27":1,"55":1,"76":1}}],["did",{"2":{"16":1,"48":1}}],["divfile",{"2":{"50":1}}],["div",{"2":{"3":4,"4":4,"5":4,"6":4,"7":8,"8":4,"9":4,"10":4,"11":4,"15":4,"22":4,"27":4,"29":4,"50":5,"61":8,"64":4,"72":4,"76":1}}],["my",{"2":{"22":2}}],["mismatch",{"2":{"71":1}}],["missing",{"2":{"50":2}}],["mistake",{"2":{"32":1,"50":1}}],["minimal",{"2":{"39":1}}],["minimum",{"2":{"18":1}}],["might",{"2":{"8":1,"11":1,"12":1,"14":1,"15":1,"17":1,"36":1,"39":1,"43":1,"48":1,"55":1,"71":2}}],["multiple",{"2":{"61":1,"73":1}}],["multiples",{"2":{"23":1}}],["much",{"2":{"16":1,"38":1,"41":1,"53":1,"54":1}}],["must",{"2":{"4":1,"9":1,"13":1,"14":1,"22":3,"25":1,"41":1,"71":1,"76":2}}],["mouth",{"2":{"76":1}}],["moment",{"2":{"56":1}}],["motivation",{"0":{"52":1}}],["monorepo",{"0":{"47":1},"2":{"47":2}}],["move",{"2":{"42":1,"74":1,"76":2}}],["modify",{"2":{"27":1,"35":2}}],["modes",{"2":{"40":2,"42":2}}],["mode",{"2":{"22":1,"41":2,"45":2}}],["modules",{"0":{"23":1},"2":{"23":4,"27":1,"31":1,"40":6,"42":2,"66":1}}],["module",{"2":{"17":1,"22":2,"23":2,"27":3,"40":4,"41":1,"42":5,"54":1,"56":2,"61":4,"62":1,"66":2,"72":1,"74":2,"75":2,"76":7}}],["most",{"2":{"12":1,"47":1,"50":1}}],["more",{"2":{"4":1,"8":1,"9":1,"10":1,"14":2,"18":1,"20":1,"22":1,"24":1,"26":1,"29":1,"32":1,"39":2,"40":1,"41":1,"50":1,"54":1,"61":1,"62":1,"73":1,"75":1}}],["madame",{"2":{"65":1,"66":1,"67":1,"76":1}}],["made",{"2":{"15":1}}],["main",{"2":{"41":1,"75":1}}],["maintain",{"2":{"24":1}}],["master",{"2":{"35":2}}],["manager",{"2":{"35":1}}],["manually",{"2":{"27":1,"39":1}}],["many",{"2":{"17":1,"34":1,"35":1,"38":1,"39":1,"52":1,"63":1}}],["making",{"2":{"18":1}}],["makefiles",{"2":{"49":1}}],["makefilesrc",{"2":{"42":1}}],["makefile",{"0":{"43":1,"44":1,"45":1},"2":{"42":2,"43":2,"44":5,"45":2,"47":1,"48":1,"50":6,"65":3}}],["makeprops",{"2":{"32":2}}],["makepropsfile",{"2":{"32":1}}],["makes",{"2":{"32":1,"38":1,"39":1,"55":1,"66":1,"75":1}}],["make",{"0":{"24":1,"69":1},"2":{"3":2,"4":2,"13":1,"15":2,"17":1,"20":1,"22":7,"24":3,"26":1,"27":4,"29":3,"32":3,"36":9,"39":1,"42":6,"43":1,"44":6,"45":2,"49":2,"52":1,"53":1,"58":1,"61":16,"62":2,"64":4,"69":4,"70":3,"71":4,"72":3,"75":1,"76":5}}],["maybe",{"2":{"76":1}}],["may",{"2":{"17":2,"38":1,"59":1,"76":1}}],["mapi",{"0":{"73":1},"2":{"73":4,"75":1,"76":2}}],["map",{"0":{"17":1},"2":{"17":13,"18":4,"20":1,"21":6,"54":1,"70":4,"71":10,"73":2,"75":3}}],["matched",{"2":{"16":2,"32":2}}],["matching",{"2":{"16":3,"32":2,"67":2,"75":1}}],["match",{"2":{"7":1,"16":2,"32":2,"67":1,"71":1}}],["menu",{"2":{"66":1,"74":1,"76":1}}],["mesages",{"2":{"56":1}}],["messages",{"2":{"39":2,"63":1}}],["message",{"2":{"18":1,"26":1}}],["mel",{"2":{"40":2}}],["melange",{"0":{"0":1,"41":1,"51":1,"56":1},"1":{"1":1,"2":1,"52":1,"53":1,"54":1},"2":{"3":1,"12":1,"14":2,"18":1,"21":1,"22":2,"32":1,"36":7,"37":1,"38":1,"39":11,"40":18,"41":5,"42":13,"43":1,"46":1,"47":4,"48":1,"49":3,"50":1,"52":2,"54":2,"56":5,"57":2,"58":1,"60":1,"61":2,"62":1,"63":1,"64":1,"65":1,"70":2,"76":1}}],["metadata",{"2":{"39":3,"49":1}}],["method",{"2":{"35":1,"70":2,"73":1,"75":2}}],["methods",{"2":{"3":1}}],["mean",{"2":{"50":1}}],["means",{"2":{"16":1,"44":1,"45":1,"69":1,"70":1}}],["meaning",{"2":{"7":1,"16":1,"24":1,"26":1,"44":1}}],["measure",{"2":{"6":1}}],["leave",{"2":{"71":1}}],["learning",{"2":{"53":1}}],["learn",{"2":{"52":1}}],["leverage",{"2":{"53":1}}],["level",{"2":{"39":1}}],["less",{"2":{"16":1,"19":1,"21":1,"75":1}}],["left",{"2":{"16":1,"28":1,"56":2}}],["lets",{"2":{"40":2,"42":2}}],["let",{"2":{"3":7,"4":9,"5":1,"12":1,"15":9,"17":1,"21":3,"22":3,"24":1,"26":1,"27":8,"29":1,"32":2,"35":1,"36":1,"42":4,"43":1,"55":6,"56":6,"58":3,"60":3,"61":13,"62":1,"64":5,"66":1,"67":3,"68":2,"69":2,"70":4,"71":4,"72":6,"76":11}}],["large",{"2":{"62":1}}],["layout",{"2":{"41":1}}],["lang",{"2":{"39":2}}],["language=reason",{"2":{"57":1}}],["language",{"2":{"32":1,"39":2,"53":2}}],["label",{"2":{"44":1}}],["labeled",{"0":{"10":1},"2":{"10":4,"13":1,"54":1,"69":1,"70":1,"75":1}}],["labs",{"2":{"37":1}}],["later",{"2":{"18":1,"24":1,"39":1,"46":1,"73":1,"76":1,"77":1}}],["latest",{"2":{"14":1}}],["last",{"0":{"5":1,"28":1},"2":{"5":1,"12":1,"15":1,"28":1,"31":1,"36":1,"54":2,"58":3}}],["ll",{"2":{"9":1,"10":2,"11":1,"15":1,"18":2,"19":1,"22":1,"24":2,"25":1,"26":1,"29":2,"38":1,"39":1,"53":1,"54":1,"56":1,"58":1,"69":1,"71":1,"73":3,"76":3}}],["lowercase",{"2":{"76":1}}],["lowers",{"2":{"67":1}}],["lowest",{"2":{"19":1}}],["low",{"2":{"64":2}}],["locally",{"2":{"47":1}}],["local",{"2":{"45":2}}],["localhost",{"2":{"2":1,"22":1,"36":1,"45":1}}],["long",{"2":{"17":1}}],["longer",{"2":{"14":3,"47":1}}],["log",{"2":{"12":6,"26":1,"55":8,"56":13,"58":14,"59":10,"60":4,"61":2,"62":2,"64":2,"71":9}}],["logic",{"2":{"5":1,"11":1,"21":1}}],["looks",{"2":{"40":1,"73":1}}],["look",{"2":{"12":1,"15":1,"17":1,"18":1,"27":1,"40":2,"42":3,"50":1,"61":1,"62":1,"64":1,"71":1,"74":1,"76":2}}],["lot",{"2":{"10":2,"70":1}}],["live",{"2":{"61":1}}],["live=off",{"2":{"57":1}}],["lives",{"2":{"44":1}}],["libraries",{"2":{"40":7,"41":1,"42":6}}],["library",{"2":{"17":1,"62":1}}],["lisp",{"2":{"39":1}}],["lists",{"2":{"40":2,"42":2}}],["list",{"2":{"4":1,"40":2,"42":2,"50":1,"73":2}}],["link",{"2":{"57":1,"61":1}}],["linux",{"2":{"34":1}}],["lines",{"2":{"16":2,"71":2}}],["line",{"2":{"4":1,"5":1,"32":2,"39":1,"44":2,"50":3,"56":1,"58":3}}],["little",{"2":{"26":1,"38":1,"61":1,"75":1,"76":1}}],["literals",{"2":{"14":1,"55":1,"59":1}}],["limit",{"2":{"10":1}}],["like",{"2":{"4":1,"8":1,"15":1,"17":1,"18":1,"23":1,"35":1,"40":1,"42":1,"44":1,"50":1,"56":1,"59":1,"64":1,"71":1,"76":4}}],["lt",{"2":{"3":6,"4":6,"5":6,"6":6,"7":12,"8":6,"9":6,"10":6,"11":6,"12":2,"14":2,"15":6,"21":4,"22":4,"26":2,"27":14,"29":16,"32":2,"35":2,"42":4,"50":2,"58":4,"61":34,"64":16,"69":12,"70":22,"71":26,"72":12,"73":2,"76":38}}],["++",{"0":{"6":1},"2":{"6":3,"7":4,"8":2,"9":2,"10":2,"11":2,"13":1,"15":2,"16":4,"17":2,"18":4,"21":6,"73":2,"76":2}}],["+",{"2":{"3":3,"4":2,"12":2,"15":2,"27":4,"29":4,"53":2,"59":4,"61":8,"62":1,"64":4,"70":2,"71":2,"76":2}}],["5em",{"2":{"61":4}}],["58",{"2":{"59":1,"64":2}}],["58js",{"2":{"59":1}}],["55",{"2":{"58":2}}],["5",{"2":{"3":2,"4":2,"12":2,"15":2,"50":4,"58":2,"62":2,"64":4,"73":1,"76":2}}],["5173",{"2":{"2":1}}],["000l",{"2":{"62":2}}],["000",{"2":{"62":4}}],["00",{"2":{"19":1,"21":1}}],["0",{"2":{"3":6,"4":6,"15":6,"18":4,"19":1,"21":6,"27":2,"39":6,"40":4,"57":1,"58":2,"59":19,"60":2,"61":6,"62":1,"64":8,"70":2,"71":2,"76":2}}],["9",{"2":{"3":2,"4":2,"15":2}}],["==",{"2":{"8":2,"9":2,"10":2,"11":2,"15":2,"19":1,"21":2,"58":6}}],["=",{"2":{"3":21,"4":24,"5":2,"6":2,"7":12,"8":6,"9":6,"10":6,"11":6,"12":8,"14":5,"15":28,"16":9,"17":17,"18":10,"21":28,"22":6,"24":1,"25":1,"26":6,"27":22,"29":8,"32":4,"42":12,"55":8,"56":12,"58":8,"60":4,"61":40,"62":2,"64":18,"66":10,"67":20,"68":12,"69":4,"70":12,"71":16,"72":14,"73":6,"76":40}}],["fssl",{"2":{"35":2}}],["flex",{"2":{"29":2,"61":4,"64":2}}],["floats",{"2":{"59":1,"63":1}}],["floatfromstring",{"2":{"21":5}}],["floatcelsius",{"2":{"11":1}}],["float",{"0":{"16":1},"2":{"5":7,"6":4,"7":9,"8":4,"9":4,"10":6,"11":13,"15":4,"16":9,"17":14,"18":9,"19":5,"21":17,"54":1,"55":5,"56":5,"58":10,"59":2,"60":2,"62":1,"63":2,"64":3,"69":2,"70":2,"71":2,"74":1,"76":2}}],["fun",{"0":{"68":1},"2":{"68":7,"75":1,"76":4}}],["func",{"2":{"17":4}}],["functionality",{"2":{"16":1}}],["function",{"0":{"24":1},"2":{"4":3,"10":1,"11":2,"12":2,"13":2,"14":4,"16":2,"17":4,"19":1,"21":1,"22":2,"24":1,"25":2,"28":1,"31":1,"32":1,"50":1,"54":1,"61":3,"66":1,"69":2,"70":1,"73":2,"74":1,"75":4,"76":4}}],["functions",{"0":{"4":1},"2":{"4":4,"11":1,"12":1,"13":1,"15":2,"17":2,"20":2,"31":1,"66":1,"68":1,"73":1,"74":1,"75":2}}],["fully",{"2":{"61":1}}],["full",{"2":{"39":1,"50":1}}],["future",{"2":{"29":1,"47":1}}],["further",{"2":{"26":1}}],["f",{"2":{"18":1}}],["few",{"2":{"52":1}}],["feeling",{"2":{"65":1}}],["feel",{"2":{"46":1}}],["feeding",{"2":{"11":1}}],["fetch",{"2":{"36":1}}],["features",{"2":{"38":1,"53":1,"63":1}}],["feature",{"2":{"11":1,"38":1,"56":1}}],["friends",{"2":{"57":1}}],["friendly",{"2":{"8":1}}],["front",{"2":{"48":1,"50":1}}],["frontend",{"2":{"22":1,"53":1}}],["fromstring",{"2":{"19":1,"21":3}}],["from",{"2":{"11":2,"12":1,"14":3,"16":1,"17":1,"21":1,"23":1,"28":1,"32":1,"37":1,"40":6,"42":3,"45":1,"46":1,"48":1,"50":1,"53":1,"54":2,"55":1,"58":1,"59":1,"61":1,"65":1,"66":1,"76":2}}],["foo",{"2":{"55":8,"56":13,"58":9,"60":5}}],["focus",{"2":{"54":1}}],["four",{"2":{"48":1,"50":1}}],["found",{"2":{"14":1,"21":1,"32":1,"39":1,"50":2,"64":1,"76":1}}],["folder",{"2":{"40":4,"42":2}}],["following",{"2":{"10":1,"12":1,"27":1,"38":1,"70":1,"71":1}}],["forgot",{"2":{"73":1}}],["forget",{"2":{"15":1,"16":1}}],["forces",{"2":{"75":1}}],["force",{"2":{"66":1,"67":1}}],["fortunately",{"2":{"50":1}}],["forth",{"2":{"18":1}}],["forward",{"2":{"47":2}}],["format",{"2":{"74":1,"76":8}}],["formatonsave",{"2":{"37":2}}],["formatting",{"2":{"37":1}}],["form",{"2":{"3":5,"4":3,"15":2}}],["for",{"0":{"0":1,"47":1,"51":1,"68":1},"1":{"1":1,"2":1,"52":1,"53":1,"54":1},"2":{"3":2,"4":1,"9":1,"13":1,"14":4,"16":1,"17":2,"21":2,"22":1,"23":1,"24":1,"27":1,"28":1,"32":4,"34":1,"36":5,"37":2,"38":3,"39":7,"40":5,"41":6,"42":4,"44":1,"45":2,"46":1,"47":3,"48":2,"49":3,"50":4,"54":1,"55":1,"56":1,"57":1,"58":1,"59":1,"60":1,"61":2,"64":2,"65":1,"66":3,"68":1,"70":1,"71":1,"76":3}}],["famed",{"2":{"65":1}}],["familiar",{"2":{"9":1,"27":1,"44":1}}],["fairly",{"2":{"17":1,"27":1}}],["failed",{"2":{"26":2,"42":2,"61":2,"72":2}}],["fails",{"2":{"16":2}}],["fail",{"2":{"15":1,"66":1}}],["failure",{"2":{"7":2,"16":1}}],["fallback",{"2":{"9":1}}],["fahrenheit",{"2":{"3":1,"5":2,"6":1,"7":8,"8":4,"9":4,"10":5,"11":4,"15":4,"16":7,"17":4,"18":15,"19":1,"21":34,"54":1}}],["fix",{"2":{"58":1,"73":1}}],["fixed",{"2":{"45":1}}],["find",{"2":{"26":4,"42":2,"61":2,"72":2}}],["finally",{"2":{"5":1,"18":1}}],["first",{"2":{"17":1,"18":1,"29":1,"40":1,"54":1,"55":1,"66":1}}],["field",{"2":{"4":1,"40":4,"41":1,"42":4,"50":1}}],["fields",{"2":{"3":3,"13":2,"39":2,"41":1}}],["files",{"0":{"40":1},"2":{"27":1,"39":1,"40":5,"41":3,"42":5,"47":1,"49":1,"50":2,"61":1,"65":1}}],["file",{"0":{"39":1},"2":{"3":1,"16":1,"23":1,"27":2,"32":1,"37":1,"39":3,"40":4,"41":1,"42":1,"44":1,"46":3,"47":3,"49":1,"50":1,"65":1,"70":1,"71":1,"76":1}}],["impressive",{"2":{"61":1}}],["implicit",{"2":{"58":1}}],["implementation",{"2":{"17":1}}],["io",{"2":{"40":4,"42":2}}],["i",{"2":{"26":1}}],["identifier",{"2":{"14":1}}],["idea",{"2":{"4":1,"67":1}}],["if",{"0":{"9":1},"2":{"3":1,"4":1,"7":3,"8":1,"9":6,"10":1,"13":3,"15":1,"16":5,"17":2,"18":1,"19":2,"22":1,"25":1,"26":4,"27":1,"30":2,"32":2,"36":1,"39":1,"41":2,"47":1,"48":1,"54":1,"66":2,"67":1,"71":2,"73":1}}],["isnan",{"2":{"19":1,"21":2}}],["isn",{"2":{"11":1,"29":1,"44":1,"50":1,"63":1}}],["is",{"2":{"3":1,"4":2,"5":1,"7":2,"8":1,"9":1,"10":2,"13":1,"14":1,"15":1,"16":9,"17":6,"18":1,"19":1,"20":2,"21":1,"22":1,"23":1,"24":1,"25":1,"26":2,"27":1,"28":1,"29":1,"31":1,"32":11,"35":1,"36":1,"38":2,"39":5,"40":13,"41":3,"42":6,"43":1,"44":6,"45":1,"47":1,"48":1,"49":2,"50":2,"51":1,"52":1,"53":1,"55":2,"56":2,"57":1,"58":1,"59":1,"62":1,"63":2,"64":2,"66":3,"67":1,"68":2,"70":4,"71":1,"73":2,"74":1,"75":5,"76":1}}],["itemrows",{"2":{"71":4}}],["items",{"2":{"66":1,"69":1,"70":6,"71":8,"72":4,"74":1,"76":4}}],["item",{"0":{"66":1,"69":1},"2":{"65":2,"66":6,"67":3,"68":1,"69":8,"70":12,"71":20,"73":8,"74":3,"76":29}}],["its",{"2":{"3":1,"17":1,"38":1,"44":1,"47":2,"50":1,"52":1,"53":1,"54":1,"56":1,"65":1}}],["it",{"2":{"3":4,"4":1,"6":1,"7":2,"8":3,"9":3,"10":4,"12":6,"14":1,"15":1,"16":4,"17":3,"18":1,"19":1,"22":2,"24":1,"25":2,"26":4,"27":2,"28":1,"29":1,"32":1,"35":3,"36":1,"38":2,"39":4,"40":6,"41":1,"42":2,"44":1,"45":3,"46":1,"47":1,"50":2,"56":2,"57":1,"58":1,"65":2,"66":1,"67":1,"68":3,"71":2,"73":6,"74":3,"76":1}}],["in64",{"2":{"64":1}}],["infer",{"2":{"55":1}}],["info",{"2":{"41":1,"47":1}}],["increment",{"2":{"62":1}}],["incremented",{"2":{"54":1}}],["includes",{"2":{"43":1}}],["include",{"2":{"40":3,"41":1,"49":1}}],["indicate",{"2":{"44":1}}],["independent",{"2":{"38":1,"39":1}}],["indexes",{"2":{"76":1}}],["index",{"0":{"72":1},"2":{"22":1,"32":3,"41":4,"42":3,"45":4,"46":1,"50":2,"65":2,"72":1,"73":6,"76":4}}],["initial",{"2":{"70":1}}],["initially",{"2":{"14":1}}],["init",{"2":{"35":3,"36":3}}],["invocations",{"2":{"56":1}}],["invoke",{"2":{"31":1}}],["invokes",{"2":{"17":1}}],["invoked",{"2":{"14":2}}],["invalid",{"2":{"8":1,"15":1}}],["int64",{"2":{"62":6,"64":4}}],["intthis",{"2":{"58":1}}],["intline",{"2":{"56":1}}],["introducing",{"2":{"52":1}}],["introduction",{"0":{"38":1},"1":{"39":1,"40":1,"41":1,"42":1,"43":1,"44":1,"45":1,"46":1,"47":1,"48":1,"49":1,"50":1},"2":{"52":1}}],["introduce",{"2":{"10":1,"53":1}}],["interactive",{"2":{"56":1}}],["interest",{"2":{"38":1,"40":1,"41":1,"49":1}}],["interesting",{"2":{"29":1}}],["interested",{"2":{"17":1,"53":1}}],["integer",{"2":{"3":1,"12":1,"28":1,"55":1,"58":1,"62":1,"63":2,"64":1}}],["int",{"2":{"12":1,"27":2,"28":3,"29":2,"54":1,"55":4,"56":7,"58":8,"60":2,"61":4,"73":4,"76":2}}],["into",{"2":{"4":1,"7":1,"24":1,"40":2,"41":1,"42":2,"45":1,"50":1,"53":1,"56":1,"73":1}}],["instances",{"2":{"60":1}}],["installed",{"2":{"22":1,"36":1}}],["install",{"2":{"1":1,"34":1,"35":3,"37":1}}],["installmake",{"2":{"1":1}}],["installation",{"0":{"1":1,"33":1},"1":{"34":1,"35":1,"36":1,"37":1},"2":{"35":1}}],["instead",{"2":{"4":1,"7":1,"8":1,"16":1,"19":2,"21":1,"29":1,"43":1,"45":1,"50":2,"54":1,"56":1,"62":1,"64":2,"73":1,"74":1,"76":1}}],["inside",{"2":{"3":1,"14":1,"22":1,"23":1,"25":1,"30":1,"40":3,"42":2,"50":1,"72":2,"74":2}}],["inputs",{"2":{"63":1}}],["input",{"2":{"3":3,"4":2,"5":2,"6":2,"7":5,"8":3,"9":2,"10":2,"11":2,"12":2,"14":3,"15":3,"16":1,"19":1,"20":1,"54":1}}],["in",{"0":{"61":1,"67":1},"2":{"3":2,"5":1,"7":1,"8":1,"9":7,"10":6,"11":2,"12":2,"13":2,"14":6,"15":2,"16":2,"17":2,"19":1,"21":3,"22":4,"23":2,"25":1,"26":2,"27":2,"28":1,"29":4,"30":2,"31":1,"32":6,"36":1,"37":2,"38":4,"39":5,"40":10,"41":4,"42":8,"43":1,"44":5,"45":4,"46":2,"47":4,"48":2,"49":1,"50":6,"51":1,"53":3,"54":1,"55":1,"56":3,"57":2,"58":2,"59":1,"61":5,"62":2,"63":2,"64":1,"65":1,"66":3,"67":3,"69":1,"70":1,"71":3,"72":1,"73":4,"74":1,"75":3,"76":4}}],["again",{"2":{"74":1}}],["agree",{"2":{"35":1}}],["amp",{"2":{"57":2}}],["audience",{"0":{"53":1}}],["automation",{"2":{"43":1,"49":1}}],["automatically",{"2":{"27":1}}],["auto",{"2":{"37":1}}],["able",{"2":{"45":1,"55":1}}],["above",{"2":{"18":1,"23":1,"32":1}}],["about",{"2":{"3":1,"4":1,"7":1,"14":1,"22":1,"26":1,"27":1,"32":1,"40":1,"41":2,"50":1,"59":1}}],["appropriate",{"2":{"56":1}}],["app=counter",{"2":{"44":1}}],["appear",{"2":{"39":1}}],["apps",{"2":{"38":1,"47":1,"53":1}}],["app",{"0":{"26":1,"42":1},"2":{"22":5,"23":1,"26":4,"27":3,"29":2,"30":1,"31":1,"32":1,"42":7,"44":1,"45":12,"47":4,"48":1,"50":1,"65":1,"72":5}}],["applies",{"2":{"58":1}}],["applications",{"2":{"53":1}}],["application",{"0":{"11":1},"2":{"11":1,"12":2,"13":1,"14":1,"45":2,"54":1}}],["apply",{"0":{"5":1},"2":{"28":1}}],["avoid",{"2":{"67":1}}],["avoids",{"2":{"15":1}}],["available",{"2":{"14":1}}],["adaptable",{"2":{"75":1}}],["adventurous",{"2":{"65":1}}],["advice",{"2":{"47":1}}],["advantages",{"2":{"66":1}}],["advantage",{"2":{"11":1,"16":1}}],["addition",{"2":{"59":1}}],["additions",{"2":{"17":1}}],["adding",{"2":{"43":1,"75":1}}],["added",{"2":{"42":1,"43":1,"66":1,"76":1}}],["add",{"2":{"15":1,"18":1,"19":1,"21":2,"29":1,"37":1,"50":1,"61":1,"62":2,"64":4,"66":5,"70":1,"71":1,"74":2,"76":3}}],["addfive",{"2":{"12":8,"14":1}}],["acc",{"2":{"70":4,"71":4,"76":4}}],["accordingly",{"2":{"67":1}}],["accepted",{"2":{"40":2}}],["accepts",{"2":{"17":1,"39":1}}],["accept",{"2":{"16":1,"63":1}}],["access",{"2":{"3":1,"13":1,"16":2}}],["actual",{"2":{"71":1}}],["actually",{"2":{"11":1,"17":1,"50":1,"71":1}}],["action",{"2":{"44":1,"72":1}}],["alias",{"2":{"61":2}}],["along",{"2":{"38":1}}],["alternate",{"2":{"31":1,"68":1}}],["already",{"2":{"22":1,"39":1,"53":1,"59":1}}],["also",{"2":{"8":1,"9":1,"13":2,"17":1,"31":1,"34":1,"40":1,"41":1,"46":1,"55":1,"57":1,"59":1,"61":1,"65":1,"66":1,"68":1,"73":1}}],["allows",{"2":{"18":2,"26":1,"40":4,"67":1}}],["allowing",{"2":{"5":1}}],["all",{"2":{"5":1,"15":1,"22":1,"23":1,"27":1,"32":1,"36":1,"39":1,"40":5,"42":2,"58":1,"59":1,"61":1,"67":1,"69":1,"70":1,"76":2}}],["always",{"2":{"4":1,"9":1}}],["arrayitems",{"2":{"73":1}}],["array",{"0":{"71":1,"73":1},"2":{"64":1,"70":15,"71":30,"73":11,"75":10,"76":9}}],["arrow",{"2":{"4":1}}],["arithmetic",{"0":{"59":1},"2":{"59":3,"63":2}}],["artifacts",{"2":{"40":2,"41":1,"42":2}}],["around",{"2":{"17":2,"63":1}}],["aren",{"2":{"9":1,"30":1}}],["are",{"0":{"23":1},"2":{"4":1,"13":1,"14":5,"16":1,"23":2,"27":1,"31":1,"34":3,"35":1,"39":1,"40":1,"41":2,"47":3,"52":1,"56":1,"58":1,"59":1,"60":1,"61":1,"63":3,"66":1,"71":1,"75":2,"76":1,"77":1}}],["arguments",{"2":{"10":1,"11":1,"13":3,"73":1,"75":1}}],["argument",{"0":{"10":1},"2":{"4":2,"10":4,"11":4,"12":1,"13":1,"16":1,"17":2,"24":1,"54":1,"69":1,"70":1}}],["after",{"2":{"4":2,"6":1,"15":1,"35":1,"42":1,"71":1,"75":1,"76":2}}],["assume",{"2":{"47":1}}],["ascii",{"2":{"14":1}}],["as",{"2":{"3":1,"4":1,"9":2,"10":1,"13":1,"14":1,"22":1,"24":1,"27":1,"34":1,"36":1,"39":1,"40":3,"42":2,"57":1,"63":2,"65":1,"66":1,"67":2,"75":1}}],["anatomy",{"0":{"44":1}}],["annotating",{"2":{"56":1}}],["annotations",{"2":{"13":1,"37":1}}],["annotated",{"2":{"4":1}}],["another",{"2":{"4":1,"5":1,"16":1,"19":1,"21":1,"22":1,"41":1,"43":1,"56":1,"58":1,"69":1,"74":1}}],["an",{"2":{"3":1,"8":1,"9":2,"12":1,"14":1,"15":1,"16":3,"17":3,"24":1,"26":2,"28":1,"29":1,"31":2,"32":2,"41":1,"44":1,"47":1,"56":3,"57":1,"58":4,"62":1,"64":1,"68":1,"69":1,"70":2,"71":2,"75":1}}],["anywhere",{"2":{"50":1,"74":1}}],["anymore",{"2":{"46":1}}],["anything",{"2":{"4":1,"7":1,"40":1}}],["any",{"2":{"3":1,"7":2,"13":1,"14":1,"17":2,"30":1,"65":1,"67":2,"76":1}}],["and",{"0":{"36":1,"54":1},"2":{"3":2,"5":1,"9":2,"10":2,"11":1,"12":2,"13":2,"14":3,"16":4,"17":3,"18":4,"21":1,"22":2,"23":1,"24":3,"26":1,"28":1,"29":2,"31":1,"34":1,"36":3,"37":3,"38":1,"39":3,"40":3,"41":1,"42":2,"43":1,"44":1,"45":2,"47":2,"48":1,"49":1,"50":3,"52":4,"53":1,"54":1,"55":2,"56":4,"58":1,"60":1,"61":1,"62":1,"63":3,"65":2,"66":2,"68":1,"70":1,"73":1,"74":2,"75":1,"76":5}}],["a",{"0":{"68":1},"2":{"3":3,"4":3,"5":1,"7":4,"8":1,"9":6,"10":7,"11":5,"12":4,"13":3,"14":5,"15":1,"16":6,"17":20,"18":6,"19":4,"20":2,"21":5,"22":3,"23":2,"26":1,"27":5,"28":3,"29":3,"31":1,"32":7,"34":3,"36":3,"38":2,"39":6,"40":7,"41":1,"42":7,"43":2,"44":6,"45":4,"47":5,"48":2,"49":1,"50":7,"51":1,"52":2,"53":1,"55":1,"56":3,"57":1,"58":3,"60":1,"61":3,"62":1,"63":1,"64":1,"65":2,"66":7,"67":4,"68":2,"69":5,"70":4,"71":5,"73":12,"74":2,"75":10,"76":10}}],["attributes",{"2":{"40":6}}],["attribute",{"2":{"24":1,"30":1,"32":1}}],["at",{"2":{"2":1,"12":1,"17":1,"32":1,"39":1,"40":1,"44":1,"52":1,"56":1,"62":1,"66":1,"71":2,"76":1,"77":1}}],["tbody",{"2":{"70":4,"71":5,"76":4}}],["td",{"2":{"69":8,"70":8,"71":8,"76":20}}],["tutorial",{"2":{"53":1}}],["turns",{"2":{"71":1}}],["turn",{"2":{"41":1}}],["typing",{"2":{"75":1}}],["typical",{"2":{"36":1}}],["typeof",{"2":{"60":4}}],["typescript",{"2":{"76":1}}],["types",{"0":{"55":1},"1":{"56":1,"57":1,"58":1,"59":1,"60":1,"61":1,"62":1,"63":1,"64":1},"2":{"3":1,"54":1,"55":4,"56":1,"60":2,"63":1,"66":1,"71":1,"76":1}}],["type",{"0":{"66":1},"2":{"3":2,"4":1,"9":1,"13":4,"16":1,"17":10,"24":2,"25":2,"29":1,"31":1,"32":1,"35":1,"37":1,"56":7,"57":1,"58":6,"60":1,"63":3,"64":1,"66":5,"69":1,"70":3,"71":9,"73":1,"74":1,"75":2,"76":4}}],["title",{"2":{"54":1}}],["time",{"2":{"14":1,"38":1,"39":1,"61":1,"66":1}}],["tip",{"2":{"14":1}}],["teaches",{"2":{"53":1}}],["tests",{"2":{"40":1}}],["technically",{"2":{"39":1}}],["tell",{"2":{"40":3}}],["telling",{"2":{"32":1}}],["tells",{"2":{"4":1,"40":1,"41":1,"44":1}}],["template",{"2":{"22":1,"36":6,"41":1}}],["temperature",{"2":{"3":1,"18":1,"19":2,"21":1}}],["terminal",{"2":{"22":1}}],["terms",{"2":{"16":1}}],["ternary",{"0":{"8":1},"2":{"8":1,"9":2,"13":1,"18":1,"54":1}}],["text",{"2":{"14":1,"21":4}}],["treated",{"2":{"75":1}}],["treating",{"2":{"3":1}}],["tr",{"2":{"69":4,"70":4,"71":4,"76":8}}],["traditional",{"2":{"54":1}}],["translate",{"2":{"63":1}}],["translated",{"2":{"50":1,"60":1}}],["transpiled",{"2":{"49":1}}],["transform",{"2":{"40":2,"42":2,"50":1}}],["track",{"2":{"15":1}}],["true",{"2":{"37":2}}],["trusting",{"2":{"4":1}}],["trigger",{"2":{"32":1}}],["trim",{"2":{"19":1,"21":3}}],["try",{"2":{"12":1,"30":1,"52":1,"56":2,"59":1,"71":1}}],["two",{"2":{"3":1,"11":1,"13":2,"62":1,"64":2,"66":2,"71":1}}],["t",{"0":{"4":1,"66":1},"2":{"3":5,"4":1,"7":1,"9":2,"11":2,"13":3,"14":2,"15":2,"16":1,"26":3,"27":1,"29":3,"30":1,"31":1,"32":2,"38":1,"39":1,"40":2,"42":2,"44":1,"46":1,"50":1,"54":1,"55":1,"59":2,"61":3,"63":1,"64":1,"66":11,"67":13,"69":3,"70":8,"71":1,"72":4,"74":2,"75":1,"76":14}}],["taste",{"2":{"68":1}}],["talk",{"2":{"26":1}}],["table",{"2":{"69":2,"70":4,"71":4,"76":5}}],["tabs",{"2":{"50":1}}],["tab",{"2":{"22":1,"36":1,"37":1,"45":1,"48":2,"50":4}}],["take",{"2":{"11":2,"12":1,"29":1,"31":1,"36":1,"43":1,"62":1,"76":1}}],["takes",{"2":{"3":1,"11":2,"12":1,"13":2,"16":1,"17":1,"21":1,"24":1,"29":1}}],["targets",{"2":{"40":2,"42":2,"44":1}}],["target",{"2":{"3":5,"4":3,"15":2,"40":2,"41":3,"42":2,"44":2,"49":1}}],["totally",{"2":{"76":1}}],["total",{"2":{"70":6,"71":6,"76":6}}],["toemoji",{"2":{"68":3,"69":2,"76":5}}],["together",{"2":{"58":1}}],["too",{"2":{"52":1}}],["tools",{"2":{"52":1}}],["tool",{"2":{"43":1,"49":1}}],["toprice",{"2":{"66":5,"67":8,"68":3,"69":2,"70":2,"71":2,"76":9}}],["topics",{"0":{"54":1},"2":{"54":1}}],["top",{"2":{"39":1,"56":1}}],["tostringwithradix",{"2":{"12":1}}],["tofixedwithprecision",{"2":{"10":4,"11":5,"15":2,"16":2,"17":6,"18":4,"21":6,"69":2,"70":2,"71":2,"74":1,"76":2}}],["to",{"0":{"38":1},"1":{"39":1,"40":1,"41":1,"42":1,"43":1,"44":1,"45":1,"46":1,"47":1,"48":1,"49":1,"50":1},"2":{"3":4,"4":4,"5":6,"6":1,"7":3,"8":1,"9":1,"10":1,"11":3,"12":6,"13":2,"14":7,"15":1,"16":6,"17":2,"18":7,"19":2,"20":2,"21":4,"22":7,"23":1,"24":3,"25":2,"26":4,"27":3,"28":8,"29":5,"30":2,"31":2,"32":6,"34":2,"35":5,"36":2,"37":2,"38":3,"39":4,"40":15,"41":8,"42":12,"43":1,"44":4,"45":6,"46":1,"47":5,"49":6,"50":5,"52":3,"53":5,"54":3,"55":2,"57":2,"58":8,"59":1,"60":2,"61":13,"62":3,"63":3,"64":3,"65":4,"66":9,"67":7,"68":4,"69":2,"70":3,"71":12,"72":3,"73":5,"74":5,"75":7,"76":15}}],["throughout",{"2":{"53":1}}],["through",{"2":{"22":1}}],["throw",{"2":{"15":1,"20":1}}],["though",{"2":{"17":1}}],["those",{"2":{"3":1,"20":1,"40":4,"42":5}}],["think",{"2":{"12":1,"39":1}}],["things",{"2":{"49":1,"52":1}}],["thing",{"2":{"4":1,"55":1,"74":1}}],["this",{"2":{"4":1,"7":2,"9":1,"10":1,"11":2,"12":1,"13":1,"14":2,"15":1,"16":6,"17":2,"18":2,"19":1,"21":1,"22":2,"25":1,"27":1,"28":1,"29":1,"31":1,"32":5,"36":1,"37":1,"39":1,"40":2,"41":1,"42":4,"43":1,"44":2,"45":2,"46":1,"47":1,"50":2,"51":1,"52":1,"53":1,"54":1,"55":1,"56":4,"58":2,"59":1,"61":1,"64":2,"66":4,"67":1,"69":2,"70":1,"71":4,"73":2,"76":6}}],["than",{"2":{"4":2,"17":1,"19":1,"21":1,"39":1,"55":1}}],["that",{"0":{"4":1},"2":{"3":1,"4":4,"5":1,"6":1,"7":1,"8":1,"10":1,"11":1,"12":3,"13":1,"14":4,"15":1,"16":8,"17":3,"18":2,"19":1,"20":3,"21":1,"23":2,"24":1,"25":1,"26":1,"27":2,"28":2,"29":1,"31":1,"32":3,"35":1,"36":1,"37":1,"38":1,"39":1,"40":5,"41":2,"42":4,"44":2,"45":1,"47":4,"48":1,"49":1,"50":2,"53":1,"54":2,"55":2,"56":4,"58":2,"59":4,"61":1,"64":1,"66":1,"67":2,"69":1,"70":1,"71":2,"73":2,"74":1,"75":1,"76":3}}],["their",{"2":{"47":1,"56":1,"76":1}}],["these",{"2":{"22":1,"38":1,"42":2}}],["them",{"2":{"13":1,"25":1,"36":1,"38":1,"59":1}}],["therefore",{"2":{"39":1,"52":1,"58":1}}],["there",{"2":{"7":2,"16":1,"23":1,"24":1,"28":1,"30":1,"34":1,"35":1,"44":1,"52":1,"58":1,"66":1,"68":1,"70":1,"71":1}}],["then",{"2":{"5":1,"11":1,"21":1,"28":1,"36":1,"39":1,"42":1,"61":1,"76":2}}],["they",{"0":{"60":1},"2":{"4":2,"6":1,"13":1,"32":1,"63":1,"64":1,"67":1,"76":2}}],["the",{"0":{"24":1,"26":1,"27":1,"28":1,"36":1,"61":1},"2":{"2":1,"3":8,"4":8,"5":5,"6":3,"7":4,"8":2,"9":4,"10":6,"11":6,"12":4,"13":7,"14":14,"15":4,"16":14,"17":10,"18":6,"19":4,"20":3,"21":2,"22":12,"23":5,"24":5,"25":3,"26":11,"27":6,"28":3,"29":3,"30":5,"31":2,"32":9,"35":3,"36":3,"37":4,"38":1,"39":17,"40":30,"41":9,"42":24,"43":2,"44":12,"45":13,"46":6,"47":4,"48":4,"49":11,"50":8,"53":5,"54":4,"55":4,"56":14,"57":4,"58":7,"59":2,"60":2,"61":16,"62":8,"63":3,"64":1,"65":10,"66":13,"67":5,"68":3,"69":4,"70":9,"71":14,"72":3,"73":11,"74":5,"75":8,"76":12,"77":1}}],["old",{"2":{"73":1}}],["oops",{"2":{"73":1}}],["own",{"2":{"47":3,"50":1}}],["overkill",{"2":{"47":1}}],["over",{"2":{"17":1,"20":1,"22":1,"25":1,"28":1,"37":1,"56":1,"65":1,"73":1,"76":1}}],["overview",{"0":{"13":1,"20":1,"31":1,"49":1,"63":1,"75":1}}],["outside",{"2":{"76":1}}],["output",{"2":{"40":2,"41":3,"42":2,"45":4,"56":2,"61":1,"63":2,"71":1}}],["outputs",{"2":{"12":1}}],["out",{"2":{"16":1,"30":1,"32":1,"56":1,"59":1}}],["ourselves",{"2":{"4":1}}],["our",{"2":{"3":1,"11":1,"18":1,"24":1,"27":1,"29":1,"30":1,"36":1,"38":1,"40":1,"41":1,"61":1}}],["occur",{"2":{"12":1}}],["ocamllabs",{"2":{"37":2}}],["ocaml",{"2":{"4":1,"9":3,"11":2,"12":1,"13":1,"14":2,"16":1,"18":1,"23":1,"27":1,"28":1,"31":1,"32":3,"34":1,"35":3,"37":6,"38":1,"41":1,"43":1,"49":1,"52":1,"53":2,"54":2,"55":3,"56":1,"57":1,"58":1,"59":1,"60":1,"61":1,"63":2,"66":2,"67":2,"71":2,"73":1}}],["opam",{"0":{"35":1},"2":{"35":11,"39":1,"41":1,"47":1,"50":1}}],["operations",{"2":{"28":1}}],["operators",{"0":{"58":1,"59":1},"2":{"12":1,"58":1,"59":3,"63":3}}],["operator",{"0":{"28":1},"2":{"3":1,"5":1,"6":1,"12":1,"13":2,"28":1,"31":1,"54":1}}],["opened",{"2":{"65":1}}],["open",{"2":{"22":2,"36":1,"37":3,"41":2,"45":3,"73":1}}],["optimization",{"2":{"56":1}}],["optional",{"2":{"73":1}}],["option",{"0":{"15":1,"17":1},"1":{"16":1,"17":1,"18":1,"19":1,"20":1,"21":1},"2":{"11":1,"16":2,"17":27,"18":4,"20":4,"21":7,"26":2,"31":1,"41":1,"44":1,"45":1,"54":5}}],["opt",{"0":{"16":1},"2":{"16":5,"17":5,"18":4,"19":2,"21":6}}],["otherwise",{"2":{"26":1}}],["other",{"2":{"4":1,"17":1,"34":1,"36":1,"38":1,"39":1,"59":1,"66":1,"68":1}}],["object",{"2":{"3":1,"13":1,"14":2,"24":1,"29":2,"54":1,"71":1}}],["objects",{"2":{"3":2,"4":1,"13":2,"14":1,"31":1,"61":1}}],["orderitem",{"2":{"74":1,"76":5}}],["order",{"0":{"65":1,"70":1},"1":{"66":1,"67":1,"68":1,"69":1,"70":1,"71":1,"72":1,"73":1,"74":1,"75":1,"76":1},"2":{"39":1,"65":5,"66":3,"69":1,"70":10,"71":9,"72":9,"73":1,"74":2,"76":10}}],["orders",{"2":{"29":1}}],["or",{"2":{"3":1,"9":1,"31":1,"32":1,"36":1,"40":5,"42":2,"46":1,"54":1,"66":2,"68":1,"75":2,"76":2}}],["often",{"2":{"75":1}}],["off",{"2":{"16":1,"71":1}}],["of",{"0":{"16":1,"44":1},"2":{"3":2,"4":2,"5":5,"6":5,"7":8,"8":4,"9":5,"10":8,"11":10,"13":6,"14":5,"15":4,"16":9,"17":18,"18":7,"19":4,"20":1,"21":7,"22":3,"23":2,"24":1,"26":1,"27":2,"28":2,"29":3,"30":1,"31":1,"32":3,"34":1,"38":1,"39":9,"40":7,"41":4,"42":6,"43":2,"44":2,"45":1,"46":1,"48":1,"49":4,"50":4,"53":1,"54":2,"56":3,"57":1,"58":7,"59":1,"60":2,"62":3,"64":2,"65":2,"66":5,"67":1,"69":2,"70":4,"71":4,"73":3,"74":1,"75":3,"76":8}}],["only",{"2":{"11":1,"13":1,"16":1,"17":1,"24":1,"41":1,"42":1,"44":1,"45":1,"47":1,"63":1,"66":1,"71":1,"74":1}}],["on",{"2":{"5":1,"9":1,"14":1,"19":1,"35":1,"38":1,"41":1,"45":1,"49":1,"54":1,"56":1,"61":2,"63":2,"65":1,"70":1,"73":1,"75":1}}],["onclick=",{"2":{"27":4,"29":4,"61":8,"64":4}}],["once",{"2":{"3":1,"52":1,"63":1}}],["onchange",{"2":{"3":1,"4":1,"12":1,"14":5}}],["onchange=",{"2":{"3":2,"4":2,"12":2,"14":2,"15":2}}],["ones",{"2":{"40":1,"41":1}}],["one",{"2":{"3":1,"4":1,"5":1,"10":2,"11":2,"13":2,"23":1,"40":1,"44":1,"58":1,"66":1,"68":1,"73":1,"75":1}}],["check",{"2":{"77":1}}],["chosen",{"2":{"76":1}}],["child",{"2":{"73":2}}],["children",{"2":{"71":1,"76":2}}],["character",{"2":{"50":1}}],["characters",{"2":{"16":2,"32":2,"50":3,"71":2}}],["changing",{"2":{"12":1,"14":1,"66":1,"67":1}}],["changed",{"2":{"41":1}}],["changes",{"2":{"15":1}}],["change",{"2":{"5":1,"41":1,"44":1,"45":1,"58":2,"66":1,"67":1,"71":1,"75":2}}],["chapter",{"2":{"11":1,"14":1,"15":1,"21":1,"26":1,"29":1,"32":2,"50":1,"52":1,"54":1,"61":1,"62":1,"64":1,"65":1,"73":1,"76":1}}],["chapters",{"0":{"54":1},"2":{"10":1,"29":1,"46":1,"47":1}}],["chaining",{"2":{"20":1,"28":1,"31":1,"54":1}}],["chained",{"2":{"11":1}}],["chain",{"2":{"11":2,"17":2}}],["customary",{"2":{"66":1}}],["currency",{"2":{"74":1,"76":10}}],["currently",{"2":{"45":1}}],["current",{"2":{"40":2}}],["curl",{"2":{"35":2}}],["center",{"2":{"61":4}}],["celsiusconverter",{"2":{"3":1,"15":1,"16":2}}],["celsius",{"0":{"3":1,"15":1},"1":{"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"14":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1},"2":{"3":7,"4":6,"5":4,"6":2,"7":4,"8":4,"9":4,"10":4,"11":4,"15":10,"16":4,"17":4,"18":6,"21":8,"26":1,"48":1,"50":2,"54":3}}],["cd",{"2":{"36":2}}],["c",{"2":{"35":2,"42":2,"44":3,"71":2}}],["css",{"2":{"29":1,"73":1}}],["click",{"2":{"61":1}}],["cleanup",{"0":{"46":1}}],["clear",{"2":{"6":1,"55":1}}],["clj",{"2":{"39":2,"40":1,"42":1}}],["clone",{"2":{"36":2}}],["class",{"2":{"54":1}}],["classes",{"2":{"29":1}}],["classic",{"2":{"22":1}}],["creating",{"2":{"27":1,"50":1,"65":1}}],["createelement",{"2":{"50":1}}],["created",{"2":{"29":2,"45":1,"47":1,"62":1}}],["creates",{"2":{"14":1,"71":1}}],["create",{"2":{"3":1,"11":1,"18":1,"27":1,"29":1,"31":1,"42":1,"45":1,"47":1,"48":1,"50":1,"70":1,"73":1,"76":2}}],["critical",{"2":{"16":1}}],["crash",{"2":{"7":1,"15":1}}],["cafe",{"2":{"65":1,"66":1,"76":1}}],["caveat",{"2":{"40":2,"42":2}}],["came",{"2":{"32":1}}],["case",{"2":{"10":1,"16":5,"19":1,"20":1,"32":3,"40":2,"42":2,"44":1,"67":1}}],["cases",{"2":{"9":2}}],["catching",{"2":{"7":1,"11":1}}],["catch",{"0":{"7":1},"2":{"7":1,"13":1,"76":1}}],["call",{"2":{"21":1,"71":5,"75":1}}],["calls",{"2":{"17":1,"22":1,"25":1,"28":1,"31":1,"50":2,"71":1}}],["calling",{"2":{"14":1,"29":1}}],["callback",{"2":{"4":1,"12":1,"14":2,"30":1,"73":1}}],["called",{"2":{"3":1,"13":1,"61":1,"65":1,"66":1,"68":1,"76":2}}],["cannedfood",{"2":{"76":1}}],["cannot",{"2":{"58":1}}],["can",{"2":{"3":2,"4":1,"6":1,"7":2,"8":1,"9":3,"10":1,"11":2,"13":5,"14":3,"15":1,"16":2,"17":2,"18":1,"20":2,"21":3,"22":3,"23":1,"28":1,"31":1,"32":1,"34":1,"39":2,"45":1,"46":1,"49":1,"50":2,"54":1,"56":3,"58":1,"59":1,"61":2,"64":2,"65":3,"66":1,"67":1,"68":2,"71":1,"73":2,"76":3}}],["cogito",{"2":{"77":1}}],["collections",{"2":{"71":1}}],["cold",{"2":{"21":1}}],["cold🥶|js",{"2":{"21":4}}],["cold🥶",{"2":{"19":1}}],["corner",{"2":{"56":1}}],["correct",{"2":{"53":1}}],["correctly",{"2":{"4":1,"8":1,"14":1,"19":1}}],["copied",{"2":{"45":1,"65":2}}],["covers",{"2":{"60":1}}],["cover",{"2":{"39":1,"54":2}}],["covered",{"2":{"31":1,"54":1}}],["coffee",{"2":{"36":1}}],["course",{"2":{"76":1}}],["counter",{"0":{"22":1,"27":1,"42":1},"1":{"23":1,"24":1,"25":1,"26":1,"27":1,"28":1,"29":1,"30":1,"31":1,"32":1},"2":{"22":1,"27":10,"28":4,"29":3,"30":1,"32":7,"42":11,"44":2,"45":5,"48":2,"50":6,"54":1,"61":14,"62":1,"64":5}}],["couple",{"2":{"17":1}}],["couldn",{"2":{"26":2,"42":2,"61":2,"72":2}}],["could",{"2":{"3":1,"7":1,"9":1,"18":1,"67":1}}],["code=bgv0igzvbya9idqyowpszxqgymfyid0gndiumtsksnmubg9nkgzvbyk7ckpzlmxvzyhiyxipow",{"2":{"57":1}}],["code",{"0":{"37":1,"57":1},"2":{"7":1,"9":1,"14":1,"18":1,"21":1,"24":1,"32":1,"34":2,"37":3,"40":2,"42":2,"49":1,"50":2,"56":2,"57":3,"61":1,"63":2,"64":1,"66":2,"70":1,"71":1,"75":2,"76":1}}],["concepts",{"2":{"52":1}}],["concerned",{"2":{"41":1}}],["concatenate",{"2":{"13":1}}],["concatenation",{"0":{"6":1},"2":{"6":1}}],["convention",{"2":{"66":1,"75":1}}],["convenient",{"2":{"39":1}}],["conversions",{"2":{"58":1}}],["convertcelsius",{"2":{"11":1}}],["converted",{"2":{"7":1,"10":1,"16":1,"18":1}}],["converter",{"0":{"3":1,"15":1},"1":{"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"14":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1},"2":{"18":1,"26":1,"48":1,"50":2,"54":2}}],["convert",{"2":{"3":2,"4":2,"5":6,"6":2,"7":4,"8":2,"9":2,"10":2,"11":5,"15":4,"16":2,"17":6,"18":5,"21":6,"25":1,"58":2,"62":1,"75":1}}],["converts",{"2":{"3":1,"12":1,"14":1,"28":2,"54":1}}],["config",{"2":{"50":1}}],["confirmation",{"0":{"65":1},"1":{"66":1,"67":1,"68":1,"69":1,"70":1,"71":1,"72":1,"73":1,"74":1,"75":1,"76":1},"2":{"65":5,"70":1,"71":2,"72":4,"73":1,"76":2}}],["confirm",{"2":{"12":1}}],["confuse",{"2":{"32":1}}],["congratulations",{"2":{"29":1}}],["content",{"0":{"77":1},"2":{"77":1}}],["contents",{"2":{"27":1}}],["containing",{"2":{"69":1}}],["contains",{"2":{"12":2,"14":1,"42":1,"47":1,"61":1,"75":1}}],["contain",{"2":{"3":1,"4":1,"42":1,"49":2,"66":1,"71":1}}],["constants",{"2":{"56":1}}],["constructors",{"2":{"66":2,"67":2,"75":2}}],["constructor",{"2":{"26":1,"66":1,"67":1,"74":1,"76":1}}],["construct",{"2":{"9":1,"18":1}}],["consider",{"2":{"36":1}}],["consisting",{"2":{"17":1}}],["consists",{"2":{"3":1,"40":1}}],["console",{"2":{"26":2,"42":2,"56":6,"63":1,"71":3,"72":2,"73":1}}],["conditionals",{"2":{"18":1,"20":1}}],["conditions",{"2":{"18":1}}],["condition",{"2":{"9":2}}],["combine",{"2":{"67":1}}],["come",{"2":{"54":1}}],["comes",{"2":{"23":1}}],["com",{"2":{"35":2,"36":2}}],["coming",{"2":{"14":1}}],["commissioned",{"2":{"65":1}}],["communicate",{"2":{"40":2}}],["command",{"2":{"36":1,"41":1,"44":2,"45":1,"49":1}}],["commands",{"0":{"2":1},"2":{"49":1}}],["commenting",{"2":{"32":1}}],["comment",{"2":{"16":1,"30":1}}],["commonly",{"2":{"49":1}}],["commonjs",{"2":{"40":2,"42":2}}],["common",{"2":{"9":1,"32":1,"38":1,"43":1,"50":1,"73":1}}],["compare",{"2":{"58":1}}],["comparison",{"0":{"58":1},"2":{"18":1,"58":1,"63":1}}],["complement",{"2":{"53":1}}],["completes",{"2":{"35":1}}],["complete",{"2":{"18":1,"60":1}}],["completely",{"2":{"15":1,"68":1}}],["complex",{"2":{"9":1,"29":1}}],["complaint",{"2":{"18":1}}],["complain",{"2":{"3":1}}],["compiling",{"2":{"63":1}}],["compile",{"2":{"58":1,"59":3,"66":1,"74":1}}],["compiled",{"2":{"40":2,"42":2,"63":2,"71":1}}],["compiles",{"2":{"12":1}}],["compiler",{"2":{"3":2,"4":1,"15":1,"16":1,"20":1,"22":1,"32":1,"40":2,"42":2,"48":1,"56":1,"58":1,"71":1}}],["compilation",{"2":{"9":1,"32":3,"50":1}}],["components",{"0":{"23":1},"2":{"22":1,"23":1,"29":1,"31":1,"38":2,"40":2,"61":1,"63":1,"73":1,"76":1}}],["component",{"0":{"26":1,"27":1,"70":1},"2":{"3":2,"4":2,"11":1,"15":2,"18":1,"22":5,"23":1,"24":2,"26":2,"27":7,"29":1,"30":1,"32":4,"42":3,"45":1,"54":1,"61":5,"62":1,"64":3,"69":5,"70":4,"72":3,"73":1,"74":3,"75":1,"76":6}}],["sweet",{"2":{"56":1}}],["switch",{"0":{"7":1,"67":1,"68":1},"2":{"7":5,"8":3,"9":3,"10":2,"11":4,"13":1,"15":3,"16":6,"17":6,"18":5,"20":2,"21":7,"26":3,"30":1,"32":2,"42":2,"54":1,"61":2,"66":3,"67":7,"68":2,"72":2,"75":2}}],["slowly",{"2":{"53":1}}],["src",{"2":{"42":4,"44":1,"45":11,"48":1,"50":3,"65":2,"70":1,"71":2,"72":2,"76":1}}],["scribendo",{"2":{"77":1}}],["scripts",{"2":{"46":1}}],["script",{"2":{"41":1,"43":1,"45":1}}],["scripting",{"2":{"39":1}}],["scope",{"2":{"14":2}}],["snippets",{"0":{"57":1},"2":{"57":1,"63":1}}],["snippet",{"2":{"37":1,"57":1}}],["specify",{"2":{"40":2,"42":2}}],["specified",{"2":{"40":2,"42":2}}],["specifies",{"2":{"39":1}}],["specifically",{"2":{"41":1}}],["specific",{"2":{"7":1,"14":1,"40":2,"42":2}}],["spend",{"2":{"38":1}}],["span",{"2":{"29":4,"61":8,"64":4,"76":1}}],["spaces",{"2":{"48":1,"50":2}}],["space",{"2":{"19":1,"21":1,"50":1}}],["systems",{"2":{"40":4,"41":1,"42":4}}],["system",{"2":{"18":1,"34":1,"38":1,"49":1}}],["syntax",{"0":{"68":1},"2":{"9":1,"10":1,"13":1,"37":1,"39":1,"54":2,"67":1,"68":3,"75":1}}],["small",{"2":{"17":1,"52":1}}],["share",{"2":{"57":1,"63":1}}],["sharing",{"0":{"57":1}}],["shame",{"2":{"17":1}}],["shell",{"2":{"35":2}}],["sh",{"2":{"35":4}}],["ship",{"2":{"32":1}}],["short",{"2":{"43":1,"63":1}}],["shorter",{"2":{"11":1,"68":1}}],["shorthand",{"2":{"3":1,"9":1,"13":1}}],["showed",{"2":{"65":1}}],["shows",{"2":{"36":1,"56":2}}],["showing",{"2":{"19":1}}],["show",{"2":{"8":1,"15":1,"56":1,"63":1}}],["shoulder",{"2":{"76":1}}],["shouldn",{"2":{"76":1}}],["should",{"2":{"6":1,"27":1,"36":1,"37":2,"39":1,"40":2,"42":1,"45":1,"49":1,"50":1,"53":2,"56":2,"66":1,"67":1,"73":3,"76":2}}],["store",{"2":{"57":1}}],["stop",{"2":{"50":2}}],["steps",{"2":{"48":1}}],["stuff",{"2":{"41":3}}],["studio",{"0":{"37":1},"2":{"34":1,"37":2}}],["styled",{"2":{"61":1,"73":1}}],["styles",{"2":{"61":5}}],["style",{"2":{"29":3,"31":1,"61":1}}],["style=styles",{"2":{"61":8}}],["style=",{"2":{"29":2,"61":2,"64":2}}],["styling",{"0":{"29":1},"2":{"29":1}}],["structure",{"0":{"47":1},"2":{"47":1,"65":1}}],["structured",{"2":{"39":1}}],["strive",{"2":{"67":1}}],["strictly",{"2":{"44":1}}],["strict",{"2":{"32":2}}],["strings",{"0":{"25":1},"2":{"13":1,"14":1,"22":1,"25":1}}],["string",{"0":{"6":1,"16":1,"25":1},"2":{"3":4,"4":9,"5":11,"6":9,"7":16,"8":8,"9":8,"10":6,"11":13,"14":2,"15":8,"16":8,"17":8,"18":4,"19":4,"21":10,"22":3,"25":2,"27":8,"28":8,"29":8,"57":1,"61":16,"64":8,"69":4,"70":4,"71":6,"72":2,"73":2,"74":1,"76":8}}],["straightforward",{"2":{"17":1}}],["still",{"2":{"17":2,"47":1}}],["states",{"2":{"76":1}}],["statements",{"2":{"32":1}}],["stable",{"2":{"40":2,"42":2}}],["stanzas",{"2":{"39":1,"40":3}}],["stanza",{"0":{"41":1},"2":{"39":2,"40":8,"41":2,"42":5,"46":1,"47":2,"48":1,"49":1}}],["standard",{"2":{"17":1,"40":2,"62":1}}],["started",{"2":{"57":1}}],["starter",{"0":{"36":1},"2":{"22":2,"39":1}}],["start",{"2":{"22":3,"26":2,"40":2,"42":2,"61":2,"65":1,"72":2}}],["starting",{"2":{"16":1}}],["stale",{"2":{"14":1}}],["sandwich|",{"2":{"72":2}}],["sandwiches",{"2":{"67":1}}],["sandwich",{"2":{"66":7,"67":4,"68":4,"76":6}}],["save",{"2":{"61":2,"75":1}}],["said",{"2":{"53":1}}],["safe",{"2":{"32":1}}],["safer",{"2":{"13":1}}],["says",{"2":{"58":1}}],["saying",{"2":{"40":2}}],["say",{"2":{"24":1,"41":1}}],["same",{"2":{"9":2,"13":2,"14":1,"27":1,"40":1,"47":1,"54":1,"58":2,"63":2,"65":1,"67":1,"71":1,"74":1}}],["sum",{"2":{"70":1}}],["summary",{"2":{"54":1}}],["superior",{"2":{"67":1}}],["support",{"2":{"34":1,"38":1,"54":1}}],["supply",{"2":{"13":1}}],["sustainable",{"2":{"29":1}}],["submodule",{"2":{"76":1}}],["subfolders",{"2":{"40":2}}],["subsystem",{"2":{"34":1}}],["substitute",{"2":{"21":1}}],["subtracts",{"2":{"12":1,"14":1}}],["surprised",{"2":{"59":1}}],["sure",{"2":{"16":1,"36":1,"42":1}}],["surrounded",{"2":{"4":1}}],["succinctly",{"2":{"26":1}}],["succeeded",{"2":{"35":1}}],["succeeds",{"2":{"16":1}}],["successfully",{"2":{"16":1,"36":1}}],["success",{"2":{"16":2}}],["such",{"2":{"13":1,"32":1,"34":1}}],["signed",{"2":{"64":1}}],["signature",{"2":{"16":1,"17":2,"73":1}}],["since",{"2":{"38":1,"67":1,"76":1}}],["single",{"2":{"11":2,"12":1,"14":1,"17":1,"23":1,"27":1,"44":1,"47":2,"54":1,"69":3,"70":2,"75":1}}],["sides",{"2":{"63":1}}],["side",{"2":{"22":1,"56":1}}],["simplify",{"2":{"67":1}}],["simplest",{"2":{"22":1,"35":1}}],["simple",{"2":{"9":1,"44":1}}],["similar",{"2":{"16":1,"17":1,"47":1,"76":1}}],["silent",{"2":{"12":1}}],["site",{"2":{"2":1}}],["solid",{"2":{"61":2}}],["solutions",{"0":{"14":1,"21":1,"32":1,"50":1,"64":1,"76":1}}],["somewhat",{"2":{"23":1}}],["somewhere",{"2":{"16":1}}],["some",{"2":{"16":7,"17":7,"18":6,"19":1,"21":19,"22":1,"26":5,"28":1,"31":1,"36":1,"42":2,"61":2,"72":2}}],["something",{"2":{"15":1,"35":1,"50":1,"56":1,"64":1,"76":2}}],["sources",{"2":{"40":2,"42":2}}],["source",{"2":{"14":1,"21":1,"27":1,"32":1,"50":1,"56":1,"57":1,"64":1,"76":1}}],["so",{"2":{"6":2,"9":1,"12":1,"14":1,"16":1,"27":1,"28":1,"43":1,"45":1,"47":1,"58":1,"67":1,"68":1,"71":1,"73":2,"76":1}}],["s",{"2":{"3":2,"4":2,"5":1,"6":1,"7":1,"9":2,"10":2,"11":1,"12":2,"14":1,"16":1,"17":2,"22":1,"23":1,"24":1,"27":2,"28":1,"29":1,"32":2,"35":1,"36":1,"39":6,"40":4,"42":2,"43":1,"44":3,"45":1,"47":2,"48":2,"50":2,"54":1,"55":1,"56":1,"57":1,"59":1,"60":1,"61":2,"62":1,"63":1,"64":1,"67":1,"68":3,"70":2,"73":2,"74":1,"75":3,"76":3}}],["separator",{"2":{"50":2}}],["separate",{"2":{"38":1,"47":1,"48":1,"50":1,"59":1,"63":1}}],["sense",{"2":{"38":1,"39":1,"66":1,"71":1}}],["several",{"2":{"38":1}}],["sets",{"2":{"44":1}}],["set",{"2":{"39":2,"73":2,"76":1}}],["settings",{"2":{"37":1}}],["setcounter",{"2":{"27":6,"29":4,"61":10,"64":6}}],["setcelsius",{"2":{"3":4,"4":4,"12":2,"14":4,"15":4}}],["search",{"2":{"37":1}}],["section",{"2":{"31":1,"46":1}}],["second",{"2":{"3":1,"17":1}}],["sequence",{"2":{"28":1}}],["seen",{"2":{"59":1}}],["seems",{"2":{"47":1}}],["see",{"2":{"10":1,"11":1,"14":2,"17":1,"18":1,"22":2,"25":1,"29":1,"32":1,"36":1,"37":1,"40":1,"41":2,"56":5,"71":2,"72":1,"73":3,"76":1}}],["seeing",{"2":{"6":1,"56":1}}],["serves",{"2":{"67":1}}],["served",{"2":{"45":1}}],["servegit",{"2":{"36":1}}],["server",{"2":{"22":1,"42":1,"45":3}}],["servemake",{"2":{"2":1,"45":1}}],["serve",{"2":{"2":2,"22":1,"36":2,"41":3,"42":7,"44":13,"45":11,"47":1,"48":1,"50":1,"72":1}}],["breakdown",{"2":{"44":1}}],["breaking",{"2":{"17":1}}],["browser",{"2":{"22":1,"36":1,"41":1,"45":1,"73":1}}],["browsing",{"2":{"17":1}}],["branches",{"2":{"9":1,"13":1,"67":1}}],["branch",{"2":{"7":1,"15":2,"16":4,"18":1,"19":1,"21":1,"30":1,"32":1,"66":1}}],["braces",{"2":{"4":2}}],["bind",{"2":{"73":1}}],["bindings",{"2":{"75":1}}],["binding",{"2":{"70":2,"73":1,"75":1}}],["binary",{"2":{"12":1,"14":1}}],["bit",{"2":{"8":1,"11":1,"14":1,"26":1,"29":1,"75":1}}],["b",{"2":{"9":2,"17":8,"71":2,"73":4}}],["blank",{"2":{"8":1}}],["burgers",{"2":{"67":1}}],["burger",{"2":{"66":6,"67":4,"68":4,"72":2,"76":6}}],["built",{"2":{"38":1}}],["building",{"2":{"41":2}}],["build",{"2":{"18":1,"22":1,"36":3,"38":1,"40":5,"41":6,"42":4,"43":1,"45":4,"47":2,"49":3,"53":1,"65":2}}],["bug",{"2":{"7":1,"12":1}}],["button",{"2":{"27":8,"29":8,"30":1,"61":24,"62":2,"64":8}}],["but",{"2":{"3":1,"8":1,"9":2,"11":2,"12":1,"16":1,"18":2,"22":1,"24":1,"30":1,"35":1,"39":1,"40":2,"41":1,"44":1,"45":1,"47":1,"54":1,"56":2,"58":2,"63":2,"65":1,"71":3,"73":2,"76":2}}],["bad",{"2":{"67":1}}],["baz",{"2":{"62":4}}],["bar",{"2":{"55":8,"56":14,"58":8,"60":5}}],["back",{"2":{"5":1,"18":1,"77":1}}],["bashwebpack",{"2":{"41":1}}],["bashgit",{"2":{"36":1}}],["bash",{"2":{"35":1}}],["bashbash",{"2":{"35":1}}],["bashmake",{"2":{"1":1,"2":1}}],["basics",{"2":{"53":1}}],["basically",{"2":{"32":1,"40":1,"44":1,"45":1}}],["basic",{"0":{"29":1},"2":{"31":1,"73":1}}],["based",{"2":{"5":1,"49":1,"52":1,"73":1}}],["by",{"2":{"4":2,"7":1,"8":1,"11":2,"14":1,"18":1,"19":1,"22":1,"27":1,"29":1,"36":1,"39":2,"40":5,"42":2,"45":2,"48":1,"56":4,"62":2,"64":1,"65":1,"75":2,"76":1}}],["bottom",{"2":{"56":2}}],["bother",{"2":{"18":1}}],["both",{"2":{"9":1,"52":1,"58":1,"60":1,"63":2,"75":1}}],["boilerplate",{"2":{"24":1}}],["boiling",{"2":{"18":1}}],["body",{"2":{"4":1,"68":1,"75":1}}],["book",{"0":{"0":1},"1":{"1":1,"2":1}}],["beautiful",{"2":{"53":1}}],["best",{"2":{"39":1}}],["besides",{"2":{"13":1,"40":1}}],["beverage",{"2":{"36":1}}],["begin",{"2":{"32":1}}],["between",{"2":{"18":1,"55":1}}],["better",{"2":{"15":1,"71":1,"76":1}}],["been",{"2":{"14":1,"38":1,"39":2,"65":1}}],["being",{"2":{"14":1,"23":1,"27":1,"40":2,"42":2,"66":1,"76":1}}],["becomes",{"2":{"11":1}}],["because",{"2":{"3":1,"9":1,"11":1,"15":1,"19":1,"27":1,"39":1,"47":1,"50":1,"52":1,"54":2,"59":1,"61":1,"66":1,"71":1}}],["before",{"2":{"8":1,"40":2,"42":2,"44":1}}],["beware",{"2":{"3":1}}],["be",{"2":{"3":1,"4":2,"7":2,"8":1,"13":1,"14":2,"16":2,"17":2,"21":1,"22":1,"23":1,"28":1,"31":1,"32":2,"37":1,"39":1,"40":2,"41":1,"42":2,"43":1,"44":1,"45":1,"49":1,"50":3,"53":1,"54":1,"56":1,"59":1,"63":1,"64":1,"65":3,"66":2,"67":2,"70":1,"73":1,"74":1,"76":2}}]],"serializationVersion":2}';export{e as default}; diff --git a/assets/chunks/VPLocalSearchBox.004b014b.js b/assets/chunks/VPLocalSearchBox.004b014b.js new file mode 100644 index 00000000..bdbaeefe --- /dev/null +++ b/assets/chunks/VPLocalSearchBox.004b014b.js @@ -0,0 +1,7 @@ +import{X as pt,h as ie,x as Be,ah as kt,ai as Nt,d as It,E as be,aj as et,g as we,ak as Dt,al as _t,y as Ot,am as Rt,j as De,O as de,V as xe,an as Mt,S as Lt,U as Pt,ao as zt,Y as Bt,s as Vt,ap as $t,o as X,b as Wt,k as F,a1 as jt,l as U,aq as Kt,ar as Jt,as as Ut,c as te,n as tt,e as Fe,D as rt,F as nt,a as he,t as ve,at as Ht,p as Gt,m as Qt,au as at,av as qt,a6 as Yt,ac as Zt,_ as Xt}from"./framework.0e8ae64e.js";import{u as er,c as tr}from"./theme.802a1aee.js";const rr={root:()=>pt(()=>import("./@localSearchIndexroot.ae053162.js"),[])};/*! +* tabbable 6.2.0 +* @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE +*/var mt=["input:not([inert])","select:not([inert])","textarea:not([inert])","a[href]:not([inert])","button:not([inert])","[tabindex]:not(slot):not([inert])","audio[controls]:not([inert])","video[controls]:not([inert])",'[contenteditable]:not([contenteditable="false"]):not([inert])',"details>summary:first-of-type:not([inert])","details:not([inert])"],Ae=mt.join(","),yt=typeof Element>"u",se=yt?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector,Ce=!yt&&Element.prototype.getRootNode?function(o){var e;return o==null||(e=o.getRootNode)===null||e===void 0?void 0:e.call(o)}:function(o){return o==null?void 0:o.ownerDocument},Te=function o(e,t){var r;t===void 0&&(t=!0);var a=e==null||(r=e.getAttribute)===null||r===void 0?void 0:r.call(e,"inert"),n=a===""||a==="true",i=n||t&&e&&o(e.parentNode);return i},nr=function(e){var t,r=e==null||(t=e.getAttribute)===null||t===void 0?void 0:t.call(e,"contenteditable");return r===""||r==="true"},gt=function(e,t,r){if(Te(e))return[];var a=Array.prototype.slice.apply(e.querySelectorAll(Ae));return t&&se.call(e,Ae)&&a.unshift(e),a=a.filter(r),a},bt=function o(e,t,r){for(var a=[],n=Array.from(e);n.length;){var i=n.shift();if(!Te(i,!1))if(i.tagName==="SLOT"){var s=i.assignedElements(),u=s.length?s:i.children,l=o(u,!0,r);r.flatten?a.push.apply(a,l):a.push({scopeParent:i,candidates:l})}else{var d=se.call(i,Ae);d&&r.filter(i)&&(t||!e.includes(i))&&a.push(i);var h=i.shadowRoot||typeof r.getShadowRoot=="function"&&r.getShadowRoot(i),v=!Te(h,!1)&&(!r.shadowRootFilter||r.shadowRootFilter(i));if(h&&v){var y=o(h===!0?i.children:h.children,!0,r);r.flatten?a.push.apply(a,y):a.push({scopeParent:i,candidates:y})}else n.unshift.apply(n,i.children)}}return a},wt=function(e){return!isNaN(parseInt(e.getAttribute("tabindex"),10))},oe=function(e){if(!e)throw new Error("No node provided");return e.tabIndex<0&&(/^(AUDIO|VIDEO|DETAILS)$/.test(e.tagName)||nr(e))&&!wt(e)?0:e.tabIndex},ar=function(e,t){var r=oe(e);return r<0&&t&&!wt(e)?0:r},ir=function(e,t){return e.tabIndex===t.tabIndex?e.documentOrder-t.documentOrder:e.tabIndex-t.tabIndex},xt=function(e){return e.tagName==="INPUT"},or=function(e){return xt(e)&&e.type==="hidden"},sr=function(e){var t=e.tagName==="DETAILS"&&Array.prototype.slice.apply(e.children).some(function(r){return r.tagName==="SUMMARY"});return t},ur=function(e,t){for(var r=0;r=0)c=r.activeElement;else{var f=i.tabbableGroups[0],p=f&&f.firstTabbableNode;c=p||d("fallbackFocus")}if(!c)throw new Error("Your focus-trap needs to have at least one focusable element");return c},v=function(){if(i.containerGroups=i.containers.map(function(c){var f=yr(c,n.tabbableOptions),p=gr(c,n.tabbableOptions),N=f.length>0?f[0]:void 0,k=f.length>0?f[f.length-1]:void 0,M=p.find(function(w){return ue(w)}),z=p.slice().reverse().find(function(w){return ue(w)}),m=!!f.find(function(w){return oe(w)>0});return{container:c,tabbableNodes:f,focusableNodes:p,posTabIndexesFound:m,firstTabbableNode:N,lastTabbableNode:k,firstDomTabbableNode:M,lastDomTabbableNode:z,nextTabbableNode:function(B){var G=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0,j=f.indexOf(B);return j<0?G?p.slice(p.indexOf(B)+1).find(function(K){return ue(K)}):p.slice(0,p.indexOf(B)).reverse().find(function(K){return ue(K)}):f[j+(G?1:-1)]}}}),i.tabbableGroups=i.containerGroups.filter(function(c){return c.tabbableNodes.length>0}),i.tabbableGroups.length<=0&&!d("fallbackFocus"))throw new Error("Your focus-trap must have at least one container with at least one tabbable node in it at all times");if(i.containerGroups.find(function(c){return c.posTabIndexesFound})&&i.containerGroups.length>1)throw new Error("At least one node with a positive tabindex was found in one of your focus-trap's multiple containers. Positive tabindexes are only supported in single-container focus-traps.")},y=function x(c){var f=c.activeElement;if(f)return f.shadowRoot&&f.shadowRoot.activeElement!==null?x(f.shadowRoot):f},b=function x(c){if(c!==!1&&c!==y(document)){if(!c||!c.focus){x(h());return}c.focus({preventScroll:!!n.preventScroll}),i.mostRecentlyFocusedNode=c,Er(c)&&c.select()}},E=function(c){var f=d("setReturnFocus",c);return f||(f===!1?!1:c)},g=function(c){var f=c.target,p=c.event,N=c.isBackward,k=N===void 0?!1:N;f=f||Ee(p),v();var M=null;if(i.tabbableGroups.length>0){var z=l(f,p),m=z>=0?i.containerGroups[z]:void 0;if(z<0)k?M=i.tabbableGroups[i.tabbableGroups.length-1].lastTabbableNode:M=i.tabbableGroups[0].firstTabbableNode;else if(k){var w=ct(i.tabbableGroups,function(J){var Q=J.firstTabbableNode;return f===Q});if(w<0&&(m.container===f||_e(f,n.tabbableOptions)&&!ue(f,n.tabbableOptions)&&!m.nextTabbableNode(f,!1))&&(w=z),w>=0){var B=w===0?i.tabbableGroups.length-1:w-1,G=i.tabbableGroups[B];M=oe(f)>=0?G.lastTabbableNode:G.lastDomTabbableNode}else ye(p)||(M=m.nextTabbableNode(f,!1))}else{var j=ct(i.tabbableGroups,function(J){var Q=J.lastTabbableNode;return f===Q});if(j<0&&(m.container===f||_e(f,n.tabbableOptions)&&!ue(f,n.tabbableOptions)&&!m.nextTabbableNode(f))&&(j=z),j>=0){var K=j===i.tabbableGroups.length-1?0:j+1,V=i.tabbableGroups[K];M=oe(f)>=0?V.firstTabbableNode:V.firstDomTabbableNode}else ye(p)||(M=m.nextTabbableNode(f))}}else M=d("fallbackFocus");return M},S=function(c){var f=Ee(c);if(!(l(f,c)>=0)){if(pe(n.clickOutsideDeactivates,c)){s.deactivate({returnFocus:n.returnFocusOnDeactivate});return}pe(n.allowOutsideClick,c)||c.preventDefault()}},T=function(c){var f=Ee(c),p=l(f,c)>=0;if(p||f instanceof Document)p&&(i.mostRecentlyFocusedNode=f);else{c.stopImmediatePropagation();var N,k=!0;if(i.mostRecentlyFocusedNode)if(oe(i.mostRecentlyFocusedNode)>0){var M=l(i.mostRecentlyFocusedNode),z=i.containerGroups[M].tabbableNodes;if(z.length>0){var m=z.findIndex(function(w){return w===i.mostRecentlyFocusedNode});m>=0&&(n.isKeyForward(i.recentNavEvent)?m+1 We’re going build the classic frontend starter app, the counter, using ReasonReact. If you’ve already installed the starter project, you can run the project now: Open This is just about the simplest component you can make, but through it, we can start to see some of the key differences of developing with ReasonReact: Let’s go over these differences in more detail. In the example above, a new module named The Unlike in normal React, we must wrap strings inside function calls to convert them to the A little bit further down, we make use of the We’ll talk more about Let’s create a counter component by creating a new file This is a component with a single Now let’s modify To display the number of the counter, we wrote This uses the pipe last operator, which is useful for chaining function calls. Let’s add a bit of styling to the root element of Unlike in React, the Congratulations! You’ve created your first ReasonReact app and component. In future chapters we’ll create more complex and interesting components. 1. There aren’t any runtime errors in our app right now. But what happens if you try to remove the 2. What happens if you rename the 3. Comment out the What we covered in this section: 1. Removing the Basically, the compiler is telling you to handle the 2. Renaming OCaml wants you to use all the variables you declare, unless they begin with 3. Commenting out For now, don’t worry about what Source code for this chapter can be found in the Melange for React Developers repo. Despite the name, don’t confuse OCaml’s switch expressions with JavaScript’s switch statements. ↩︎ We need opam, the OCaml Package Manager. There are many ways to install it depending on your platform, but let’s go with the simplest method: While Type After the installation completes, run to verify that it succeeded. Let’s make sure that everything works by downloading and running our project template melange-for-react-devs-template. While Depending on how you’ve been following along, you may have several components in your project. Since these components don’t have much in common with each other, it makes sense to put them in separate, independent apps. To do that, we’ll have to spend a little time with Dune. Dune is a build system designed for OCaml projects, and it has many useful features. For our purposes, the feature of primary interest is its built-in support for Melange. The The line is necessary because we have to manually enable the Melange extension for Dune in order to use it. Note that the version of the Melange Dune extension is independent of the version of Melange we’re using. Technically, Each S-expression at the top level is a known as a stanza. All the possible stanzas you can use in Besides Like The main stanza of interest for us is melange.emit, which tells Dune to turn our OCaml files into JavaScript files. The fields we give to A note about the If you change the value of For more details about where JavaScript output files end up in the build directory, see JavaScript artifacts layout. INFO In this dune file, we’re only concerned with building JavaScript to run in the browser, but if we also wanted to build JavaScript to run on Node, we’d include another Create a new directory After you’ve added those files, your You might be wondering why we’re using a In case you’re not familiar with Make, here’s a breakdown of Currently, the We basically copied the This means that the entry script served by webpack dev server depends on the You’re now ready to run the new Counter app you created! Go into the It should open a new tab in your default browser to http://localhost:8080/ and display your Counter component. Feel to delete the Going forward, we’re going to use a monorepo where projects are separate apps that are developed in their own directory. Each project directory will have its own INFO Melange documentation’s guidelines for melange.emit recommends you put the Huzzah! You created a new 1. Repeat the steps we did for Counter and create a separate app for Celsius Converter. 2. Delete 3. You might have noticed that every recipe in the Makefile is prefixed by a tab. What happens if you replace the tab in front of the 1. Creating a separate app for Celsius Converter, with its own 2. Deleting That’s because putting 3. Replacing the tab in front of the The line number where it expects the tab is 42. It is a very common mistake to write spaces instead of tabs in a Source code for this chapter can be found in the Melange for React Developers repo: WARNING This is a work in progress. This is a project-based, guided introduction to Melange and its ecosystem. Because Melange uses both OCaml and JavaScript ecosystems, there are quite a few tools and concepts to learn. Therefore we try to make each chapter small and digestible, not introducing too many things at once. You should already know how to make frontend applications in JavaScript, in particular with React. You should be interested in learning how to leverage your existing knowledge to build apps using ReasonReact. You do not need to know OCaml[1]—we’ll slowly introduce the basics of the language throughout the tutorial. That said, a good complement to this guide is OCaml Programming: Correct + Efficient + Beautiful, which teaches the language from the ground up and goes much deeper into its features. …and much more to come! Because of the focus on ReasonReact, we won’t cover traditional OCaml syntax in this guide. Instead, we’ll cover the Reason syntax which works great with ReasonReact because of its first-class support for JSX. ↩︎ OCaml’s numeric types work differently than in JavaScript. The first thing you might notice is that OCaml makes a clear and hard distinction between integer and float types. For example: Note that you didn’t have to write the types yourself, OCaml is able to infer the types from the literals. This would also work: Melange Playground is an interactive environment for running OCaml code and seeing its output. Paste this into the source code editor on the left side: In the bottom right panel, you can see the console output, which should be: In the top right panel, you can see the JavaScript generated by the Melange compiler: Note that the Another sweet feature is that you can hover over the variables and see their types. Try it out, and you’ll see that The Problems pane in the bottom left corner shows error mesages when appropriate. At the moment, it shows The Problems pane should now show something like this: Melange Playground is also an excellent way to share OCaml code snippets with your friends! As you type in the source editor, it will store your code in the In Melange Playground, change your program to this: You’ll get a compiler error for the last line that says Unlike JavaScript, there are no implicit conversions in OCaml. Therefore you cannot expect to compare an integer and a float together, unless you convert one of the values so that both values have the same type. To make the last line compile, you can change it to: Another way to fix the last line is to convert The same-type restriction applies to all the comparison operators: What about addition? From what you’ve already seen, you can probably guess that Here are examples of the other arithmetic operators, try them out in the playground: Notice that all float arithmetic operators end with Underneath the covers, Refer to the Melange docs for a complete rundown of how OCaml types get translated to JavaScript types. Melange Playground can also render ReasonReact components! Click the Live button next to the JavaScript output button. Now paste in the code to render the Counter component from the previous chapter: Note that we explicitly defined a module for the Counter component this time because we can’t use multiple files in Melange Playground. Let’s make the counter look a little more impressive. Add a new module called And then update the JSX in our Here’s the playground link for the fully-styled Counter component. W00t! You are now empowered to use numbers in your OCaml programs. 1. Convert the Counter component we created in the previous chapter to use float instead of integer. Make the 2. Add an int64 value to your program in Melange Playground: Note the use of underscores to make the large number more readable. What is the JavaScript representation of int64? 3. How do you add two int64 values? Hint: Take a look at the standard library’s Int64 module. 1. A Counter component that uses float instead of integer would look something like this: 2. JavaScript’s 3. You can add two int64 values using Source code for this chapter can be found in the Melange for React Developers repo. The famed restauranteur Madame Jellobutter has opened a hot new pop-up restaurant called Emoji Cafe, and you’ve been commissioned to build the order confirmation widget on its website. Feeling adventurous, you decide to build it using Melange. Start by creating a new directory The For the time being, there are only two items you can order at Emoji Cafe, the sandwich or the burger. In This is a variant type[1] named The If Madame Jellobutter decides to add a hotdog to the menu, you would need to: Your OCaml code would fail to compile if you added If Madame Jellobutter decides to do a promotion that lowers the price of burgers so that they’re the same price as sandwiches, you could rewrite The underscore ( Since OCaml’s pattern-matching syntax allows you to combine branches, you can simplify it to: In any case, you should strive to avoid wildcards. The OCaml Way is to explicitly match all constructors in your switch expressions. There’s an alternate, shorter syntax for functions whose entire body is a switch expression. It’s called We can also define Using the Now we’re ready to define the The Note that this renders a single row of a table. We’ll need another component to render a table containing all items in an order. Create a new file There’s a lot going on here: You might have noticed that we need a call to If we leave off the call to We get this error primarily because collections in OCaml can only contain elements of the same type. The To better see what types are at play, it might make sense to refactor In reality, If you look at the JavaScript output, you’ll see that the two calls to Render the Run Open your browser’s dev console, where you should see a warning: Oops, we forgot the set the The When a JavaScript function has optional arguments, it’s common to create multiple OCaml functions that bind to it. We’ll discuss this in more detail later. Wunderbar! You’ve got a basic order confirmation component, but it looks… not so great[5]. In the next chapter, we’ll see how ReasonReact components can be styled with plain old CSS. 1. The 2. Add another constructor to 3. Instead of repeatedly using 1. To move the 2. After you add a Of course, you may have chosen a different price for the hotdog. Or maybe you didn’t add a hotdog at all, and instead added 3. In order to create a helper function Then we can use that function like this: Source code for this chapter can be found in the Melange for React Developers repo. Variant types have no equivalent in JavaScript, but they are similar to TypeScript’s union enums ↩︎ Recall that the name of this restaurant is Emoji Cafe, so everything from the menu to the order confirmation must use emojis. ↩︎ All lowercase elements like Using array indexes to set keys violates React’s rules of keys, which states that you shouldn’t generate keys while rendering. We’ll see a better way to do this later. ↩︎ Madame Jellobutter was passing by and just happened to catch a glimpse of the unstyled component over your shoulder and puked in her mouth a little. ↩︎ A project-based, guided introduction to Melange, for React developersCounter
make watch
to start the Melange compiler in watch mode.make serve
. As a side effect, it will open a browser tab pointed to http://localhost:8080/.Index.re
and you’ll see this:module App = {
+ [@react.component]
+ let make = () => <div> {React.string("welcome to my app")} </div>;
+};
module App = {
+ [@react.component]
+ let make = () => <div> {React.string("welcome to my app")} </div>;
+};
make
function renders the component and returns React.element
make
function with [@react.component]
React.string
Components are modules
App
is defined. OCaml’s modules are somewhat like JavaScript modules, with one notable difference being that there can be multiples modules inside a single file. For now, you just need to know that all components in ReasonReact are modules, and the name of the component comes from the name of the module.The make function
make
function has the type unit => React.element
, meaning it takes ()
as the only argument and returns an object of type React.element
. You’ll need to decorate make
with the attribute@react.component
. We’ll go into the details later, but for now let’s just say that @react.component
is there to reduce boilerplate and make our code more readable and easier to maintain.Wrap strings with
React.string
React.element
type. This is exactly what the React.string
function does—if you hover over it, you’ll see that it displays the type string => React.element
.Using the App component
App
component:let node = ReactDOM.querySelector("#root");
+switch (node) {
+| Some(root) => ReactDOM.render(<App />, root)
+| None =>
+ Js.Console.error("Failed to start React: couldn't find the #root element")
+};
let node = ReactDOM.querySelector("#root");
+switch (node) {
+| Some(root) => ReactDOM.render(<App />, root)
+| None =>
+ Js.Console.error("Failed to start React: couldn't find the #root element")
+};
React.querySelector("#root")
returns an option(Dom.element)
, meaning that if it doesn’t find the element, it returns None
, and if it does find the element, it returns Some(Dom.element)
, i.e. the element wrapped in the Some
constructor. The switch expression[1] allows you to succinctly express:node
is Some(Dom.element)
, render the App
component to the DOMnode
is None
, log an error messageoption
in the Celsius converter chapter.The Counter component
Counter.re
with the following contents:[@react.component]
+let make = () => {
+ let (counter, setCounter) = React.useState(() => 0);
+
+ <div>
+ <button onClick={_evt => setCounter(v => v - 1)}>
+ {React.string("-")}
+ </button>
+ {React.string(Int.to_string(counter))}
+ <button onClick={_evt => setCounter(v => v + 1)}>
+ {React.string("+")}
+ </button>
+ </div>;
+};
[@react.component]
+let make = () => {
+ let (counter, setCounter) = React.useState(() => 0);
+
+ <div>
+ <button onClick={_evt => setCounter(v => v - 1)}>
+ {React.string("-")}
+ </button>
+ {React.string(Int.to_string(counter))}
+ <button onClick={_evt => setCounter(v => v + 1)}>
+ {React.string("+")}
+ </button>
+ </div>;
+};
useState
hook. It should look fairly familiar if you know about hooks in React. Note that we didn’t need to manually define a module for Counter
, because all source files in OCaml are automatically modules, with the name of the module being the same as the name of the file.App
so that it uses our new Counter
component:module App = {
+ [@react.component]
+ let make = () => <Counter />;
+};
module App = {
+ [@react.component]
+ let make = () => <Counter />;
+};
The pipe last operator
{React.string(Int.to_string(counter))}
, which converts an integer to a string, and then converts that string to React.element
. In OCaml, there’s a way to apply a sequence of operations over some data so that it can be read from left to right:{counter |> Int.to_string |> React.string}
{counter |> Int.to_string |> React.string}
Basic styling
Counter
:<div
+ style={ReactDOMStyle.make(
+ ~padding="1em",
+ ~display="flex",
+ ~gridGap="1em",
+ (),
+ )}>
+ <button onClick={_evt => setCounter(v => v - 1)}>
+ {React.string("-")}
+ </button>
+ <span> {counter |> Int.to_string |> React.string} </span>
+ <button onClick={_evt => setCounter(v => v + 1)}>
+ {React.string("+")}
+ </button>
+</div>;
<div
+ style={ReactDOMStyle.make(
+ ~padding="1em",
+ ~display="flex",
+ ~gridGap="1em",
+ (),
+ )}>
+ <button onClick={_evt => setCounter(v => v - 1)}>
+ {React.string("-")}
+ </button>
+ <span> {counter |> Int.to_string |> React.string} </span>
+ <button onClick={_evt => setCounter(v => v + 1)}>
+ {React.string("+")}
+ </button>
+</div>;
style
prop in ReasonReact doesn’t take a generic object, instead it takes an object of type ReactDOMStyle.t
that is created by calling ReactDOMStyle.make
. This isn’t a sustainable way to style our app—in the orders chapter, we’ll see how to style using CSS classes.Exercises
| None
branch of the switch (node)
expression?_evt
variable inside the button callback to evt
?[@react.component]
attribute in Counter.re
. What happens?Overview
option
type whose value can be either None
or Some(_)
style
prop doesn’t take generic objectsSolutions
| None
branch will result in a compilation error:Error (warning 8 [partial-match]): this pattern-matching is not exhaustive.
+Here is an example of a case that is not matched:
+None
Error (warning 8 [partial-match]): this pattern-matching is not exhaustive.
+Here is an example of a case that is not matched:
+None
None
case if you want to ship your app. This is part of what makes OCaml such a type-safe language._evt
to evt
results in a compilation error:Error (warning 27 [unused-var-strict]): unused variable evt.
Error (warning 27 [unused-var-strict]): unused variable evt.
_
(underscore).[@react.component]
in Counter.re
will trigger a compilation error in Index.re
, at the place where Counter
component is used:File "Index.re", line 3, characters 19-27:
+3 | let make = () => <Counter />;
+ ^^^^^^^^^^^
+Error: Unbound value Counter.makeProps
File "Index.re", line 3, characters 19-27:
+3 | let make = () => <Counter />;
+ ^^^^^^^^^^^
+Error: Unbound value Counter.makeProps
Counter.makeProps
is or where it came from—just remember that you need to put the [@react.component]
attribute above your make
function if you want your component to be usable in JSX. This is a very common newbie mistake. See the PPX chapter for more details.Installation
Prerequisites
Opam
bash -c "sh <(curl -fsSL https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh)"
+opam init
+eval $(opam env)
bash -c "sh <(curl -fsSL https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh)"
+opam init
+eval $(opam env)
opam init
is running, it will prompt you with something likeDo you want opam to modify ~/.profile?
Do you want opam to modify ~/.profile?
y
to agree.opam --version
opam --version
Download and run the starter project
git clone https://github.com/melange-re/melange-for-react-devs-template
+cd melange-for-react-devs-template
+make init
+make build
+make serve
git clone https://github.com/melange-re/melange-for-react-devs-template
+cd melange-for-react-devs-template
+make init
+make build
+make serve
make init
is running, consider grabbing some coffee or other beverage, as it might take a while to fetch all the dependencies and build them. The last command, make serve
, should open a tab in your default browser which points to http://localhost:8080/ and shows you a typical “Hello World” page. If you see this page, then the project was successfully installed!Visual Studio Code Extension
"[reason]": {
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "ocamllabs.ocaml-platform"
+}
"[reason]": {
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "ocamllabs.ocaml-platform"
+}
Introduction to Dune
dune-project
file dune-project
file specifies metadata for a project, and should appear in the root directory of your project. If you’ve been using the starter project, then you’ve been using Dune this whole time and therefore already have a dune-project
file:(lang dune 3.8)
+
+; Use version 0.1 of the melange plugin for dune
+
+(using melange 0.1)
+
+; Set the name which is used by error messages
+
+(name melange-for-react-devs)
(lang dune 3.8)
+
+; Use version 0.1 of the melange plugin for dune
+
+(using melange 0.1)
+
+; Set the name which is used by error messages
+
+(name melange-for-react-devs)
(using melange 0.1)
(using melange 0.1)
dune-project
accepts many more metadata fields, but it’s best to keep it minimal. Other than name
, it makes more sense to put the rest of your project’s metadata fields in your .opam
file, which we’ll cover later.dune-project
files use S-expressions, which might make you think of the Lisp programming language. However, S-expressions are just a convenient syntax for encoding structured data, and Dune doesn’t have the power of a full scripting language.dune-project
can be found in Dune’s Stanza Reference.dune
files dune-project
, Dune also looks at the dune
files in our project. Basically, dune
files tell Dune about directories, executables, libraries, tests, and anything else of interest. For example, here’s the dune
file inside the root directory of your project:; \`dirs\` is a stanza to tell dune which subfolders from the current folder
+; (where the \`dune\` file is) it should process. Here it is saying to include
+; all directories that don't start with . or _, but exclude node_modules.
+
+(dirs :standard \\ node_modules)
+
+; \`melange.emit\` is a Dune stanza that will produce build rules to generate
+; JavaScript files from sources using the Melange compiler
+; https://dune.readthedocs.io/en/stable/melange.html#melange-emit
+
+(melange.emit
+ ; The \`target\` field is used by Dune to put all JavaScript artifacts in a
+ ; specific folder inside \`_build/default\`
+ (target output)
+ ; Here's the list of dependencies of the stanza. In this case (being
+ ; \`melange.emit\`), Dune will look into those dependencies and generate rules
+ ; with JavaScript targets for the modules in those libraries as well.
+ ; Caveat: the libraries need to be specified with \`(modes melange)\`.
+ (libraries reason-react)
+ ; The \`preprocess\` field lists preprocessors which transform code before it is
+ ; compiled. melange.ppx allows to use Melange attributes [@mel. ...]
+ ; (https://melange.re/v2.0.0/communicate-with-javascript/#attributes)
+ ; reason-react-ppx allows to use JSX for ReasonReact components by using the
+ ; [@JSX] attributes from Reason: https://reasonml.github.io/docs/en/jsx
+ (preprocess
+ (pps melange.ppx reason-react-ppx))
+ ; module_systems lets you specify commonjs (the default) or es6
+ (module_systems es6))
; \`dirs\` is a stanza to tell dune which subfolders from the current folder
+; (where the \`dune\` file is) it should process. Here it is saying to include
+; all directories that don't start with . or _, but exclude node_modules.
+
+(dirs :standard \\ node_modules)
+
+; \`melange.emit\` is a Dune stanza that will produce build rules to generate
+; JavaScript files from sources using the Melange compiler
+; https://dune.readthedocs.io/en/stable/melange.html#melange-emit
+
+(melange.emit
+ ; The \`target\` field is used by Dune to put all JavaScript artifacts in a
+ ; specific folder inside \`_build/default\`
+ (target output)
+ ; Here's the list of dependencies of the stanza. In this case (being
+ ; \`melange.emit\`), Dune will look into those dependencies and generate rules
+ ; with JavaScript targets for the modules in those libraries as well.
+ ; Caveat: the libraries need to be specified with \`(modes melange)\`.
+ (libraries reason-react)
+ ; The \`preprocess\` field lists preprocessors which transform code before it is
+ ; compiled. melange.ppx allows to use Melange attributes [@mel. ...]
+ ; (https://melange.re/v2.0.0/communicate-with-javascript/#attributes)
+ ; reason-react-ppx allows to use JSX for ReasonReact components by using the
+ ; [@JSX] attributes from Reason: https://reasonml.github.io/docs/en/jsx
+ (preprocess
+ (pps melange.ppx reason-react-ppx))
+ ; module_systems lets you specify commonjs (the default) or es6
+ (module_systems es6))
dune-project
, a dune
file consists of one or more stanzas. The first stanza is dirs, which tells Dune which directories to include in the build. Note that the stanzas accepted in dune
files are not the same as the ones accepted by dune-project
. See all possible dune
stanzas in Dune’s Stanza Reference.melange.emit
stanza melange.emit
here are target
, libraries
, preprocess
, and module_systems
, which are ones that we need to use for pretty much every Melange project.target
field: It is referenced in the serve
npm script, whose command iswebpack serve --open --mode development --entry ./_build/default/output/Index.js
webpack serve --open --mode development --entry ./_build/default/output/Index.js
target
to, say, stuff
, the value of the --entry
option must be changed to./_build/default/stuff/Index.js
./_build/default/stuff/Index.js
melange.emit
stanza specifically for that. See melange-opam-template for an example of building for Node.Counter app directory
src/counter
, which will contain a new app that only renders the Counter component. Then make sure this new directory contains:Counter.re
(move from root directory to src/counter
)dune
file that just has a melange.emit
stanza; \`melange.emit\` is a Dune stanza that will produce build rules to generate
+; JavaScript files from sources using the Melange compiler
+; https://dune.readthedocs.io/en/stable/melange.html#melange-emit
+
+(melange.emit
+ ; The \`target\` field is used by Dune to put all JavaScript artifacts in a
+ ; specific folder inside \`_build/default\`
+ (target output)
+ ; Here's the list of dependencies of the stanza. In this case (being
+ ; \`melange.emit\`), Dune will look into those dependencies and generate rules
+ ; with JavaScript targets for the modules in those libraries as well.
+ ; Caveat: the libraries need to be specified with \`(modes melange)\`.
+ (libraries reason-react)
+ ; The \`preprocess\` field lists preprocessors which transform code before it is
+ ; compiled. These enable, for example, the use of JSX in .re files.
+ (preprocess
+ (pps melange.ppx reason-react-ppx))
+ ; module_systems lets you specify commonjs (the default) or es6
+ (module_systems es6))
; \`melange.emit\` is a Dune stanza that will produce build rules to generate
+; JavaScript files from sources using the Melange compiler
+; https://dune.readthedocs.io/en/stable/melange.html#melange-emit
+
+(melange.emit
+ ; The \`target\` field is used by Dune to put all JavaScript artifacts in a
+ ; specific folder inside \`_build/default\`
+ (target output)
+ ; Here's the list of dependencies of the stanza. In this case (being
+ ; \`melange.emit\`), Dune will look into those dependencies and generate rules
+ ; with JavaScript targets for the modules in those libraries as well.
+ ; Caveat: the libraries need to be specified with \`(modes melange)\`.
+ (libraries reason-react)
+ ; The \`preprocess\` field lists preprocessors which transform code before it is
+ ; compiled. These enable, for example, the use of JSX in .re files.
+ (preprocess
+ (pps melange.ppx reason-react-ppx))
+ ; module_systems lets you specify commonjs (the default) or es6
+ (module_systems es6))
Index.re
to render the app to the DOMmodule App = {
+ [@react.component]
+ let make = () => <Counter />;
+};
+
+let node = ReactDOM.querySelector("#root");
+switch (node) {
+| None =>
+ Js.Console.error("Failed to start React: couldn't find the #root element")
+| Some(root) => ReactDOM.render(<App />, root)
+};
module App = {
+ [@react.component]
+ let make = () => <Counter />;
+};
+
+let node = ReactDOM.querySelector("#root");
+switch (node) {
+| None =>
+ Js.Console.error("Failed to start React: couldn't find the #root element")
+| Some(root) => ReactDOM.render(<App />, root)
+};
Makefile
to serve the app using webpack dev server.PHONY: serve
+serve:
+ app=counter make -C ../.. serve
.PHONY: serve
+serve:
+ app=counter make -C ../.. serve
src/counter
directory should look like this:src/counter
+├─ Counter.re
+├─ dune
+├─ Index.re
+└─ Makefile
src/counter
+├─ Counter.re
+├─ dune
+├─ Index.re
+└─ Makefile
Why Makefile
Makefile
instead of adding another npm script. In OCaml projects, usage of the Make build automation tool is common, and this includes Melange-related projects. So let’s take a short detour to explain the Makefile
we just added.Anatomy of Makefile
src/counter/Makefile
:.PHONY: serve
+serve:
+ app=counter make -C ../.. serve
.PHONY: serve
+serve:
+ app=counter make -C ../.. serve
serve
..PHONY: serve
is used to indicate that serve
is a phony target, meaning serve
is not a file we want to generate but just a label for an action. In a simple Makefile
like this one, declaring phony targets isn’t strictly necessary.serve:
is the recipe, basically the command that will be executed when we run make serve
at the command line. app=counter
sets the app
environment variable to counter
, which is the name of the directory this Makefile
lives inMakefile
. In effect, it means we’re running the serve
rule in the root directory’s Makefile
.Root directory
Makefile
serve
rule in root directory’s Makefile
can only run the app in the root directory. We want it to be able to run the app in src/counter
, so we need to change it:.PHONY: serve
+serve: ## Serve the application with a local HTTP server
+ npx webpack serve --open --mode development --entry ./_build/default/src/$(app)/output/src/$(app)/Index.js
.PHONY: serve
+serve: ## Serve the application with a local HTTP server
+ npx webpack serve --open --mode development --entry ./_build/default/src/$(app)/output/src/$(app)/Index.js
serve
npm command from package.json
to create the recipe for this rule, but instead of a fixed value for the --entry
option, we use./_build/default/src/$(app)/output/src/$(app)/Index.js
./_build/default/src/$(app)/output/src/$(app)/Index.js
app
environment variable, which is provided by src/counter/Makefile
.src/counter
directory and runmake serve
make serve
Cleanup
Index.re
file in the root directory. You won’t need it anymore for this or later chapters. You can also delete:scripts
section of yourpackage.json
filemelange.emit
stanza from the dune
file in the root directoryRationale for monorepo structure
dune
file with its own melange.emit
stanza. We want to use a monorepo because most projects will have very similar dependencies, so it seems overkill to create new dune-project
, .opam
, and package.json
files[1] for every single project.melange.emit
stanza in the dune
file in the project’s root directory. We are no longer doing that going forward, but this is still great advice if your repo only contains a single app!dune
file to build an app and a Makefile
to serve that app locally. In future chapters, we assume that you will use the same directory structure for each new app you build.Exercises
reason-react-ppx
from src/counter/dune
’s melange.emit
stanza. What compiler errors do you get?serve
rule’s recipe with four spaces?Overview
dune-project
file describes the metadata for your project, primarily: dune
files describe things of interest to Dune, for example: melange.emit
stanzaMakefile
s contain rules which can run commands based on the target you pass to the make
commandSolutions
dune
, Makefile
, and Index.re
files, should look something like this.reason-react-ppx
from src/counter/dune
will result in a compilation error:File "src/counter/Counter.re", line 5, characters 2-6:
+5 | <div
+ ^^^^
+Error: Unbound value div
File "src/counter/Counter.re", line 5, characters 2-6:
+5 | <div
+ ^^^^
+Error: Unbound value div
reason-react-ppx
in the preprocess/pps
field will transform function calls to div
(which isn’t defined anywhere) into calls to React.createElement("div", ...)
[2].serve
rule’s recipe with four spaces will result in a Makefile
error:Makefile:42: *** missing separator. Stop.
Makefile:42: *** missing separator. Stop.
Makefile
. Fortunately, most code editors know that when you press the Tab key inside a Makefile
, you mean to add a tab character instead of space characters.Melange for React Developers
Motivation
Audience
Chapters and topics
Title Summary Topics covered Counter Number that can be incremented or decremented module, Option, pipe last operator, function chaining, switch Melange Playground Use Melange Playground to explore OCaml’s numeric types Playground, Int, Float Celsius Converter Single input that converts from Celsius to Fahrenheit polymorphic object, exception handling, ternary expression, if-else expression, labeled argument, partial application Celsius Converter using Option The same component from the last chapter but replacing exception handling with Option Option, Option.map, when guard Numeric Types
let foo = 42; // int
+let bar = 42.1; // float
+Js.log(foo);
+Js.log(bar);
let foo = 42; // int
+let bar = 42.1; // float
+Js.log(foo);
+Js.log(bar);
let foo: int = 42;
+let bar: float = 42.1;
+Js.log(foo);
+Js.log(bar);
let foo: int = 42;
+let bar: float = 42.1;
+Js.log(foo);
+Js.log(bar);
Melange Playground
let foo = 42;
+let bar = 42.1;
+Js.log(foo);
+Js.log(bar);
let foo = 42;
+let bar = 42.1;
+Js.log(foo);
+Js.log(bar);
42
+42.1
42
+42.1
// Generated by Melange
+
+
+console.log(42);
+
+console.log(42.1);
+
+var foo = 42;
+
+var bar = 42.1;
+
+export {
+ foo ,
+ bar ,
+}
+/* Not a pure module */
// Generated by Melange
+
+
+console.log(42);
+
+console.log(42.1);
+
+var foo = 42;
+
+var bar = 42.1;
+
+export {
+ foo ,
+ bar ,
+}
+/* Not a pure module */
console.log
invocations are given constants instead of the variables. That’s a nifty optimization done by Melange.foo
has type int
while bar
has type float
.No problems
. Try type-annotating bar
with int
and see what happens:let foo = 42; // int
+let bar: int = 42.1; // float
+Js.log(foo);
+Js.log(bar);
let foo = 42; // int
+let bar: int = 42.1; // float
+Js.log(foo);
+Js.log(bar);
Line 2, 15:
+ Error This expression has type float but an expression was expected of type int
Line 2, 15:
+ Error This expression has type float but an expression was expected of type int
Sharing code snippets
code
query string parameter of the URL. For example, here’s a link to the snippet we started with:Comparison operators
let foo = 42; // int
+let bar = 42.0; // float
+Js.log(foo == bar);
let foo = 42; // int
+let bar = 42.0; // float
+Js.log(foo == bar);
This expression has type float but an expression was expected of type int
This expression has type float but an expression was expected of type int
Js.log(foo == Int.of_float(bar));
Js.log(foo == Int.of_float(bar));
foo
from an int to a float:Js.log(Float.of_int(foo) == bar);
Js.log(Float.of_int(foo) == bar);
Js.log(3 < 33);
+Js.log(44. > 4.);
+Js.log(5 <= 55);
+Js.log(66. >= 6.);
Js.log(3 < 33);
+Js.log(44. > 4.);
+Js.log(5 <= 55);
+Js.log(66. >= 6.);
Arithmetic operators
Js.log(42 + 16.0)
won’t compile. However, you may be surprised to discover that Js.log(42.0 + 16.0)
also won’t compile! That’s because OCaml uses separate arithmetic operators for floats. What will compile is this:Js.log(42.0 +. 16.0); // prints 58
Js.log(42.0 +. 16.0); // prints 58
Js.log(66.0 -. 6.0);
+Js.log(66.0 *. 6.0);
+Js.log(66.0 /. 6.0);
Js.log(66.0 -. 6.0);
+Js.log(66.0 *. 6.0);
+Js.log(66.0 /. 6.0);
.
(period), just like float literals.They’re just
Number
foo
and bar
are both instances of JavaScript’s Number
type:let foo = 42; // int
+let bar = 42.0; // float
+Js.log(Js.typeof(foo)); // prints "number"
+Js.log(Js.typeof(bar)); // prints "number"
let foo = 42; // int
+let bar = 42.0; // float
+Js.log(Js.typeof(foo)); // prints "number"
+Js.log(Js.typeof(bar)); // prints "number"
Widgets in the playground
module Counter = {
+ [@react.component]
+ let make = () => {
+ let (counter, setCounter) = React.useState(() => 0);
+
+ <div
+ style={ReactDOMStyle.make(
+ ~padding="1em",
+ ~display="flex",
+ ~gridGap="1em",
+ (),
+ )}>
+ <button onClick={_evt => setCounter(v => v - 1)}>
+ {React.string("-")}
+ </button>
+ <span> {counter |> Int.to_string |> React.string} </span>
+ <button onClick={_evt => setCounter(v => v + 1)}>
+ {React.string("+")}
+ </button>
+ </div>;
+ };
+};
+
+switch (ReactDOM.querySelector("#preview")) {
+| None => Js.log("Failed to start React: couldn't find the #preview element")
+| Some(root) => ReactDOM.render(<Counter />, root)
+};
module Counter = {
+ [@react.component]
+ let make = () => {
+ let (counter, setCounter) = React.useState(() => 0);
+
+ <div
+ style={ReactDOMStyle.make(
+ ~padding="1em",
+ ~display="flex",
+ ~gridGap="1em",
+ (),
+ )}>
+ <button onClick={_evt => setCounter(v => v - 1)}>
+ {React.string("-")}
+ </button>
+ <span> {counter |> Int.to_string |> React.string} </span>
+ <button onClick={_evt => setCounter(v => v + 1)}>
+ {React.string("+")}
+ </button>
+ </div>;
+ };
+};
+
+switch (ReactDOM.querySelector("#preview")) {
+| None => Js.log("Failed to start React: couldn't find the #preview element")
+| Some(root) => ReactDOM.render(<Counter />, root)
+};
Styles
which contains all the styles we want to use:module Styles = {
+ // Alias the function to save on keystrokes
+ let make = ReactDOMStyle.make;
+
+ let root =
+ make(
+ ~fontSize="2em",
+ ~padding="1em",
+ ~display="flex",
+ ~gridGap="1em",
+ ~alignItems="center",
+ (),
+ );
+
+ let button =
+ make(
+ ~fontSize="1em",
+ ~border="1px solid white",
+ ~borderRadius="0.5em",
+ ~padding="0.5em",
+ (),
+ );
+
+ let number = make(~minWidth="2em", ~textAlign="center", ());
+};
module Styles = {
+ // Alias the function to save on keystrokes
+ let make = ReactDOMStyle.make;
+
+ let root =
+ make(
+ ~fontSize="2em",
+ ~padding="1em",
+ ~display="flex",
+ ~gridGap="1em",
+ ~alignItems="center",
+ (),
+ );
+
+ let button =
+ make(
+ ~fontSize="1em",
+ ~border="1px solid white",
+ ~borderRadius="0.5em",
+ ~padding="0.5em",
+ (),
+ );
+
+ let number = make(~minWidth="2em", ~textAlign="center", ());
+};
make
function to use the style objects in Styles
:<div style=Styles.root>
+ <button style=Styles.button onClick={_evt => setCounter(v => v - 1)}>
+ {React.string("-")}
+ </button>
+ <span style=Styles.number>
+ {counter |> Int.to_string |> React.string}
+ </span>
+ <button style=Styles.button onClick={_evt => setCounter(v => v + 1)}>
+ {React.string("+")}
+ </button>
+</div>;
<div style=Styles.root>
+ <button style=Styles.button onClick={_evt => setCounter(v => v - 1)}>
+ {React.string("-")}
+ </button>
+ <span style=Styles.number>
+ {counter |> Int.to_string |> React.string}
+ </span>
+ <button style=Styles.button onClick={_evt => setCounter(v => v + 1)}>
+ {React.string("+")}
+ </button>
+</div>;
Exercises
-
button decrement by 0.5
and the +
button increment by 1.5
.let baz = 42_000_000_000L; // int64
+Js.log(baz);
let baz = 42_000_000_000L; // int64
+Js.log(baz);
Overview
Number
type once your program is compiled.
) and only accept floats as inputsSolutions
[@react.component]
+let make = () => {
+ let (counter, setCounter) = React.useState(() => 0.0);
+
+ <div
+ style={ReactDOMStyle.make(
+ ~padding="1em",
+ ~display="flex",
+ ~gridGap="1em",
+ (),
+ )}>
+ <button onClick={_evt => setCounter(v => v -. 0.5)}>
+ {React.string("-")}
+ </button>
+ <span> {counter |> Float.to_string |> React.string} </span>
+ <button onClick={_evt => setCounter(v => v +. 1.5)}>
+ {React.string("+")}
+ </button>
+ </div>;
+};
[@react.component]
+let make = () => {
+ let (counter, setCounter) = React.useState(() => 0.0);
+
+ <div
+ style={ReactDOMStyle.make(
+ ~padding="1em",
+ ~display="flex",
+ ~gridGap="1em",
+ (),
+ )}>
+ <button onClick={_evt => setCounter(v => v -. 0.5)}>
+ {React.string("-")}
+ </button>
+ <span> {counter |> Float.to_string |> React.string} </span>
+ <button onClick={_evt => setCounter(v => v +. 1.5)}>
+ {React.string("+")}
+ </button>
+ </div>;
+};
Number
type doesn’t have enough precision to represent in64 values. They’re instead represented by an array of two numbers [high, low]
, where high
is signed, low
is unsigned.Int64.add
, e.g.let result = Int64.add(42L, 16L);
+Js.log(result); // prints [0,58]
let result = Int64.add(42L, 16L);
+Js.log(result); // prints [0,58]
Order Confirmation
src/order-confirmation
and give it the same directory structure as we showed you in the previous chapter:src/order-confirmation
+├─ dune
+├─ Index.re
+├─ Makefile
+└─ Item.re
src/order-confirmation
+├─ dune
+├─ Index.re
+├─ Makefile
+└─ Item.re
dune
file can be copied from any of the existing projects. The Makefile
can also be copied over, but remember to update the value of the app
environment variable to order-confirmation
. The .re
files can be empty for now.Variant type
Item.t
Item.re
, add a new type:type t =
+ | Sandwich
+ | Burger;
type t =
+ | Sandwich
+ | Burger;
t
with two constructors, Sandwich
and Burger
. In OCaml, it is customary for the primary type of a module to be called t
. This convention makes sense because in other modules, this type will be referred to as Item.t
.Item
module should contain helper functions that return the price and the emoji[2] for a given item. First, add the toPrice
function:let toPrice = t =>
+ switch (t) {
+ | Sandwich => 10.
+ | Burger => 15.
+ };
let toPrice = t =>
+ switch (t) {
+ | Sandwich => 10.
+ | Burger => 15.
+ };
Hotdog
constructor to Order.t
| Hotdog
branch to the switch expression of Order.toPrice
Hotdog
or removed Sandwich
from Item.t
without also updating Item.toPrice
. This is one of the great advantages of variant types: changing the constructors will force you to change the relevant parts of your code.Wildcard in switch expressions
Item.toPrice
to:let toPrice = t =>
+ switch (t) {
+ | _ => 10.
+ };
let toPrice = t =>
+ switch (t) {
+ | _ => 10.
+ };
_
) here serves as a wildcard matching any constructor. However, this would be a very bad idea! Now changing the constructors in Item.t
would not force you to change Item.toPrice
accordingly. A superior version would be:let toPrice = t =>
+ switch (t) {
+ | Sandwich => 10.
+ | Burger => 10.
+ };
let toPrice = t =>
+ switch (t) {
+ | Sandwich => 10.
+ | Burger => 10.
+ };
let toPrice = t =>
+ switch (t) {
+ | Sandwich
+ | Burger => 10.
+ };
let toPrice = t =>
+ switch (t) {
+ | Sandwich
+ | Burger => 10.
+ };
A
fun
syntax for switch fun
, and we can rewrite Item.toPrice
to use it:let toPrice =
+ fun
+ | Sandwich => 10.
+ | Burger => 15.;
let toPrice =
+ fun
+ | Sandwich => 10.
+ | Burger => 15.;
toEmoji
using the fun
syntax:let toEmoji =
+ fun
+ | Sandwich => {js|🥪|js}
+ | Burger => {js|🍔|js};
let toEmoji =
+ fun
+ | Sandwich => {js|🥪|js}
+ | Burger => {js|🍔|js};
fun
syntax is completely equivalent to using a switch expression, so it’s up to your personal taste whether you want to use one or the other.Item.make
make
function which will render the Item
component:[@react.component]
+let make = (~item: t) =>
+ <tr>
+ <td> {item |> toEmoji |> React.string} </td>
+ <td>
+ {item
+ |> toPrice
+ |> Js.Float.toFixedWithPrecision(~digits=2)
+ |> React.string}
+ </td>
+ </tr>;
[@react.component]
+let make = (~item: t) =>
+ <tr>
+ <td> {item |> toEmoji |> React.string} </td>
+ <td>
+ {item
+ |> toPrice
+ |> Js.Float.toFixedWithPrecision(~digits=2)
+ |> React.string}
+ </td>
+ </tr>;
make
function has a single labeled argument, ~item
, of type Item.t
. This effectively means the Item
component has a single prop named item
.Order
component src/order-confirmation/Order.re
and add the following code:type t = array(Item.t);
+
+[@react.component]
+let make = (~items: t) => {
+ let total =
+ items
+ |> Js.Array.reduce((acc, order) => acc +. Item.toPrice(order), 0.);
+
+ <table>
+ <tbody>
+ {items |> Js.Array.map(item => <Item item />) |> React.array}
+ <tr>
+ <td> {React.string("Total")} </td>
+ <td>
+ {total |> Js.Float.toFixedWithPrecision(~digits=2) |> React.string}
+ </td>
+ </tr>
+ </tbody>
+ </table>;
+};
type t = array(Item.t);
+
+[@react.component]
+let make = (~items: t) => {
+ let total =
+ items
+ |> Js.Array.reduce((acc, order) => acc +. Item.toPrice(order), 0.);
+
+ <table>
+ <tbody>
+ {items |> Js.Array.map(item => <Item item />) |> React.array}
+ <tr>
+ <td> {React.string("Total")} </td>
+ <td>
+ {total |> Js.Float.toFixedWithPrecision(~digits=2) |> React.string}
+ </td>
+ </tr>
+ </tbody>
+ </table>;
+};
Order
is array(Item.t)
, which is an array of variants.Order.make
function has a single labeled argument, ~items
, of type t
. This means the Order
component has a single prop named items
.Js.Array.reduce
requires the initial value to be passed in.Item
component via Js.Array.map, which is the Melange binding to the Array.map method.React.array
React.array
after the call to Js.Array.map
:{items |> Js.Array.map(item => <Item item />) |> React.array}
{items |> Js.Array.map(item => <Item item />) |> React.array}
React.array
, we’d get this error:File "src/order-confirmation/Order.re", lines 12, characters 6-12:
+12 | {items |> Js.Array.map(item => <Item item />)}
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Error: This expression has type React.element array
+ but an expression was expected of type React.element
File "src/order-confirmation/Order.re", lines 12, characters 6-12:
+12 | {items |> Js.Array.map(item => <Item item />)}
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Error: This expression has type React.element array
+ but an expression was expected of type React.element
tbody
element expects children of type React.element
[3], but the call to Js.Array.map
returns array(React.element)
, which creates a type mismatch. To make the actual type match the expected type, we must add a call to React.array
which turns array(React.element)
to React.element
.Order.make
like so:let total =
+ items
+ |> Js.Array.reduce((acc, order) => acc +. Item.toPrice(order), 0.);
+
+let itemRows: array(React.element) =
+ items |> Js.Array.map(item => <Item item />);
+
+<table>
+ <tbody>
+ {itemRows |> React.array}
+ <tr>
+ <td> {React.string("Total")} </td>
+ <td>
+ {total |> Js.Float.toFixedWithPrecision(~digits=2) |> React.string}
+ </td>
+ </tr>
+ </tbody>
+</table>;
let total =
+ items
+ |> Js.Array.reduce((acc, order) => acc +. Item.toPrice(order), 0.);
+
+let itemRows: array(React.element) =
+ items |> Js.Array.map(item => <Item item />);
+
+<table>
+ <tbody>
+ {itemRows |> React.array}
+ <tr>
+ <td> {React.string("Total")} </td>
+ <td>
+ {total |> Js.Float.toFixedWithPrecision(~digits=2) |> React.string}
+ </td>
+ </tr>
+ </tbody>
+</table>;
React.array
is just there to make the OCaml compiler happy—it doesn’t actually change the the underlying JavaScript object. For example, try running the following code in the playground:let elemArray: array(React.element) =
+ [|"a", "b", "c"|] |> Js.Array.map(x => React.string(x));
+Js.log(elemArray);
+Js.log(React.array(elemArray));
let elemArray: array(React.element) =
+ [|"a", "b", "c"|] |> Js.Array.map(x => React.string(x));
+Js.log(elemArray);
+Js.log(React.array(elemArray));
Js.log
get compiled toconsole.log(elemArray);
+
+console.log(elemArray);
console.log(elemArray);
+
+console.log(elemArray);
Index.re
Order
component inside src/order-confirmation/Index.re
:module App = {
+ let items: Order.t = [|Sandwich, Burger, Sandwich|];
+
+ [@react.component]
+ let make = () =>
+ <div>
+ <h1> {React.string("Order confirmation")} </h1>
+ <Order items />
+ </div>;
+};
+
+let node = ReactDOM.querySelector("#root");
+switch (node) {
+| None =>
+ Js.Console.error("Failed to start React: couldn't find the #root element")
+| Some(root) => ReactDOM.render(<App />, root)
+};
module App = {
+ let items: Order.t = [|Sandwich, Burger, Sandwich|];
+
+ [@react.component]
+ let make = () =>
+ <div>
+ <h1> {React.string("Order confirmation")} </h1>
+ <Order items />
+ </div>;
+};
+
+let node = ReactDOM.querySelector("#root");
+switch (node) {
+| None =>
+ Js.Console.error("Failed to start React: couldn't find the #root element")
+| Some(root) => ReactDOM.render(<App />, root)
+};
make serve
inside src/order-confirmation
to see your new app in action.Js.Array.mapi
Warning: Each child in a list should have a unique "key" prop.
Warning: Each child in a list should have a unique "key" prop.
key
props! One way to fix this is to use Js.Array.mapi instead[4] so we can set key
based on the index of the element:items
+|> Js.Array.mapi((item, index) =>
+ <Item key={"item-" ++ string_of_int(index)} item />
+ )
+|> React.array
items
+|> Js.Array.mapi((item, index) =>
+ <Item key={"item-" ++ string_of_int(index)} item />
+ )
+|> React.array
Js.Array.mapi
function is also a binding to the Array.map
method, but unlike Js.Array.map
, it passes the element and the index into the callback. If you hover over it, you’ll see that it has the type signature(('a, int) => 'b, array('a)) => array('b)
(('a, int) => 'b, array('a)) => array('b)
Exercises
Item
component is only used inside the Order
component and we don’t expect it to be used anywhere else (items rendered in a menu component would look different). Rename it to OrderItem
and move it inside the Order
module.Item.t
variant type. Update the Item
module’s helper functions to get your program to compile again.value |> Js.Float.toFixedWithPrecision(~digits=2) |> React.string
, add a helper function Format.currency
that does the same thing.Overview
t
fun
syntax helps you save a little bit of typing when you have a function whose entire body is a switch expressionmake
function are treated as props by ReasonReact.Js.Array.reduce
function is the binding to JavaScript’s Array.reduce
methodJs.Array.map
and Js.Array.mapi
functions are both bindings to JavaScript’s Array.map
methodReact.array
function is needed when you want to convert an array of React.element
s to a single React.element
, e.g. after a call to Js.Array.map
Solutions
Item
component from the Item
module to the Order
module, you’ll have to move the Item.make
function to a submodule called Order.OrderItem
. Then you’ll have to prefix the references to t
, toPrice
, and toEmoji
with Item.
since they’re now being referenced outside the Item
module. After you’re done, src/order-confirmation/Order.re
should look something like this:type t = array(Item.t);
+
+module OrderItem = {
+ [@react.component]
+ let make = (~item: Item.t) =>
+ <tr>
+ <td> {item |> Item.toEmoji |> React.string} </td>
+ <td> {item |> Item.toPrice |> Format.currency} </td>
+ </tr>;
+};
+
+[@react.component]
+let make = (~items: t) => {
+ let total =
+ items |> Js.Array.reduce((acc, order) => acc +. Item.toPrice(order), 0.);
+
+ <table>
+ <tbody>
+ {items
+ |> Js.Array.mapi((item, index) =>
+ <OrderItem key={"item-" ++ string_of_int(index)} item />
+ )
+ |> React.array}
+ <tr>
+ <td> {React.string("Total")} </td>
+ <td> {total |> Format.currency} </td>
+ </tr>
+ </tbody>
+ </table>;
+};
type t = array(Item.t);
+
+module OrderItem = {
+ [@react.component]
+ let make = (~item: Item.t) =>
+ <tr>
+ <td> {item |> Item.toEmoji |> React.string} </td>
+ <td> {item |> Item.toPrice |> Format.currency} </td>
+ </tr>;
+};
+
+[@react.component]
+let make = (~items: t) => {
+ let total =
+ items |> Js.Array.reduce((acc, order) => acc +. Item.toPrice(order), 0.);
+
+ <table>
+ <tbody>
+ {items
+ |> Js.Array.mapi((item, index) =>
+ <OrderItem key={"item-" ++ string_of_int(index)} item />
+ )
+ |> React.array}
+ <tr>
+ <td> {React.string("Total")} </td>
+ <td> {total |> Format.currency} </td>
+ </tr>
+ </tbody>
+ </table>;
+};
HotDog
constructor to Item.t
, your Item
module should look something like this:type t =
+ | Sandwich
+ | Burger
+ | Hotdog;
+
+let toPrice =
+ fun
+ | Sandwich => 10.
+ | Burger => 15.
+ | Hotdog => 5.;
+
+let toEmoji =
+ fun
+ | Sandwich => {js|🥪|js}
+ | Burger => {js|🍔|js}
+ | Hotdog => {js|🌭|js};
type t =
+ | Sandwich
+ | Burger
+ | Hotdog;
+
+let toPrice =
+ fun
+ | Sandwich => 10.
+ | Burger => 15.
+ | Hotdog => 5.;
+
+let toEmoji =
+ fun
+ | Sandwich => {js|🥪|js}
+ | Burger => {js|🍔|js}
+ | Hotdog => {js|🌭|js};
CannedFood
(🥫) or PotOfFood
(🍲). It’s totally up to you!Format.currency
, we must create a new module file called Format.re
and add a currency
function:let currency = value =>
+ value |> Js.Float.toFixedWithPrecision(~digits=2) |> React.string;
let currency = value =>
+ value |> Js.Float.toFixedWithPrecision(~digits=2) |> React.string;
<td> {item |> toPrice |> Format.currency} </td>
<td> {item |> toPrice |> Format.currency} </td>
div
, span
, table
, etc expect their children to be of type React.element
. But React components (with uppercase names) can take children of any type. ↩︎Melange for React Devs