flatdoc Documentation

Official TypeScript developer resources and documentation for reasonml/flatdoc. Verified technical context for AI.

0 stars TypeScript TypeScript 1 file ยท ~8,239 tokens
RAW Doc

1. Project Overview & Quickstart (reasonml/flatdoc)

flatdoc

Open-source repository reasonml/flatdoc

Repository Details

Note: High-volume repository documentation is actively indexed and synchronized by YakaAI.

2. Official Technical Reference & Guides (reasonml/reasonml.github.io)

File: README.md

Reason Website

This code is used to generate https://reasonml.github.io. It pulls in files from docs/ and website/ to generate HTML files served on the site.

website/ contains the JS, CSS, images and other files (and some markdown files in blog which are separated from docs/; it's not too important).

cd website && npm install && npm run start to start the development server & watcher.

Don't use npm run build. It's mostly for debugging.

In the end, we spit out normal HTML with all but a few critical JS dependencies removed, including ReactJS itself. It's a full static website: super lightweight, portable, unfancy, but good looking. And it works with JS turned off!

Two special files:

  • sidebars.json: lists the sections.
  • siteConfig.json: some header and i18n configs.

During your development, most changes will be picked up at each browser refresh. If you touch these two files or blog/, however, you'll have to restart the server to see the changes.

Translations

The entire site can be translated via the Crowdin project. This repo only has the canonical English documentation. Don't manually edit things in i18n/.

Debugging

console.logs appear in your terminal! Since the site itself is React-free.

Building and Deploying

Changes from the source branch are automatically picked into master branch by CI and then published.

Building the Playground

text
cd website && npm install

For modifying the actual try playground source code, run npm run bundle-try-playground:dev to start a watcher. Once you're done, run npm run bundle-try-playground:prod once.

Building with custom BuckleScript/etc

To build the actual bsReasonReact.js, refmt.js, stdlibBundle.js, reasonReactBundle.js:

You'll need to have this repository and the BuckleScript repository cloned. These will likely be next to each other in the directory structure.

  1. Inside the BuckleScript repository, follow the Setup guide.

  2. Follow the Contributing to the BS Playground Bundle guide.

  3. Run BS_PLAYGROUND=../../reasonml.github.io/website/playground/bs node scripts/repl.js

  4. Inside the reasonml.github.io repository, run cd website && yarn

  5. Then run, yarn prepare in the same place.

  6. Finally, run node setupSomeArtifacts.js

If this command fails, ping @chenglou in Discord.


File: docs/articles-and-videos.md


title: Articles & Videos

Videos

General Functional Programming & Type System

The more dated they look, the richer the content =P


File: docs/basic-structures.md


title: Basic Structures

Quick overview: Basic Structures

List

Lists are immutable, dynamically sized, homogeneous, and ordered collections
of items. It is fast to add or remove elements from the front of a list, but
random access is slow.

Reason lists have a very basic singly-linked implementation.

reason
let listA = [1, 2, 3];

Add to the front using the spread operator ...:

reason
let listB = [0, ...listA];
let listC = [-1, 0, ...listA];

Concatenate lists with the @ operator:

reason
let listD = listA @ listB @ [7, 8];

Add to end

Adding to the end of a list is slow because the built-in list is singly linked.
The expected syntax does not work and will not compile:

reason
/* Syntax error */
let listB = [...listA, 100];

If you need to add to the end and do not care about the operation being slow,
then use the concatenate operator:

reason
let listB = listA @ [100];

A common workaround that remains performant is adding to the front of the list,
then reversing the list at the end with
List.rev.

Common Functions

Map

Map over a list to change items according to some function:

reason
let doubled = List.map(i => i * 2, list);

Fold / Reduce

Fold over a list to reduce it to one value (which may be another collection):

reason
let max = List.fold_left(
  (result, item) => item > result ? item : result,
  initialValue,
  list,
);

Reverse

reason
let reversed = List.rev(list);

Length

  • Important: This is an O(n) operation, be careful when using this function.
    It is not constant time.
reason
let n = List.length(list);

Is Empty

reason
let isEmpty = list == [];

Pattern Matching

Pattern matching works with lists:

reason
switch (list) {
| [first, second, ...rest] => "at least two"
| [first, ...rest] => "at least one"
| [] => "empty"
}

Array

Arrays are mutable, fixed sized, homogeneous, and ordered collections of items.
Random access on an array is fast, but changing the size of an array requires
recreating the entire structure.

reason
let arrayA = [| 1, 2, 3 |];

Access elements using square brackets:

reason
let first = arrayA[0];
let second = arrayA[1];

Update elements using assignment:

reason
arrayA[2] = 23;

Common Functions

Map

Map over an array to change items according to some function:

reason
let doubled = Array.map(i => i * 2, array);

Fold / Reduce

Fold over an array to reduce it to one value (which may be another collection):

reason
let max = Array.fold_left(
  (result, item) => item > result ? item : result,
  initialValue,
  array,
);

Concatenate

reason
let combined = Array.append(arrayA, arrayB);
let combined = Array.concat([arrayA, arrayB, arrayC]);

Length

  • Unlike lists this is a constant time operation. It is safe to use anywhere.
reason
let n = Array.length(array);

Dynamic Creation

reason
let zeroes = Array.make(length, 0);
let squares = Array.init(length, i => i * i);

Conversion to List

reason
let list = Array.to_list(array);
let array = Array.of_list(list);

Pattern Matching

Pattern matching works with arrays, but there is no
...rest syntax like lists have:

reason
switch (array) {
| [||] => "empty"
| [| first |] => "exactly one"
| [| first, second |] => "exactly two"
| _ => "at least three"
};

Tuple

Tuples are immutable, constant sized, and heterogeneous collections of items.
They provide a way to group values together quickly.

reason
let pair = (1, "hello");
let triple = ("seven", 8, '9');

Access specific items in a tuple using destructuring:

reason
let (_, second) = pair;
let (first, _, third) = triple;

Or pattern matching over a tuple:

reason
switch (pair) {
| ("hello", name) => print_endline("Hello " ++ name);
| ("bye", name) => print_endline("Goodbye " ++ name);
| (command, _) => print_endline("Unknown command: " ++ command);
};
  • Note: If a tuple will have many items or be passed around often, then records
    should be preferred.

Records

Records are another basic structure that has named fields and heterogeneous
values. There is a dedicated article on records: Records.


File: docs/boolean.md


title: Boolean

This page has moved to: Boolean


File: docs/community.md


title: Community

This is where we keep track of all the awesome things happening in the community.

Come say hi!

GitHub


File: docs/compiling-to-js-with-jsoo.md


title: Js_of_ocaml

Js_of_ocaml

Template

To get started check out this hello-jsoo-esy template:

text
git clone https://github.com/jchavarri/hello-jsoo-esy.git
cd hello-jsoo-esy
esy

npm install
npm run webpack

File: docs/compiling-to-js-with-melange.md


title: Melange

One of the best ways to use Reason is by compiling it to JavaScript and running it in a web browser or on any other environment with a JavaScript runtime, such as Node.js, Deno, Cloudflare Workers.

Reason source code can be compiled to JavaScript through our partner project, Melange.

What's Melange?

Melange is a backend for the OCaml compiler that emits readable, modular and highly performant JavaScript code.

Melange strives to provide the best integration with both the OCaml and JavaScript ecosystems

If you have experience with JavaScript and the ecosystem we recommend Melange, over other alternatives such as Js_of_ocaml.

Getting started

The install guide on the Melange website shares a lot of common steps with our installation, check it out: melange.re/v2.0.0/getting-started.

Since Melange v1, Melange integrates with dune. This means that you can use dune to compile your Reason code to JavaScript and also to bytecode or native.

Template

To get started with Reason and Melange, there's an official template:

bash
git clone https://github.com/melange-re/melange-opam-template
cd melange-opam-template

# Create opam switch and install dependencies
make init

# In separate terminals:

make watch # Watch for reason file changes and recompile
make serve # Run the development server

Manual

If you prefer to do all the steps manually, here is a step by step. Assuming you have an opam switch with OCaml 5.1.0. If not, check the installation guide.

Install melange

bash
opam install melange

Create dune-project

If you don't have a dune-project file, create one with the following content:

lisp
; dune-project
(dune lang 3.8)
(use melange 0.1) ; Here we enable melange to work with dune

Emit JavaScript

In your dune file, add a melange.emit stanza to emit JavaScript.

The melange.emit stanza tells dune to generate JavaScript files from a set of libraries and modules. In-depth documentation about this stanza can be found in the build-system on the Melange documentation.

lisp
; dune
(melange.emit
 (target app))

Libraries

dune allows to define libraries by creating a dune file inside a folder and adding a library stanza.

To compile a library with melange, add the (modes melange) stanza to the library dune file.

diff
(library
  (name ui_library)
+ (modes melange)
)

Once you have a melange library, you can add it to the melange.emit stanza:

lisp
; dune
(melange.emit
 (target app)
 (libraries ui_library))

Compile

Each time you compile the project, dune will emit JavaScript files under _build/default/app/ (app comes from the (target app) defined under melange.emit).

To compile the project, run:

bash
dune build # Compile the entire project
# or compile the melange alias (only melange.emit stanzas)
dune build @melange

File: docs/converting-from-js.md


title: Converting from JS

This section has been moved to ReScript project: https://rescript-lang.org/docs/manual/v8.0.0/converting-from-js. More info on the transition here.

Some parts aren't relevant anymore, but the general idea is still the same. The process of introducing a new language to your project applies for Reason also.


File: docs/destructuring.md


title: Destructuring

Destructuring is a concise way to extract data from a structure. Destructuring
can be used anywhere a variable name might appear.

Tuples

reason
let pair = (1, "Hello");

/* Destructuring */
let (one, hello) = pair;

/* Prints: Hello */
print_endline(hello);

Records

Destructuring is very commonly used with records.

reason
type person = {
  name: string,
  age: int,
};

let alice = {
  name: "Alice",
  age: 42,
};

/* Destructuring */
let {name, age} = alice;

/* Prints: Alice */
print_endline(name);
  • Note: When destructuring a record the type needs to be in scope or the
    record needs to be explicitly annotated.

Renaming Record Fields

While destructuring record fields, it is possible to bind variable names that
are different than the record field names:

reason
let {name: legalName, age} = alice;

/* Prints: Alice */
print_endline(legalName);

Function Arguments

reason
/* Without destructuring */
let hello = (person) => {
  print_endline("Hello " ++ person.name);
};

/* With destructuring */
let hello = ({name}) => {
  print_endline("Hello " ++ name);
};

/* With destructuring and aliasing */
let hello = ({name: legalName}) => {
  print_endline("Hello " ++ legalName);
};

File: docs/draft-blog-posts.md


title: Draft Blog Posts

This article captures thoughts on design decisons from our old documentation.
These may find their way into a blog post at some point in the future, for now
they will live on this page.

Record Design Decisions

After reading the constraints in the previous sections, and if you're coming from a dynamic language background, you might be wondering why one would bother with record in the first place instead of imitating JS and use a catch-all object concept for everything.

The reason is that JavaScript objects can be really slow. Some code might add fieds, remove fields, iterate over them, pass the keys themselves somewhere else, etc. The JS engines nowadays try to "guess" your object's usage patterns and sometime optimize that into a solid C++ struct, but some other time fail to do so and convert your overly dynamic object into a hash map, with a sudden >100x performance degradation (imagine an object field access needing to hash the key and traverse the hashmap to find it).

We love simplicify, but a single all-powerful data structure that is the JS object is a bit too naive. This is why Reason separates the above use-cases into record, and proper hashmap (documented later). This way, you get to leverage the consistently fast record experience, like in the above immutable update section. Field access is also guaranteed to be super fast.

"But doesn't a Reason record compile to a JS object anyway"? Yes, but those records will trigger the JS engines' optimistic object optimizations, since they see that you never tried to e.g. add or remove record fields, iterate through the keys, etc., and therefore they'll never transform your those compiled JS objects into C++ hash maps or other slow data structures. Basically, Reason's type system enforced the disciplined usage of this data structure so that you can guarantee that it will never be accidentally slow.

(And yes, we're aware that it's comical for a language feature to transform into a dynamic-looking JS object, then transformed again by the JS engines into a C++ struct, then end up where we started in the first place. Such is modern engineering.)

Block Scope Design Decisions

Bindings can be scoped through {}.

reason
let message = {
  let part1 = "hello";
  let part2 = "world";
  part1 ++ " " ++ part2
};
/* `part1` and `part2` not accessible here! */
js
var message = "hello world";

The value of the last line of a scope is implicitly returned.

Design Decisions

Reason's if, while and functions all use the same block scoping. The code below works not because of some special "if scope"; but simply because it's the same scope syntax and feature you just saw:

reason
if (displayGreeting) {
  let message = "Enjoying the docs so far?";
  Js.log(message)
};
/* `message` not accessible here! */

Pattern matching Design Decisions

The notorious fizzbuzz problem strangely trips up some people, partially due its nature of paralyzing the programmer who hopes to simplify/unify the few condition branches in search of elegance where there's none. Hopefully you can see that usually, pattern-matching's
visual conciseness [allows us to overcome decision paralysis](/try.html?reason=PQKgBAQghgzgpgEzAewHZgBYBcsAcYBcwwATsvDlAMbIJwB0yJA5sAO4CWA1h8AGIcAXoIgBXYQGIA8gGEoAWwA2YEMABQiuFjAAzIYIBG4wWAC8YABQcAlGYB8asGBicsVDJY5h5tMAGYAGjAvHyQAVlsAb0cwAB9LAAYghNtTOzAAIgFhMWEMmPiLJLAAfVT0rP18p0KS5PLM3MFquNL7ZywSDlRmEuQdEu6sK2sYgF8AbjU1HSZPYPQARjA
sZDBFhJSwaKcAKRh6RWRmCz1hI2ER0cmgA), while keeping all the benefits (and more, as you've seen) of a bunch of brute-forced if-elses. There's really nothing wrong with explicitly listing out all the possibilities; Pattern matching corresponds to case analysis in math, a valid problem-solving technique that proves to be extremely
convenient.

Using a Reason switch for the first time might make you feel like you've been missing out all these years. Careful, for it might ruin other languages for you =).

If you've tried to refactor a big, nested if-else logic, you might realize it's very hard to get the logic right. On the other hand, pattern matching + tuple conceptually maps to a 2D table, where each cell can be independently filled. This ensures that whenever you need to add a case in the switch, you can target that and only th
at table cell, without messing other cells up.

reason
type animal = Dog | Cat | Bird;
let result = switch (isBig, myAnimal) {
| (true, Dog) => 1
| (true, Cat) => 2
| (true, Bird) => 3
| (false, Dog | Cat) => 4
| (false, Bird) => 5
};
isBig \ myAnimal Dog Cat Bird
true 1 2 3
false 4 4 5

Strings Tips & Tricks

You probably don't need to use strings as much as you did! https://twitter.com/jusrin00/status/875238742621028355

In other languages, you'd often overload the meaning of string by using it as:

  • a unique id: var BLUE_COLOR = "blue"
  • an identifier into a data structure: var BLUE = "blue"; var RED = "red"; var colors = [BLUE, RED]
  • the name of an object field: person["age"] = 24
  • an enum: if (audio.canPlayType() === 'probably') {...} (เฒ _เฒ )
  • other crazy patterns you'll soon find horrible, after getting used to Reason's alternatives.

The more you overload the poor string type, the less the type system (or a teammate) can help you! Reason provides concise, fast and maintainable types & data structures alternatives to the use-cases above (e.g. variants, in a later section).

Under native compilation, Reason strings compile to a simple representation whose performance is straightforward to analyze, at the expense of sometimes requiring manual performance tuning. For example, naively concatenating strings like "hi " ++ "how " ++ "are " ++ "you?" unnecessarily allocates the intermediate strings "are you?" and "how are you?" (though it might be optimized into a single string in these simple cases). In this case, prefer String.concat. In a way, it's somewhat nice that the traditional runtime analysis we've learned in school can finally be useful again.

Under JavaScript compilation, a Reason string maps to a JavaScript string and vice-versa, so no such above concern or analysis opportunities apply.

Ternary Design Decisions

Reason ternary is just a sugar for the bool variant and a switch:

reason
switch (isMorning) {
| true => "Good morning!"
| false => "Hello!"
}

If you pass that through refmt, you'd get:

reason
isMorning ? "Good morning!" : "Hello!";

Interested? Here's a blog post about the spirit of our refmt.

Tuples

Tips & Tricks

You'd use tuples in handy situations that pass around multiple values without too much ceremony. For example, to return many values:

reason
let getCenterCoordinates = () => {
  let x = doSomeOperationsHere();
  let y = doSomeMoreOperationsHere();
  (x, y)
};

Or to "pattern-match" (covered later) on the conjunction of possibilities:

reason
switch (isWindowOpen, isDoorOpen) { /* this is a 2-tuple */
| (true, true) => ...
| (true, false) => ...
| (false, true) => ...
| (false, false) => ...
}

Try to keep the usage of tuple local. For data structures that are long-living and passed around often, prefer a record, which has named fields.

A tuple type might also be called a "product type", and (string, int) is written as string * int in some places. The idea is that a tuple is really a "cartesian product"; imagine a 2D grid, with string on the x axis and int on the y axis!

The combination of tuple + switch is very powerful and concise, and wipes out an entire category of bugs. Together, they cleanly list out all the possible combinations of values. A tuple of type (bool, bool) indeed has 2 * 2 = 4 possibilities, and the type system asks you to cover all 4. This lends itself well to code refactors; instead of informally adding a few if-else on arbitrary values here and there, you can directly pinpoint the exact switch branch you need to alter, no more and no less.

Design Decisions

The existence of tuples might seem odd for those coming from untyped languages. "Why not just use an array?"

A type system isn't all-powerful, nor should it be; some tasteful trade-offs need to be applied in order to keep the language simple, performant (both compilation and running speed) and easy to understand. Reason lists, for example, are more flexible in size; they can be concatenated, appended, sliced, etc. In return, they need to be homogenous (can only contain a single type of value per list), and random index access on them might not always be valid*. Tuple, on the other hand, through its constraint on size, is faster, gives the type system the leeway to exhaustively track all its items' types, and guarantees safe access. In general, you'll notice a few prominent, tasteful tradeoffs in a type system: record fields are fixed but can be heterogenous, while a map's fields are flexible but homogenous, etc.

A Reason tuple is typed "structurally". This means that even if you don't annotate your data with an explicit type, the compiler can still deduce it by looking at its content, its usage, etc. As long as the declarations and the usages' inferred shapes match up, you're all good!

* It's not that the Reason type system cannot accept heterogenous, dynamically-sized lists; it actually can in some circumstances, but making such feature the default increases both the first-time learning overhead and the understandability of code. Just because the types can accomplish it doesn't mean it's always a good idea to let some pieces of code grow unboundedly complex!

Type Design Decisions

Reason's type system is the crystallization of decades of research and engineering. Here are a few highlights:

  • Types can be inferred. The type system deduces the types for you even if you don't manually write them down. This speeds up the prototyping phase. Additionally, editor features like VSCode's codelens in our editor integrations show you all the types while you write code.

  • The type coverage is always 100%. We don't need a "type coverage" tool! Every piece of Reason code has a type.

  • The type system is completely "sound". This means that, as long as your code compiles fine, every type guarantees that it's not lying about itself. In a conventional, best-effort type system, just because the type says it's e.g. "an integer that's never null", doesn't mean it's actually never null. In contrast, a pure Reason program has no null bugs.

Many folks who come from a gradually typed community (for example, having experienced using a type system on top of JavaScript, Ruby, Python, etc.) don't understand the fuss about Reason's types "soundness". In reality, there's a big difference between these type systems that are "best effort", aka 99.9% correct, and our type system that's "sound", aka 100% correct.

Many claim that "99% is good enough". While this could be true in other domains, such claim isn't very valid for code. Naive probability: If your code has 100 types, 99.9% probability of correctness per type means that overall, that code's correctness is 99.9% ^ 100 = 90.4%

Oops. That's potentially lots of false positives in the system's reported types now. Which ones are wrong though? That type system sure isn't telling you ยฏ\_(ใƒ„)_/ยฏ.

Part 2

A type system allowing type argument is basically allowing type-level functions. list(int) is really the list type function taking in the int type, and returning the final, concrete type you'd use in some places. You might have noticed that in other languages, this is more or less called "generics". For example, ArrayList in Java.

The principle of least power applies when you're trying to "Get Things Done". If the problem domain allows, definitely pick the least abstract (aka, the most concrete) solution available, so that the solution is reached faster and has fewer unstable indirections you'd have to traverse. For example, prefer types over free-form data, prefer data-driven configuration over turing-complete function calls, prefer function calls over macros, prefer macros over project forks, etc. When you constrain your domain and power, things become easier to analyze. That is, if the domain is constrained enough to allow it.

When a type system is an all-encompassing aspect of your program, we need to make sure we leave enough power in order not to overly constrain your expressiveness; without "type functions", you'd end up with quite a bit of boilerplate, e.g. hard-coded listOfInt, listOfString, listOfArrayOfFloat, their respective helper functions, etc. However, please also make sure you don't overly abuse the power given to you through a rather powerful type system. Sometimes, it's fine to write a little bit of boilerplate to reduce the need for otherwise extra powerful types. If anything, tasteful tradeoffs might show your pragmatism and judgement more than fancy types!

Variant Design Decisions

Variants, in their many forms (polymorphic variant, open variant, GADT, etc.), are likely the feature of a type system such as Reason's. The aforementioned option variant, for example, obliterates the need for nullable types, a major source of bugs in other languages. Philosophically speaking, a problem is composed of many possible branches/conditions. Mishandling these conditions is the majority of what we call bugs. A type system doesn't magically eliminate bugs; it points out the unhandled conditions and asks you to cover them*. The ability to model "this or that" correctly is crucial.

For example, some folks wonder how the type system can safely eliminate badly formatted JSON data from propagating into their program. They don't, not by themselves! But if the parser returns the option type None | Some(actualData), then you'd have to handle the None case explicitly in later call sites. That's all there is.

Performance-wise, a variant can potentially tremendously speed up your program's logic. Here's a piece of JavaScript:

js
let data = 'dog';
if (data === 'dog') {
  ...
} else if (data === 'cat') {
  ...
} else if (data === 'bird') {
  ...
}

There's a linear amount of branch checking here (O(n)). Compare this to using a Reason variant:

reason
type animal = Dog | Cat | Bird;
let data = Dog;
switch (data) {
| Dog => ...
| Cat => ...
| Bird => ...
}

The compiler sees the variant, then

  1. conceptually turns them into type animal = 0 | 1 | 2
  2. compiles switch to a constant-time format (O(1)).

You might wonder why typed functional languages are used so often for parsing; switching on a large tree efficiently and safely is pretty much the best-case scenario for variants.

Mind blown yet? Variants have a deep connection to other fields of mathematics; See here for an interesting exploration.

* It's always nicer to design away the problem rather than resorting to a type system to cover the pitfalls; In reality, it's unrealistic to do so for every problem, or even just to understand every problem fully in order to design a solution. A type system allows you to safely make a big category of changes to codebases without needing to understand the whole thing upfront. This is great for guided exploration. In this regard, types also allow us not needing to overly design an API just to circumvent callers' simple pitfalls. They reduce the layers of abstractions needed to "get things done", which in return reduces callers' cognitive burden.