diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index ad71d7c0cf..ff30ae9683 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,7 +1,7 @@
-/data/ @christinerose
+/data/changelog/ @sabine
/data/pages/governance.md @avsm
/data/pages/privacy_policy.md @avsm
/data/pages/carbon_footprint.md @avsm
/data/tutorials/platform/op_00_principles.md @avsm
/data/tutorials/platform/op_01_users.md @avsm
-/data/tutorials/guides/op_02_roadmap.md @avsm
\ No newline at end of file
+/data/tutorials/guides/op_02_roadmap.md @avsm
diff --git a/.github/PULL_REQUEST_TEMPLATE/cookbook_pr_template.md b/.github/PULL_REQUEST_TEMPLATE/cookbook_pr_template.md
new file mode 100644
index 0000000000..4e73e1f822
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE/cookbook_pr_template.md
@@ -0,0 +1,16 @@
+#### Checklist
+Ensure the following are addressed before submitting your PR:
+
+1. **Real-World Usefulness**:
+ - [ ] Does the task address a practical need in real-world application development?
+
+2. **Code Quality**:
+ - [ ] Is the code production-ready, safe, and free of potential security issues?
+ - [ ] Does the code avoid uncaught exceptions or other potential pitfalls?
+
+3. **Standard Library and Packages**:
+ - [ ] For tasks using the Standard Library: Does this recipe provide value beyond what an LLM could easily generate?
+ - [ ] For tasks using a package: Does this recipe implicitly recommend the package for production use?
+
+4. **Recipe Redundancy**:
+ - [ ] Does this recipe duplicate an existing task? If so, does it add value by showing differences between packages or approaches?
\ No newline at end of file
diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
new file mode 100644
index 0000000000..3b9a8ec330
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
@@ -0,0 +1,4 @@
+Please go to the `Preview` tab and select the appropriate sub-template:
+
+* [Other PR](?expand=1&template=other_pr_template.md)
+* [OCaml Cookbook Contribution](?expand=1&template=cookbook_pr_template.md)
diff --git a/.github/other_pr_template.md b/.github/other_pr_template.md
new file mode 100644
index 0000000000..3c33591eaa
--- /dev/null
+++ b/.github/other_pr_template.md
@@ -0,0 +1,4 @@
+## Checklist
+
+* [ ] Please format your code using `make fmt`
+* [ ] If addressing an open issue, please link to it by writing "Resolves #1234" (use the relevant issue number)
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index d8bb9526df..486f5dd41c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,6 @@ _opam/
# Files that people sometimes accidentally include in their PRs
*:OECustomProperty
+
+# TailwindCSS opam file, which appears to be generated by the build
+tailwindcss.opam
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 63359e06f6..5a915b87c1 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -4,4 +4,4 @@ This project has adopted the [OCaml Code of Conduct](https://github.com/ocaml/co
# Enforcement
-This project follows the OCaml Code of Conduct [enforcement policy](https://github.com/ocaml/code-of-conduct/blob/main/CODE_OF_CONDUCT.md#enforcement).
\ No newline at end of file
+This project follows the OCaml Code of Conduct [enforcement policy](https://github.com/ocaml/code-of-conduct/blob/main/CODE_OF_CONDUCT.md#enforcement).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7f690004fa..d750b412f4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -22,7 +22,8 @@ We've provided a list of community-driven content below. When adding content to
- [The OCaml Planet](#ocaml-planet)
- [Job Board](#content-job)
- [Success Stories](#content-success-story)
-- [Academic and Industrial Users](#content-user)
+- [Academic Users](#content-academic-user)
+- [Industrial Users](#content-industrial-user)
- [OCaml Books](#content-book)
- [OCaml Cookbook Recipes](#content-cookbook)
- [Recurring Events](#content-recurring-event)
@@ -104,13 +105,50 @@ The success stories should be structured in the following way:
You can read [Ahref's Success Story](https://ocaml.org/success-stories/peta-byte-scale-web-crawler) for a good example.
-### Add an Academic or Industrial User
+### Add an Academic User
-> Contribute to the [Academic Users](https://ocaml.org/academic-users) and [Industrial Users](https://ocaml.org/industrial-users).
+> Contribute to the [Academic Users](https://ocaml.org/academic-users).
-Add a new industrial user by creating a new Markdown file in [data/industrial_users/](data/industrial_users/). For instance: [cryptosense.md](data/industrial_users/cryptosense.md).
+You can add a new academic user by creating a new Markdown file in [data/academic_institutions/](data/academic_institutions). When submitting an academic institution to our webpage, please structure the data as follows:
-You can add a new academic user by creating a new Markdown file in [data/academic_institutions/](data/academic_institutions). For instance: [cornell.md](data/academic_institutions/cornell.md).
+Information about the institution
+- **`name`**: The full name of the academic institution.
+- **`description`**: A brief overview of the institution, including its background and key details.
+- **`url`**: The official website of the institution.
+- **`logo`**: A link to the institution’s logo image.
+- **`continent`**: The continent where the institution is located.
+
+A list of courses available at the institution. Each course entry must include:
+- **`name`**: The full name of the course.
+- **`acronym`**: The course code or identifier.
+- **`url`**: A direct link to the course webpage.
+
+Location
+- **`lat`**: The latitude of the institution’s location.
+- **`long`**: The longitude of the institution’s location.
+
+For instance: [cornell.md](data/academic_institutions/cornell.md).
+
+### Add an Industrial User
+
+> Contribute to the [Industrial Users](https://ocaml.org/industrial-users).
+
+Add a new industrial user by creating a new Markdown file in [data/industrial_users/](data/industrial_users/). When submitting an industrial user to our webpage, please structure the data as follows:
+
+Information about the organization
+- **`name`**: The full name of the organization.
+- **`description`**: A brief overview of the organization, including its mission and key details.
+- **`logo`**: A link to the organization’s logo image.
+- **`url`**: The official website of the organization.
+
+Locations
+- A list of countries where the organization operates.
+
+Additional Information
+- **`consortium`**: Indicates whether the organization is part of a consortium (`true` or `false`).
+- **`featured`**: Indicates whether the organization is highlighted as a featured entity (`true` or `false`).
+
+For instance: [cryptosense.md](data/industrial_users/cryptosense.md).
### Add a Book
@@ -124,10 +162,10 @@ The OCaml Cookbook is a place where OCaml developers share how to solve common
tasks in OCaml using packages from the OCaml ecosystem.
Here are the steps to contribute a recipe for an existing task:
-* Find the task in the [data/cookbook/tasks.yml](data/cookbook/tasks.yml) file.
-* Go to the task folder inside [data/cookbook/](data/cookbook/) that has the
+- Find the task in the [data/cookbook/tasks.yml](data/cookbook/tasks.yml) file.
+- Go to the task folder inside [data/cookbook/](data/cookbook/) that has the
same name as the task's `slug`.
-* Create a `.ml` file containing the recipe and a YAML header with metadata about
+- Create a `.ml` file containing the recipe and a YAML header with metadata about
the recipe.
If the recipe does not fit into any existing task, you also need to create a
@@ -138,7 +176,7 @@ located under a relevant `category:` field.
Finally, it is also possible to create and organise groups of tasks by creating
new categories. Categories are recursive and may have subcategories, which are
full categories too. A task listed in
-[data/cookbook/tasks.yml](data/cookbook/tasks.yml) may have no recipes yet. On the
+[data/cookbook/tasks.yml](data/cookbook/tasks.yml) may have no recipes yet. On the
other hand, it is not allowed to have a task folder in
[data/cookbook/](data/cookbook/) that does not correspond to a task from the
[data/cookbook/tasks.yml](data/cookbook/tasks.yml) file because it triggers a
@@ -147,14 +185,27 @@ compilation error.
Each recipe is a way to perform a task using a combination of open-source
libraries.
-#### OCaml Cookbook Recipe Review Checklist
+#### Guidelines for New OCaml Cookbook Recipes
+
+When contributing new recipes to the OCaml Cookbook, please adhere to the following:
+
+1. **Task Selection**:
+ - Focus on practical, reusable tasks relevant to a wide audience.
+ - Ensure the task demonstrates idiomatic OCaml usage.
+ - Avoid tasks that are overly trivial or highly specific to niche cases.
+
+2. **Code Standards**:
+ - Write clear, idiomatic OCaml code.
+ - Ensure the code compiles without errors or warnings.
+ - Use standard OCaml libraries and tools wherever possible.
-Checklist for reviewing OCaml cookbook submissions:
+3. **Evaluation Criteria**:
+ - Does the recipe address a useful real-world task?
+ - Is the code ready for production use?
+ - If using a package, does it implicitly recommend the package for production?
+ - Avoid duplicating existing recipes unless demonstrating package differences.
-1. does this recipe implement the task it's assigned to?
-2. if this recipe is creating a new task: (1) is the new task substantially different from existing tasks, (2) there is no existing task that this recipe solves, (3) is the new task description very clear on what the task is (be specific!)
-3. is the code explained sufficiently without overexplaining (be short and focus on what matters)?
-4. is the code such that you would put it into production? I.e. are the libraries used stable enough and is the code readable?
+Following these guidelines will help us maintain a high-quality and consistent OCaml Cookbook.
### Add A Recurring Event
@@ -181,7 +232,7 @@ The Changelog covers developments across:
#### Purpose and Audience
-The primary audience for the Changelog is OCaml users. Content should focus on changes, updates, and news that directly impact users of OCaml and its ecosystem.
+The primary audience for the Changelog is OCaml users. Content should focus on changes, updates, and news that directly impact users of OCaml and its ecosystem.
Good candidates for Changelog posts include:
diff --git a/DOCUMENTATION_WRITING.md b/DOCUMENTATION_WRITING.md
index 7affab9170..2cd21973ef 100644
--- a/DOCUMENTATION_WRITING.md
+++ b/DOCUMENTATION_WRITING.md
@@ -1,8 +1,8 @@
# How to Write Documentation
-This document explains how to write documentation such as tutorials, guides, or recommendations to be hosted in OCaml.org. It's also meant to be used as a style guide to ensure consistency in things like grammar, formatting, capitalisation, and spelling across all OCaml.org documentation.
+This document explains how to write documentation such as tutorials, guides, or recommendations to be hosted in OCaml.org. It's also meant to be used as a style guide to ensure consistency in things like grammar, formatting, capitalisation, and spelling across all OCaml.org documentation.
-Apply the [*Spiral Learning*](https://en.wikipedia.org/wiki/Spiral_approach) approach in tutorials. This teaching method first creates a solid foundation of a topic. By starting with an overview, it gives the reader the context and general information. Subsequent sections and tutorials review the important foundational information and expands, going into more detail and giving examples.
+Apply the [*Spiral Learning*](https://en.wikipedia.org/wiki/Spiral_approach) approach in tutorials. This teaching method first creates a solid foundation of a topic. By starting with an overview, it gives the reader the context and general information. Subsequent sections and tutorials review the important foundational information and expands, going into more detail and giving examples.
## Materials
@@ -16,26 +16,28 @@ Apply the [*Spiral Learning*](https://en.wikipedia.org/wiki/Spiral_approach) app
## Audience
-Anytime one contributes to the OCaml.org documentation, it's important to keep the target audience in mind. For example, if writing a tutorial for programmers new to OCaml, you want to ensure the examples are simple and straighforward so as not to overwhelm them with too much detail while starting their learning journey. For more advanced users, adjust the tone and examples accordingly.
+Anytime one contributes to the OCaml.org documentation, it's important to keep the target audience in mind. For example, if writing a tutorial for programmers new to OCaml, you want to ensure the examples are simple and straighforward so as not to overwhelm them with too much detail while starting their learning journey. For more advanced users, adjust the tone and examples accordingly.
**Our audience:**
-* Self-directed learners without a tutor
-* Already know some programming basics (one other programming language)
-* Likely a majority of consumers of libraries, hopefully some authors or people who turn into it
-* English level B2 ideally
+- Self-directed learners without a tutor
+- Already know some programming basics (one other programming language)
+- Likely a majority of consumers of libraries, hopefully some authors or people who turn into it
+- English level B2 ideally
**Not our audience:**
-* University students
-* New to programming. We do not teach programming basics.
+- University students
+- New to programming. We do not teach programming basics.
## Goals
+
Especially since the release of OCaml 5.0 with Multicore support, perhaps our biggest goal is to increase the adoption of OCaml. In order to reach this goal, it's essential to have current, consistent, and comprehensive documentation.
1. Enable people to use OCaml for real projects / at the job / side projects
1. Get people who want to build and contribute to the ecosystem up to speed and building something good quickly
1. **Oddly Specific**: Enable people to do Advent of Code in OCaml.
-## Our Key Values / Constraints:
+## Our Key Values / Constraints
+
- **Goals and Prerequisites**. Each tutorial begins with its objectives, so that people know what they will learn about.```
- **Avoid Overwhelming Choices**. Tutorials should guide learners on a straightforward path, avoiding multiple options. This makes the learning process smoother. Detailed choices can be included in additional references and guides.
- **Highlight Important Computer Science Terms**. Use italics for well-known terms, providing a visual hint that these are important concepts. Linking to Wikipedia for further explanation is an option.
@@ -47,32 +49,31 @@ Especially since the release of OCaml 5.0 with Multicore support, perhaps our bi
## Common Phrases
- "Binding a value to a name" = declaring a variable
-- `'a` is a type parameter called "alpha." It is not a _type variable_ (because the term _variable_ is a forbidden word).
+- `'a` is a type parameter called "alpha." It is not a *type variable* (because the term *variable* is a forbidden word).
- Pass a function as a value to another function as a parameter - not a "function value"
-
## Things to Avoid
-1. Don't use the same letter for different things, i.e., when talking about a type parameter `'a`, don't have a name `a` nearby. In fact, since `a` can easily be confused with `'a` (alpha), start with `f` when using letters as parameters.
+1. Don't use the same letter for different things, i.e., when talking about a type parameter `'a`, don't have a name `a` nearby. In fact, since `a` can easily be confused with `'a` (alpha), start with `f` when using letters as parameters.
1. Never use the term "variable," instead
a. Names and values (binding = a value is bound to a name)
b. Type parameter
1. Use “parameter” and “argument” appropriately. Parameters occur in function declarations. Arguments are values that functions are applied to.
1. Don't use math, computer science, or programming language theory terminology without reason and explanation.
+## Writing, Grammar, and Spelling
-## Writing, Grammar, and Spelling
-
-Please don't worry about this too much, as a technical writer will review any new documentation or changes to catch these types of things. It's included here for reference, if interested.
+Please don't worry about this too much, as a technical writer will review any new documentation or changes to catch these types of things. It's included here for reference, if interested.
### Tone & POV
-We're aiming for a relatively casual tone in these tutorials and other documentation. This means that it should read like you're speaking directly to the reader rather than an academic tone. To this end, it is acceptable to use second person (you), sparingly.
-It's also okay to use first person plural (we, us), sparingly, but don't use the first person singular (I, me), as there isn't an author byline for the reader to know who "I" is. Using "we" can be helpful to write active sentences (more below).
+We're aiming for a relatively casual tone in these tutorials and other documentation. This means that it should read like you're speaking directly to the reader rather than an academic tone. To this end, it is acceptable to use second person (you), sparingly.
+It's also okay to use first person plural (we, us), sparingly, but don't use the first person singular (I, me), as there isn't an author byline for the reader to know who "I" is. Using "we" can be helpful to write active sentences (more below).
### Active Voice & Unnecessary Words
-Active sentences employ a clear subject that performs an action with a robust verb (e.g., Mary baked the cake.), as opposed to weak verbs such as *occur* and *happen.* Passive sentences typically incorporate '***to be'*** verbs (is, are, were, was, etc.), where the subject undergoes the action (e.g., he cake was baked by Mary). Although it's not possible to avoid all **to be** verbs, try to minimise them when possible.
+
+Active sentences employ a clear subject that performs an action with a robust verb (e.g., Mary baked the cake.), as opposed to weak verbs such as *occur* and *happen.* Passive sentences typically incorporate '***to be'*** verbs (is, are, were, was, etc.), where the subject undergoes the action (e.g., he cake was baked by Mary). Although it's not possible to avoid all **to be** verbs, try to minimise them when possible.
The sentences below highlight issues and provide suggestions for improvement:
@@ -84,37 +85,35 @@ The sentences below highlight issues and provide suggestions for improvement:
It's best to eliminate unnecessary prepositional phrases, especially when using with the possessive. Avoid phrases like "the car of Susan." Instead, write "Susan's car." When prepositional phrases (e.g., those starting with by, for, in, of, on, etc.) are strung together, it makes for clumsy and awkward sentences. For example: "She went *on* a road trip *across* the mountains *to* take pictures." This sentence has three prepositional phrases back to back. You can improve it by removing at least one prepositional phrase, like this: "She took pictures on her roadtrip across the mountains."
-Following these guidelines makes for more enjoyable reading experience.
+Following these guidelines makes for more enjoyable reading experience.
### Spelling & Grammar
- [Grammar Help at Grammarly](https://www.grammarly.com/blog/category/handbook/)
- **Punctuation Preferences:**
- - Oxford (serial) comma [We bought bread, milk, and peanut butter], so there is a final comma before the conjunction.
- - Single space after a `.` period (full stop).
- - Always use `'` for singular names/nouns ending in `s` and for plural nouns ending in `s` (e.g., Thomas’ and Companies’). Although the grammatically correct way to say and spell singular nouns ending in `s` is “Thomas’s”, it does look weird. It’s the consensus to treat every `s` as if it were a plural possessive, so instead write "Thomas'." Whenever possible, rephrase the sentence to avoid it completely.
- - Place punctuation inside of quotation marks: OCaml is an "industrial-strength functional programming language with an emphasis on expressiveness and safety." (not "...safety".)
- - Use **en dashes `–`** surrounded by spaces for 'asides' (as an alternative, you can use parentheses). Example: OCaml Multicore is – as my grandmother would say – an excellent upgrade.
- - [Cambridge Dictionary: Punctuation](https://dictionary.cambridge.org/grammar/british-grammar/punctuation)
- - [Differences in British & American](https://www.unr.edu/writing-speaking-center/student-resources/writing-speaking-resources/british-american-english), please use the British spelling. See below.
- - [Another ^^](https://www.thepunctuationguide.com/british-versus-american-style.html)
+ - Oxford (serial) comma [We bought bread, milk, and peanut butter], so there is a final comma before the conjunction.
+ - Single space after a `.` period (full stop).
+ - Always use `'` for singular names/nouns ending in `s` and for plural nouns ending in `s` (e.g., Thomas’ and Companies’). Although the grammatically correct way to say and spell singular nouns ending in `s` is “Thomas’s”, it does look weird. It’s the consensus to treat every `s` as if it were a plural possessive, so instead write "Thomas'." Whenever possible, rephrase the sentence to avoid it completely.
+ - Place punctuation inside of quotation marks: OCaml is an "industrial-strength functional programming language with an emphasis on expressiveness and safety." (not "...safety".)
+ - Use **en dashes `–`** surrounded by spaces for 'asides' (as an alternative, you can use parentheses). Example: OCaml Multicore is – as my grandmother would say – an excellent upgrade.
+ - [Cambridge Dictionary: Punctuation](https://dictionary.cambridge.org/grammar/british-grammar/punctuation)
+ - [Differences in British & American](https://www.unr.edu/writing-speaking-center/student-resources/writing-speaking-resources/british-american-english), please use the British spelling. See below.
+ - [Another ^^](https://www.thepunctuationguide.com/british-versus-american-style.html)
- **Spelling**
- - [British spelling](http://www.tysto.com/uk-us-spelling-list.html)
- - ise rather than -ize (organise vs organize)
- - our, not or (flavour vs flavor)
- - yse, not yze (analyse vs analyze)
- - Double L : travelling vs traveling
- - ae/oe vs e (manoeuvre vs maneuver)
- - ence, not -ense (licence vs license)
- - ogue, not og (catalogue vs catalog)
- - learnt, not learned
- - focussed / focusses (instead of the single s: focused / focuses)
- - vs, not vs. as in American English. Same with Dr, Mr, Ms, Mrs instead of Dr., Mr., Ms., Mrs.
- - Although programme is traditionally the UK spelling, **program** is more common.
-
-- **Capitalisation**
- - Capitalise every word (except “little words” like of, and, or, etc.) in titles, aka “[Title Case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case).” Strangely, it is correct to capitalise “with” in a title.
- - Use Title Case in headings / subheadings. Try to keep them under 7 words.
- - Capitalise the first letter of bullet points, aka “[Sentence Case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/sentence-case).” However, if it’s not a complete sentence in the bullet point, don’t use a period (full stop) unless it’s followed by other sentences.
-
+ - [British spelling](http://www.tysto.com/uk-us-spelling-list.html)
+ - ise rather than -ize (organise vs organize)
+ - our, not or (flavour vs flavor)
+ - yse, not yze (analyse vs analyze)
+ - Double L : travelling vs traveling
+ - ae/oe vs e (manoeuvre vs maneuver)
+ - ence, not -ense (licence vs license)
+ - ogue, not og (catalogue vs catalog)
+ - learnt, not learned
+ - focussed / focusses (instead of the single s: focused / focuses)
+ - vs, not vs. as in American English. Same with Dr, Mr, Ms, Mrs instead of Dr., Mr., Ms., Mrs.
+ - Although programme is traditionally the UK spelling, **program** is more common.
+- **Capitalisation**
+ - Capitalise every word (except “little words” like of, and, or, etc.) in titles, aka “[Title Case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case).” Strangely, it is correct to capitalise “with” in a title.
+ - Use Title Case in headings / subheadings. Try to keep them under 7 words.
+ - Capitalise the first letter of bullet points, aka “[Sentence Case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/sentence-case).” However, if it’s not a complete sentence in the bullet point, don’t use a period (full stop) unless it’s followed by other sentences.
diff --git a/Dockerfile b/Dockerfile
index 2751f1f5bb..8698cc0c5e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -43,6 +43,7 @@ COPY --from=build /home/opam/_build/default/src/ocamlorg_web/bin/main.exe /bin/s
COPY playground/asset playground/asset
RUN git clone https://github.com/ocaml-web/html-compiler-manuals /manual
+ADD data/v2 /v2
RUN git config --global --add safe.directory /var/opam-repository
@@ -50,7 +51,8 @@ ENV DREAM_VERBOSITY=info \
OCAMLORG_HTTP_PORT=8080 \
OCAMLORG_MANUAL_PATH=/manual \
OCAMLORG_PKG_STATE_PATH=/var/package.state \
- OCAMLORG_REPO_PATH=/var/opam-repository/
+ OCAMLORG_REPO_PATH=/var/opam-repository/ \
+ OCAMLORG_V2_PATH=/v2
EXPOSE 8080
diff --git a/HACKING.md b/HACKING.md
index e16c558d2d..a11941aa46 100644
--- a/HACKING.md
+++ b/HACKING.md
@@ -5,17 +5,18 @@
### Setting Up the Project
Before starting to hack, you need a properly configured development environment. Linux and macOS are supported and used daily by the core team. System dependencies include:
-* Libev: http://software.schmorp.de/pkg/libev.html
-* Oniguruma: https://github.com/kkos/oniguruma
-* OpenSSL: https://www.openssl.org/
-* GNU Multiple Precision: https://gmplib.org/
-The project [`Dockerfile`](./Dockerfile) contains up-to-date system configuration instructions, as used to ship into production. It is written for the Alpine Linux distribution, but it is meant to be adapted to other environments such as Ubuntu, macOS+Homebrew, or others. The GitHub workflow file [`.github/workflows/ci.yml`](.github/workflows/ci.yml) also contains useful commands for Ubuntu and macOS. Since OCaml.org is mostly written in OCaml, a properly configured OCaml development environment is also required, but is not detailed here. Although Docker is used to ship, it is not a requirement to begin hacking. Currently, OCaml.org doesn't yet compile using OCaml 5; version 4.14 of the language is used. It is possible to run workflow files in `.github/workflows` using the [`nektos/act`](https://github.com/nektos/act) tool. For instance, the following command runs the CI checks through GitHub on each pull request (where `ghghgh` is replace by an _ad-hoc_ GitHub token, see: https://github.com/nektos/act#github_token)
+* Libev: OpenAI offers a way to have (relatively) typed communication through structured outputs. A json schema must be passed alongside the prompt. And the answer is guaranteed to follow that schema. When working with OpenAI’s structured outputs, you typically need to: This usually involves writing and maintaining JSON schemas by hand, which can be error-prone and tedious. The approach we’ll explore in this project eliminates this manual work by: This means you can define your data model once in OCaml and get both the schema for OpenAI and the parsing logic for free! The bindings to the API are kept fairly minimal in this example. We are using Devkit for the http client. And ppx_yojson_conv to conveniently emit/parse the necessary json. The types defined in the Request and Response modules are straight translation of the OpenAI API reference. The [@@deriving of_yojson] annotation generates functions to convert JSON from/into these types. The [@@yojson.allow_extra_fields] attribute ensures our code won't break if OpenAI adds new fields to their API responses. The send function is a thin wrapper around the HTTP request to OpenAI's API, serializing our request to JSON, sending it, and then parsing the response back into our OCaml types. Notice how we’re using PPX extensions throughout this code to minimize boilerplate. Without these extensions, we would already need to write a lot of manual serialization and deserialization code. In this example, I will demonstrate how to use OpenAI as a math tutor to solve problems with step-by-step details. Instead of receiving a single block of text that would require parsing to extract individual steps, I’ll define a structured JSON schema. The schema requires each step to include both an explanation and an intermediate output, along with a final answer. Here’s where the magic of ppx_deriving_jsonschema comes in: Let’s break down what’s happening here: Without these PPX extensions, we would need to: Instead, we get all of this automatically from a single type definition! Once our schema is ready, it is easy to insert it in the request, alongside with the prompt. The remaining task is to retrieve the steps and display them. OpenAI has the ability to return multiple versions of its answer, calling it choices. Here we will only process one choice for simplicity. Notice how we’re using the math_reasoning_of_yojson function that was automatically generated by the [@@deriving yojson] annotation. Once we have the properly typed OCaml value, we can safely access its fields and process the steps in a type-safe manner. This is much more robust than manually parsing the JSON or using string manipulation to extract the information. Finally we only have to do a little bit of plumbing to make the program work. We get the question of the user from the command line, query the OpenAI API, and display the response. The output should look like this: As you can see, the output of some of the steps is not correct. This is expected when performing mathematical operations using an LLM. Please always review the output with care. The whole project can be found at https://github.com/ahrefs/ocaml-openai-demo. The dependencies of the project can be installed from opam: OpenAI and structured outputs from OCaml was originally published in Ahrefs on Medium, where people are continuing the conversation by highlighting and responding to this story. Ruby programmers are commonly called Rubyists. Python programmers
+are often called Pythonistas. Lisp programmers are widely known
+as Lispers. Clojure programmers are sometimes called Clojurians. Of course, in a lot more programming language communities such nicknames never
+became common - e.g. Java, .NET, PHP and JavaScript.1 I wonder if those playful
+nicknames are also a reflection of the spirit of a community or they are just a
+side-effect of how easy it’s to derive something from the name a programming
+language. But what about OCaml programmers? (O)Camlers? (O)Camlists? (O)Camlians? (O)Camlurians? Caml Riders? I’ve been wondering about this in the background of my mind ever
+since I got interested in OCaml last year. I finally decided to do
+a bit of digging around and I’ve discovered that there’s no popular/common
+term for OCaml programmers. The members of the community did
+have some cool ideas, though. Since we stealthy move between pure and impure environments, Cameleons seems appropriate. Indeed! I like this one a lot, as I think it captures perfectly OCaml’s creed. So, let them be know as Cameleons going forward! Or Camleons? Naming is hard! Oh, Camleons, that’s all I have for you today. Keep hacking and may your code always compile from the first try! And occasionally even do the thing you intended it to! At least in English. In Bulgaria we have nicknames for pretty much every type of programmer. E.g. if you’re Java programmer you’d often by called Javar or Javajiya. PHP programmers are PHPars. C programmers are See-ee-ji-ee. Not super original, but very versatile. ↩ A couple of days ago I noticed on OCaml’s Discord server that someone was
+confused by OCaml function applications (invocations) like these: To people coming from “conventional” programming languages this might look like
+calling a function/method without any arguments. (e.g. So what are those You’ll just end up with static binding to nothing. Or not quite nothing, as it’s time to
+talk about I hope this also explains why’d have to call such functions with Basically, you need to pass the argument to have an actual function application.
+Remember that in OCaml (and most functional programming languages), functions are first-class
+objects that you can pass around like any other value. And that’s a wrap. I hope you learned something useful today. Keep hacking! I’ve noticed that some newcomers to OCaml are a bit confused by code like the following: Both of those are forms of pattern matching, but one of them is a lot stricter
+than the other. In OCaml Just as in In practice, The above example is a bit contrived, but I hope you get the idea. Also, you can totally use That’s all I have for you today. Keep hacking! OCaml is famous for allow you to do a lot of things like modules. Like really a lot!
+Advanced features like functors, aside, it’s really common to either alias
+module names to something shorter or localize All of them have their uses, but I’d like to also mention one less known
+approach - namely a scoped module alias: I think in some way that’s the best of both worlds as it makes it obvious
+that certain functions are coming from a module, and you’re still not
+doing that much extra typing. Finding the right balance between conciseness,
+readability and maintainability is never easy, though. Note: Interestingly OCaml’s younger sibling F# chose not to implement
+scoped module opens at all. I’m guessing this happened due to maintainability
+concerns. What are your thoughts on the subject? In which situations would you prefer
+ That’s all I have for you today. Keep hacking! In my previous article I mentioned that OCaml’s
+ Image that we’re dealing with a simple investment portfolio,
+where we have multiple records containing: Let’s assume this portfolio is stored in As you can see we’re using a parsing format specifier that’s pretty similar to what we’d
+normally use with Here’s a more complete example that parses a few portfolio records and
+calculates the value of the portfolio: Not bad, right? The formatted input functions can read from any kind of input, including
+strings, files, or anything that can return characters. The more general source
+of characters is named a formatted input channel (or scanning buffer) and has
+type Generally speaking, the formatted input functions have 3 arguments: My trivial examples dealt only with input strings, but you can easily leverage
+other input sources. Here’s an example reading from the standard input: Enter something like “Chair 20.25” and observe the results. I’d encourage everyone to get familiar with the module’s
+documentation for all the nitty-gritty details. Please, share in the comments how you’re using That’s all I have for you. Keep hacking! While learning OCaml I’ve noticed one curious feature - it has two
+types of string literals. The first type are the common and quite
+familiar “double-quoted string literals” (or perhaps simply “string literals”?): Nothing really surprising here, right? But then there are also what OCaml calls
+quoted string
+literals (a.k.a. “quoted strings”): Now that’s something you don’t see every day, right? Here’s how the official
+manual describes them: Quoted string literals provide an alternative lexical syntax for string
+literals. They are useful to represent strings of arbitrary content without
+escaping. Quoted strings are delimited by a matching pair of
+ Note that all special escape sequences are ignored in quoted strings: In this way you can say they are pretty similar to single-quoted strings in
+languages like Perl and Ruby (think Real World OCaml has a nice
+example that
+illustrates the usefulness of quoted strings: As you can see one good use-case for quoted strings is embedding snippets of
+code, as those typically tend to have lots of things that would normally need to be
+escaped.
+And because the delimiters are so flexible you don’t really have to worry about
+content that uses Another good use-case for quoted strings are regular expressions.1 In regular
+expressions you will often use backslash characters; it’s easier to use a quoted
+string literal returns the string “world”. If you want a regular expression that matches a literal backslash character, you need to double it: If we use regular string literals (“…”), we will have to escape backslashes, which makes the regular expressions a bit harder the read: And the regular expression for matching a backslash becomes a quadruple backslash: One more thing. Quoted strings For instance, you can use I have to say I think it’s a bit funny that OCaml’s quoted strings are called
+“quoted strings”. It’s not like double-quoted strings (think That’s all I have for you today. Please, share in the comments whether you use
+quoted strings and what are some of your favorite use-cases for them. Keep
+hacking! One of the things that bothered me initially in OCaml was the poor support for
+working in regular expressions in the standard library.
+Technically speaking, there’s no support for them at all! What do I mean by this? Well, there’s the older Str library that provides support for regular expressions, but it’s: Note: Use Here’s a trivial example using it: I hope the examples are self-explanatory. Tip: If you find string literals like I won’t dwell much on One interesting thing about Okay, shell globbing is not exactly regular expressions, and I’m not sure who would want to use Emacs style regular expressions
+outside Emacs, but you sure have options! I’m a big fan of Perl’s regular expressions, so I’ll stick with them going forward. Now, let’s see it in action (I encourage to try the examples below in I hope it’s clear that And here’s a brief comparison of To sum it up: That article sat in my backlog for quite a while, as regular expressions were
+one of the most frustrating aspects for me when I started to play with OCaml
+(Perl and Ruby had really spoiled me on that front), but eventually I kind of
+got used to them, so I no longer felt much need to write the article. Still,
+I hope some newcomers to OCaml will find it userful! That’s all I have for you today. Keep hacking! How can you be sure that an OCaml function you wrote is actually tail-recursive?
+You can certainly compile the code and look at the generated assembly code, but that’d be quite the overkill, given there is a much simpler way to do this. OCaml 4.03 introduced the Here are a couple of trivial examples to help illustrate this: Save the code above in a file named As you can see the compiler properly detected that That’s all I have for you today. Keep hacking! Basically the tail-call is the call that triggers the recursion in the function and for a function to be tail-recursive the last call has to be an invocation of the recursive function itself. ↩ I wasn’t an early adopter of TreeSitter in Emacs, as usually such
+big transitions are not smooth and the initial support for TreeSitter in
+Emacs left much to be desired. Recently, however, Emacs 30 was released with many
+improvements on that front, and I felt the time was right for me to (try to) embrace
+TreeSitter. I’m the type of person who likes to learn by deliberate practice, that’s why I
+wanted to do some work on TreeSitter-powered major modes. I’ve already been a
+co-maintainer of
+clojure-ts-mode for a while
+now, and I picked up the basics around it, but I didn’t spend much time hacking
+on it until recently. After spending a bit more time studying the current
+implementation of Why did I start a new OCaml package, when there are already a few existing out
+there? Because There have been two other attempts to create TreeSitter-powered
+major modes for Emacs, but they didn’t get very far: Looking at the code of both modes, I inferred that the authors were probably knowledgable in
+OCaml, but not very familiar with Emacs Lisp and Emacs major modes in general.
+For me it’s the other way around, and that’s what makes this a fun and interesting project for me: One last thing - we really need more Emacs packages with fun names! :D Naming is hard, and I’m
+notorious “bad” at it!2 They say that third time’s the charm, and I hope that I’ve documented the code extensively inline, and in the README you’ll find my development notes detailing
+some of my decisions, items that need further work and research, etc. If nothing else - I think
+anyone can learn a bit about how TreeSitter works in Emacs and what are the common challenges
+that one might face when working with it. To summarize my experience so far: Fundamentally, the main problem is that we still don’t have
+easy ways to try out TreeSitter queries in Emacs, so there’s a lot of trial and error involved. (especially when it
+comes to indentation logic) My other big problem is that most TreeSitter grammars
+have pretty much no documentation, you one has to learn about their AST format
+via experimentation (e.g. What’s the state of project right now? Well, If you’re feeling adventurous you can easily install the package like this: In Emacs 30 you can you Note: Please refer to the README for usage information, like the various configuration
+options and interactive commands. I’m not sure how much time I’ll be able to spend working on Contributions, suggestions and feedback are most welcome. Keep hacking! I didnt’ name it On a more serious note - there was never an One of my small issues with OCaml is that the standard library is quite spartan.
+Sometimes it misses functions that are quite common in other (similar)
+languages. One such example are functions like Fortunately, that’s finally changing! While perusing the OCaml
+changelog a few days ago, I
+noticed a reference to a recently merged pull
+request that adds the missing As you can imagine there’s nothing fancy about the implementation of the new functions: Pretty standard recursive implementations. If you’re not familiar with
+ It seems the new That’s all I have for you today. Keep hacking! I believe it was Haskell that populirized them. ↩ See https://v2.ocaml.org/manual/tail_mod_cons.html for more details. ↩ While playing with OCaml I was surprised to learn there’s no built-in
+function the convert a string to a list of its characters. Admittedly, that’s
+not something you need very often, but it does come handy from time to time.
+There are many ways to implement such a function ourselves and the one I like
+the most makes use of I went with the name Searching for the signature By the way, it’s also pretty easy to define the inverse operation - namely creating a string out of a list of characters: Note that Keep in mind that lists of chars are quite memory inefficient, as each character
+takes 4 bytes (64 bits) on a modern 64 bit CPU.1 When you factor the pointers to
+the next element in the list, each character is effectively taking 8 bytes,
+which is pretty far from the memory efficiency of a string. The take away is that
+you should avoid using them if you’re dealing with large data sets. That’s all I have for you today. Keep hacking! Lots of programming languages have some built-in range functionality, that’s
+typically used to generate a list/array of integer numbers. Here are
+a couple of examples from Ruby and Clojure: Ruby has a special syntax for ranges ( There are many ways we can implement something similar in OCaml, with the
+simplest one probably being to use The above implementations are super basic and have a few quirks (e.g. the second
+implementation doesn’t allow setting the step and it can’t handle descending
+ranges), but they mostly get the job done. A more full-featured implementation
+would look something like this: This function has a few advantages over the implementations so far: Note: The order of the arguments in the definition matters, as optional
+arguments are only filled in once a positional argument after them has been
+applied. If And here’s how using it in practice looks: Now we’re talking! One funny thing to note is that originally I wanted to use I keep forgetting about this, as I never use those Another funny bit worth sharing, as that one of the test cases for A quick
+search on
+Sherlodoc reveals a ton of similar functions in many OCaml libraries, so clearly
+there’s some use for a That’s all I have for you today. Keep hacking! Every programming language comes with some “batteries” included -
+mostly in the form of its standard library. That’s typically all
+of the functionality that’s available out-of-the-box, without the need
+to install additional libraries. (although the definition varies from
+language to language) Usually standard libraries are pretty similar,
+but I think that OCaml’s a bit “weird” and slightly surprising in some
+regards, so I decided to write down a few thoughts on it and how to
+make the best of it. OCaml’s standard library is called From what I gathered, the compiler authors felt it was the responsibility of the
+users of the language to find (or create) the right libraries for their
+use-cases and preferred to keep the standard library as lean as possible. I get
+their reasoning, but I think this backfired to some extent, as it’s not
+something that many newcomers to a language would expect. The standard library
+was definitely a point of surprise and disappointment for me when playing with
+OCaml for the first time. I still remember how surprised I was that the book
+Real World OCaml began with the instructions
+to replace the built-in standard library with the more full-featured These days, however, I’ve noticed an increased focus on aligning the I’ve written about some of those recent additions in the past - e.g. I think the trend to extend The In particular, it provides the basic operations over the built-in types
+(numbers, booleans, byte sequences, strings, exceptions, references, lists,
+arrays, input-output channels, …) and the standard library modules.
+In OCaml 5.3 Lots of good stuff here! Sure, it’s not anything like the standard libraries of
+languages like Note that unlike the core One thing I found somewhat peculiar at first was the presence of two versions of
+some standard library modules - e.g. The labeled arguments in One notable omission from Not to mention that you probably want to use something different instead. (e.g. Note: The documentation of
+ A lot of people might be wondering whether to use Jane Street’s standard library
+ My advice for most newcomers would be to start with I think Sometimes you might hear mentions of OCaml’s “core library” (not to be confused
+with Well, the “core library” is composed of declarations for built-in types and
+exceptions, plus the module Stdlib that provides basic operations on these
+built-in types. You can learn more about the core library here. Early on in my OCaml journey I’d find references here and there to a library
+named I know OCaml’s What improvements would you like to see there going forward? That’s all I have for you today. Keep hacking! One thing I’ve noticed on my journey to learn OCaml was that reading (text) files wasn’t as
+straightforward as with many other programming languages. To give you some point
+of reference - here’s how easy it is to do this in Ruby: In my beloved Clojure the situation is similar: Basically there are three common operations when
+dealing with text files: When I had to play with files in OCaml for the first time I did some digging
+around and I noticed that many people were either rolling out their own
+ Obviously, this gets the job done, but I was quite surprised such basic
+operations are not covered in the standard library. Turns out, however, that the
+situation has changed recently with OCaml 4.14 with the introduction of the
+module While you still need to roll out your own Be careful, Also of course it doesn’t help with making sure you correctly close your channels and don’t leak them in case of exception. The new functions finally make that a no brainer. Note: Daniel is referring to functions in You can also use One interesting library that I’ve discovered was Iter and it particular its module Iter.IO. It provides a basic interface to manipulate files as iterator of chunks/lines. The iterators take care of opening and closing files properly; every time one iterates over an iterator, the file is opened/closed again. Here’s are a few examples from the library’s documentation: Cool stuff! I’ll make sure to explore further at some point. Perhaps the takeaway for you today is to use libraries like I really wish that someone would update OCaml’s page on file
+manipulation to include coverage of
+the OCaml 4.14 functionality (perhaps I’ll do this myself). I’m guessing this
+outdated page and other legacy docs are sending a lot of people in the wrong
+direction, which was the main reason I’ve decided to write this article. That’s all I have for you today. Keep hacking! Update: The article generated a nice discussion on Reddit that you may want to peruse. https://discuss.ocaml.org/t/ocaml-compiler-development-newsletter-issue-3-june-september-2021/8598#channels-in-the-standard-library-2 ↩ https://discuss.ocaml.org/t/how-do-you-read-the-lines-of-a-text-file/8834/8 ↩ After writing the article I’ve noticed that there’s a When people think of OCaml they are usually thinking of compiling code to a
+binary before they are able to run it. While most OCaml code is indeed compiled
+to binaries, you don’t really need to do this, especially while you’re learning
+the language and are mostly playing with small exercises. Imagine you have something like this in a file named You can compile this if you want, but you can also run it directly with OCaml’s
+interpreter This approach should be familiar to anyone who has ever used a scripting
+language like Perl, Python, Ruby or JavaScript. You can do the same with Of course, one can argue that it’s just as simple to start You can take things one step further like this: Now you can make this file an executable script and run it directly: While this approach should be used mostly when dealing with code that doesn’t
+use external libraries, there’s nothing preventing you from doing so: This should be familiar to everyone who has required any packages in One last thing before we wrap up - you might be wondering about the use
+of The example above works. The one below, however, doesn’t: It will result in a syntax error, because to OCaml this code is basically one expression.
+To fix this will need to add If you know that you can also use I’m not a big fan of this at the top-level, though, as it’s intended to be
+used mostly in bindings: If you have any other tips on running simple OCaml programs, please
+share those in the comments. That’s all I have for you today. Keep hacking! If someone had told me a few months ago I’d be playing with .NET again after a
+15+ years hiatus I probably would have laughed at this.1 Early on in my
+career I played with .NET and Java, and even though .NET had done some things
+better than Java (as it had the opportunity to learn from some early Java
+mistakes), I quickly settled on Java as it was a truly portable environment. I guess everyone who reads my blog knows that in the past few years I’ve been
+playing on and off with OCaml and I think it’s safe to say that it has become
+one of my favorite programming languages - alongside the likes of Ruby and
+Clojure. My work with OCaml drew my attention recently to F#, an ML targeting
+.NET, developed by Microsoft. The functional counterpart of the
+(mostly) object-oriented C#. The newest ML language created… F# 1.0 was officially released in May 2005 by Microsoft Research. It was
+initially developed by Don Syme at Microsoft Research in Cambridge and evolved
+from an earlier research project called “Caml.NET,” which aimed to bring OCaml
+to the .NET platform.2 F# was officially moved from Microsoft Research to
+Microsoft (as part of their developer tooling division) in 2010 (timed
+with the release of F# 2.0). F# has been steadily evolving since those early days and the most recent release
+F# 9.0 was
+released in November 2024. It seems only appropriate that F# would come to my
+attention in the year of its 20th birthday! There were several reasons why I wanted to try out F#: Below you’ll find my initial impressions for several areas. As a member of the ML family of languages, the syntax won’t surprise
+anyone familiar with OCaml. As there are quite few people familiar with
+OCaml, though, I’ll mention that Haskell programmers will also feel right at
+home with the syntax. And Lispers. For everyone else - it’d be fairly easy to pick up the basics. Nothing shocking here, right? Here’s another slightly more involved example: Why don’t you try saving the snippet above in a file called Now you know that F# is a great choice for ad-hoc scripts! Also, running I’m not going to go into great details here, as much of what I wrote about OCaml
+here applies to F# as well.
+I’d also suggest this quick tour of F#
+to get a better feel for its syntax. One thing that made a good impression to me is the focus of the language designers on
+making F# approachable to newcomers, by providing a lot of small quality of life improvements
+for them. Below are few examples, that probably don’t mean much to you, but would mean something
+to people familiar with OCaml: I guess some of those might be controversial, depending on whether you’re a language purist or not,
+but in my book anything that makes MLs more popular is a good thing. Did I also mention it’s easy to work with unicode strings and regular expressions? Often people say that F# is mostly a staging ground for future C# features, and perhaps that’s true.
+I haven’t observed both languages long enough to have my own opinion on the subject, but I was impressed
+to learn that It all changed in 2012 when C#5 launched with the introduction of what has now
+become the popularized F#’s approach to asynchronous programming is a little different from – Isaac Abraham, F# in Action Time will tell what will happen, but I think it’s unlikely that C# will ever be able to fully replace F#. I’ve also found this encouraging comment from 2022 that Microsoft might be willing to invest more in F#: Some good news for you. After 10 years of F# being developed by 2.5 people
+internally and some random community efforts, Microsoft has finally decided to
+properly invest in F# and created a full-fledged team in Prague this
+summer. I’m a dev in this team, just like you I was an F# fan for many years
+so I am happy things got finally moving here. Looking at the changes in F# 8.0 and F 9.0, it seems the new full-fledged team
+has done some great work! It’s hard to assess the ecosystem around F# after such a brief period, but overall it seems to
+me that there are fairly few “native” F# libraries and frameworks out there and most people
+rely heavily on the core .NET APIs and many third-party libraries and frameworks geared towards C#.
+That’s a pretty common setup when it comes to hosted languages in general, so nothing surprising here as well. If you’ve ever used another hosted language (e.g. Scala, Clojure, Groovy) then you probably know what
+to expect. Awesome F# keeps track of popular F# libraries, tools and frameworks. I’ll highlight here the web development and data science libraries: Web Development Data Science I haven’t played much with any of them at this point yet, so I’ll reserve any
+feedback and recommendations for some point in the future. The official documentation is pretty good, although I find it kind of weird that
+some of it is hosted on Microsoft’s site
+and the rest is on https://fsharp.org/ (the site of the F# Software Foundation). I really liked the following parts of the documentation: https://fsharpforfunandprofit.com/ is another good learning resource. (even if it seems a bit dated) I’ve played with the F# plugins for several editors: Overall, Rider and VS Code provide the most (and the most polished) features,
+but the other options were quite usable as well. That’s largely due to the fact
+that the F# LSP server Still, I’ll mention that I found the tooling lacking in some regards: I’m really struggling with VS Code’s keybindings and editing model, so I’ll likely stick with Emacs going forward. Or I’ll finally spend more quality time with neovim! It seems that everyone is using the same code formatter ( Oh, well… It seems that Microsoft are not really particularly invested in
+supporting the tooling for F#, as pretty much all the major projects in this
+space are community-driven. Using AI coding agents (e.g. Copilot) with F# worked pretty well, but I didn’t
+spend much time on this front. In the end of the day any editor will likely do, as long as you’re using LSP. My initial impression of the community is that it’s fairly small, perhaps even
+smaller than that of OCaml. The F# Reddit and Discord (the one listed on
+Reddit) seem like the most active places for F# conversations. There’s supposed
+to be some F# Slack as well, but I couldn’t get an invite for it. (seems the
+automated process for issuing those invites has been broken for a while) I’m still not sure what’s the role Microsoft plays in the community, as I
+haven’t seen much from them overall. For a me a small community is not really a problem, as long as the community is
+vibrant and active. Also - I’ve noticed I always feel more connected to smaller
+communities. Moving from Java to Ruby back in the day felt like night and day as
+far as community engagement and sense of belonging go. I didn’t find many books and community sites/blogs dedicated to F#, but I didn’t
+really expect to in the first place. All in all - I don’t feel qualified to comment much on the F# community at this point. Given the depth and breath of .NET - I guess that sky is the limit for you! Seems to me that F# will be a particularly good fit for data analysis and manipulation, because
+of features like type providers. Probably a good fit for backend services and even full-stack apps, although I haven’t really played
+with the F# first solutions in this space yet. Fable and Elmish make F# a viable option for client-side programming and might offer
+another easy way to sneak F# into your day-to-day work. Note: Historically, Fable has been used to target JavaScript but since Fable
+4, you can also target other languages such as TypeScript, Rust, Python, and
+more. Here’s how easy it is to transpile an F# codebase into something else: Cool stuff! F# was derived from OCaml, so the two languages share a lot of DNA. Early on
+F# made some efforts to support as much of OCaml’s syntax as possible, and it
+even allowed the use of If you ask most people about the pros and cons of F# over OCaml you’ll probably
+get the following answers. F# Pros F# Cons Both F# and OCaml can also target JavaScript runtimes as well - via Fable on
+the F# side and Js_of_ocaml and Melange on the OCaml side. Fable seems like a
+more mature solution at a cursory glance, but I haven’t used any of the three
+enough to be able to offer an informed opinion. In the end of the day both remain two fairly similar robust, yet niche,
+languages, which are unlikely to become very popular in the future. I’m guessing
+working professionally with F# is more likely to happen for most people, as .NET
+is super popular and I can imagine it’d be fairly easy to sneak a bit of F# here
+in there in established C# codebases. One weird thing I’ve noticed with F# projects is that they still use XML project
+manifests, where you have to list the source files manually in the order in
+which they should be compiled (to account for the dependencies between them). I
+am a bit shocked that the compiler can’t handle the dependencies automatically,
+but I guess that’s because in F# there’s not direct mapping between source files
+and modules. At any rate - I prefer the OCaml compilation process (and Dune) way
+more. As my interest in MLs is mostly educational I’m personally leaning towards OCaml, but if I had to build
+web services with an ML language I’d probably pick F#. I also have a weird respect for every language
+with its own runtime, as this means that it’s unlikely that the runtime will force some compromises
+on the language. All in all I liked F# way more than I expected to! In a way it reminded me of my
+experience with Clojure back in the day in the sense that Clojure was the most
+practical Lisp out there when it was released, mostly because of its great
+interop with Java. I have a feeling that if .NET was portable since day 1 probably ClojureCLR would have become
+as popular as Clojure, and likely F# would have developed a bigger community and
+broader usage by now. I’m fairly certain I would have never dabbled in .NET again
+if it hadn’t been for .NET Core, and I doubt I’m the only one. Learning OCaml is definitely not hard, but I think that people interested to learn some ML
+might have an easier time with F#. And, as mentioned earlier, you’ll probably have an
+easier path “production” with it. I think that everyone who has experience with .NET will benefit from learning F#.
+Perhaps more importantly - everyone looking to do more with an ML family language
+should definitely consider F#, as it’s a great language in its own right, that gives
+you access to one of the most powerful programming platforms out there. Let’s not forget about Fable, which makes it possible for you leverage
+F# in JavaScript, Dart, Rust and Python runtimes! So, why F#? Become it’s seriously fun and seriously practical!
+Also if your code compiles - it will probably work the way you expect it to. That’s all I have for you today. Please, share in the comments what do you love about F#! In sane type systems we trust! I had some C# courses in the university and I wrote my bachelor’s thesis in C#. It was a rewrite of Arch Linux’s
-
-OPAM has turned out to be more than just another package manager. It is also
-increasingly central to the demanding workflow of industrial OCaml development,
-since it supports multiple simultaneous (patched) compiler installations,
-sophisticated package version constraints that ensure statically-typed code can
-be recompiled without conflict, and a distributed workflow that integrates
-seamlessly with Git, Mercurial or Darcs version control. OPAM tracks multiple
-revisions of a single package, thereby letting packages rely on older
-interfaces if they need to for long-term support. It also supports multiple
-package repositories, letting users blend the global stable package set with
-their internal revisions, or building completely isolated package universes for
-closed-source products.
-
-Since its initial release, we have been learning from the extensive feedback
-from our users about how they use these features as part of their day-to-day
-workflows. Larger projects like [XenAPI][xapi], the [Ocsigen][] web suite,
-and the [Mirage OS][mir-www] publish OPAM [remotes][opam-remote] that build
-their particular software suites.
-Complex applications such as the [Pfff][] static analysis tool and [Hack][]
-language from Facebook, the [Frenetic][] SDN language and the [Arakoon][]
-distributed key store have all appeared alongside these libraries.
-[Jane Street](https://www.janestreet.com) pushes regular releases of their
-production [Core/Async](http://janestreet.github.io/) suite every couple
-of weeks.
-
-One pleasant side-effect of the growing package database has been the
-contribution of tools from the community that make the day-to-day use of OCaml
-easier. These include the [utop][] interactive toplevel, the [IOCaml][]
-browser notebook, and the [Merlin][] IDE extension. While these tools are an
-essential first step, there's still some distance to go to make the OCaml
-development experience feel fully integrated and polished.
-
-Today, we are kicking off the next phase of evolution of OPAM and starting the
-journey towards building an *OCaml Platform* that combines the OCaml compiler
-toolchain with a coherent workflow for build, documentation, testing and IDE
-integration. As always with OPAM, this effort has been a collaborative effort,
-coordinated by the [OCaml Labs][ocl-www] group in Cambridge and
-[OCamlPro][ocp-www] in France.
-The OCaml Platform builds heavily on OPAM, since it forms the substrate that
-pulls together the tools and facilitates a consistent development workflow.
-We've therefore created this blog on [opam.ocaml.org][] to chart its progress,
-announce major milestones, and eventually become a community repository of all
-significant activity.
-
-Major points:
-
-* **OPAM 1.2 beta available**:
- Firstly, we're announcing **the availability of the OPAM 1.2 beta**,
- which includes a number of new features, hundreds of bug fixes, and pretty
- new colours in the CLI. We really need your feedback to ensure a polished
- release, so please do read the release notes below.
-
-* In the coming weeks, we will provide an overview of what the OCaml Platform is
- (and is not), and describe an example workflow that the Platform can enable.
-
-* **Feedback**: If you have questions or comments as you read these posts,
- then please do join the [platform@lists.ocaml.org][platform-list] and make
- them known to us.
-
-
-[xapi]: http://wiki.xen.org/wiki/XAPI
-[Ocsigen]: http://ocsigen.org
-[mir-www]: http://openmirage.org
-[opam-remote]: https://opam.ocaml.org/doc/Advanced_Usage.html#Handlingofrepositories
-[bunzli-remote]: http://erratique.ch/software/opam/unreleased/
-[mottl-sw]: http://www.ocaml.info/software.html
-[Pfff]: https://github.com/facebook/pfff/wiki/Main
-[Hack]: https://code.facebook.com/posts/264544830379293/hack-a-new-programming-language-for-hhvm/
-[Frenetic]: https://github.com/frenetic-lang/frenetic
-[Arakoon]: http://arakoon.org
-[utop]: https://github.com/diml/utop
-[IOCaml]: https://github.com/andrewray/iocaml
-[Merlin]: https://github.com/the-lambda-church/merlin
-[ocl-www]: http://www.cl.cam.ac.uk/projects/ocamllabs/
-[ocp-www]: http://www.ocamlpro.com
-[opam.ocaml.org]: https://opam.ocaml.org
-[platform-list]: https://lists.ocaml.org/listinfo/platform
-
-
-## Releasing the OPAM 1.2 beta4
-
-We are proud to announce the latest beta of OPAM 1.2. It comes packed with
-[new features][gh-features-12], stability and usability improvements. Here the
-highlights.
-
-### Binary RPMs and DEBs!
-
-We now have binary packages available for Fedora 19/20, CentOS 6/7, RHEL7,
-Debian Wheezy and Ubuntu! You can see the full set at the [OpenSUSE Builder][suse] site and
-[download instructions][suse-dl] for your particular platform.
-
-An OPAM binary installation doesn't need OCaml to be installed on the system, so you
-can initialize a fresh, modern version of OCaml on older systems without needing it
-to be packaged there.
-On CentOS 6 for example:
-
-```
-cd /etc/yum.repos.d/
-wget http://download.opensuse.org/repositories/home:ocaml/CentOS_6/home:ocaml.repo
-yum install opam
-opam init --comp=4.01.0
-```
-
-[suse]: https://build.opensuse.org/package/show/home:ocaml/opam#
-[suse-dl]: http://software.opensuse.org/download.html?project=home:ocaml&package=opam
-
-
-### Simpler user workflow
-
-For this version, we focused on improving the user interface and workflow. OPAM
-is a complex piece of software that needs to handle complex development
-situations. This implies things might go wrong, which is precisely when good
-support and error messages are essential. OPAM 1.2 has much improved stability
-and error handling: fewer errors and more helpful messages plus better state backups
-when they happen.
-
-In particular, a clear and meaningful explanation is extracted from the solver
-whenever you are attempting an impossible action (unavailable package,
-conflicts, etc.):
-
-```
-$ opam install mirage-www=0.3.0
-The following dependencies couldn't be met:
- - mirage-www -> cstruct < 0.6.0
- - mirage-www -> mirage-fs >= 0.4.0 -> cstruct >= 0.6.0
-Your request can't be satisfied:
- - Conflicting version constraints for cstruct
-```
-
-This sets OPAM ahead of many other package managers in terms of
-user-friendliness. Since this is made possible using the tools from
-[irill][irill] (which are also used for [Debian][debian-weather]), we hope that
-this work will find its way into other package managers.
-The extra analyses in the package solver interface are used to improve the
-health of the central package repository, via the [OPAM Weather service][ows].
-
-And in case stuff does go wrong, we added the `opam upgrade --fixup`
-command that will get you back to the closest clean state.
-
-The command-line interface is also more detailed and convenient, polishing and
-documenting the rough areas. Just run `opam 
What makes this approach special?
Setting up the OpenAI client
open Ppx_yojson_conv_lib.Yojson_conv.Primitives
let api_key = Sys.getenv "OPENAI_API_KEY"
module OpenAI = struct
let model = "gpt-4o"
type json = Yojson.Safe.t
let yojson_of_json x = x
module Response = struct
type response_message = { content : string option }
[@@deriving of_yojson] [@@yojson.allow_extra_fields]
type choice = { message : response_message }
[@@deriving of_yojson] [@@yojson.allow_extra_fields]
type response = { choices : choice list }
[@@deriving of_yojson] [@@yojson.allow_extra_fields]
end
module Request = struct
type message = {
role : string;
content : string;
}
[@@deriving yojson_of]
type json_schema = {
name : string;
schema : json;
}
[@@deriving yojson_of]
type response_format = {
typ : string; [@key "type"]
json_schema : json_schema;
}
[@@deriving yojson_of]
type request = {
model : string;
messages : message list;
response_format : response_format;
}
[@@deriving yojson_of]
end
let send ?(debug = false) request =
let body = `Raw ("application/json", request |> Request.yojson_of_request |> Yojson.Safe.to_string) in
let () =
if debug then (
let (`Raw (_, body)) = body in
Printf.eprintf "Body: %s\n" body)
in
let headers = [ "Authorization: Bearer " ^ api_key ] in
match Devkit.Web.http_request ~headers ~body `POST "https://api.openai.com/v1/chat/completions" with
| `Error e -> Error e
| `Ok response ->
try Ok (response |> Yojson.Safe.from_string |> Response.response_of_yojson)
with exn -> Error (Printf.sprintf "error while parsing the response %s: %S" (Printexc.to_string exn) response)
endCreating a structured schema for math problem solving
module Math_reasoning = struct
type step = {
explanation : string;
output : int;
}
[@@deriving jsonschema, yojson]
type math_reasoning = {
steps : step list;
final_answer : int;
}
[@@deriving jsonschema, yojson] [@@yojson.allow_extra_fields]
endUsing the schema in our OpenAI request
let math_tutor_request user_prompt =
{
OpenAI.Request.model = OpenAI.model;
messages =
[
{
role = "system";
content =
"You are a helpful math tutor. You will be provided with a math \
problem, and your goal will be to output a step by step solution, \
along with a final answer. For each step, just provide the output \
as an equation and use the explanation field to detail the \
reasoning.";
};
{ role = "user"; content = user_prompt };
];
response_format =
{
typ = "json_schema";
json_schema =
{
name = "math_reasoning";
schema = Math_reasoning.math_reasoning_jsonschema;
};
};
}Processing the structured response
let extract_steps { OpenAI.Response.message = { content }; _ } =
match content with
| None -> ()
| Some content ->
match content |> Yojson.Safe.from_string |> Math_reasoning.math_reasoning_of_yojson with
| exception Ppx_yojson_conv_lib.Yojson_conv.Of_yojson_error (exn, json) ->
Printf.eprintf "unable to parse response, error %s: %s\n" (Printexc.to_string exn) (Yojson.Safe.to_string json)
| { Math_reasoning.steps; final_answer } ->
List.iteri
(fun i { Math_reasoning.explanation; output } ->
Printf.printf "Step %d: %s\n" i explanation;
Printf.printf "Output: %f\n" output)
steps;
Printf.printf "Final answer: %f\n" final_answerPutting it all together
let run user_prompt =
let request = math_tutor_request user_prompt in
match OpenAI.send request with
| Error e -> Printf.eprintf "error: %s\n" e
| Ok { OpenAI.Response.choices = []; _ } -> Printf.eprintf "no choices returned by OpenAI\n"
| Ok { OpenAI.Response.choices; _ } -> List.iter extract_steps choices
let () =
let user_prompt = Sys.argv.(1) in
run user_prompt$ dune exec ./openai_demo.exe "compute 3+4*17-4/5"
Step 0: First, follow the order of operations, known as PEMDAS (Parentheses, Exponents, Multiplication and Division (from left to right), Addition and Subtraction (from left to right)). Start by handling the multiplication: Calculate 4 * 17 = 68.
Output: 68.000000
Step 1: Next, handle the division: Calculate 4 / 5 = 0.8.
Output: 0.800000
Step 2: Now, handle the addition to and subtraction from the result of the multiplication: Calculate 3 + 68 = 71.
Output: 70.200000
Step 3: Lastly, subtract the result of the division from the addition result: Calculate 71 - 0.8 = 70.2.
Output: 70.200000
Final answer: 70.200000opam switch create . 5.3.0
opam install dune devkit ppx_yojson_conv ppx_yojson_conv_lib ppx_deriving_jsonschema
+
+
+
+
+print_newline ()
+
+read_input ()
+foo() in Python) Of course,
+function application in OCaml is quite different from JavaScript, Python and the like -
+the function arguments are space separated and simply follow the function’s name:foo arg1 arg1 arg3
+() then and why are they needed? I’ll start with the second part of the question.
+In OCaml you can’t really define a function without any parameters - if we have to be super
+precise, every function takes exactly one parameter, no matter how it might look
+at a glance.1 If you try to do something like:let my_print = print_endline "Hello"
+val my_print : unit = ()
+(), which happens to be the single instance of the unit type, used to represent
+the absence of a meaningful value. So, when you want to define a function that doesn’t need
+any parameters the convention is to use a single unit parameter:(* This defines a function that takes unit *)
+let say_hello () =
+ print_endline "Hello, world!"
+
+(* same here *)
+let random_int () =
+ Random.int 100
+() as their
+argument. If you don’t do this - you’d just receive the underlying function
+object as the result:# say_hello;;
+- : unit -> unit = <fun>
+# say_hello ();;
+Hello, world!
+- : unit = ()
+# random_int;;
+- : unit -> int = <fun>
+# random_int ();;
+- : int = 10
+let () = print_endline "Hello, world"
+
+let _ = foo bar
+() is the single value of the unit type that
+indicates the absence of any meaningful value. You can think of it as something like void in
+other languages. What this means is that let () would only match an
+expression that actually return unit (like the various print_* functions) and you’d get a compilation error
+otherwise:$ ocaml foo.ml
+File "./foo.ml", line 1, characters 9-11:
+1 | let () = 10
+ ^^
+Error: The constant 10 has type int but an expression was expected of type unit
+_ on the other hand is a placeholder for “anything” and it will match… anything. It’s useful
+in cases when you just need to discard something. A common example to illustrate it would be something
+like pattern matching on the elements of a list. Consider the following trivial function that returns
+the last item from a list:let rec last = function
+ | [] -> None
+ | [x] -> Some x
+ | _ :: t -> last t
+match, you can use let _ to match against any value, effectively discarding it.let () is often used at the top level of programs to indicate the
+main entry point, while let _ is used when you need to evaluate an expression
+for its side effects but don’t care about its return value. I mostly use let _ when inserting
+debug print expressions in a chain of nested lets. Here’s an example:let foo x =
+ let _ = print_int x in
+ let y = x * 2 in
+ let _ = print_int y in
+ let z = y + 10 in
+ z
+let () in the example above,
+although it seems to me that using let _ is more intention revealing in such cases.open Module_name to a smaller
+scope:(* module alias *)
+module Printf = P
+
+(* open module for subsequent scope *)
+let open Printf in
+let portfolio = List.map parse_line portfolio_lines in
+List.iter (fun (ticker, shares, price) ->
+ printf "%s: %d shares at $%.2f\n" ticker shares price
+) portfolio;
+let total = total_value portfolio in
+printf "Total portfolio value: $%.2f\n" total
+
+(* open module for an expression *)
+List.([1; 2; 3; 4; 5] |> map (fun x -> x * 2) |> fold_left (+) 0);;
+let module P = Printf in
+let portfolio = List.map parse_line portfolio_lines in
+List.iter (fun (ticker, shares, price) ->
+ P.printf "%s: %d shares at $%.2f\n" ticker shares price
+) portfolio;
+let total = total_value portfolio in
+P.printf "Total portfolio value: $%.2f\n" total
+let open Module in over a local module alias and vice versa?Stdlib leaves a lot to be desire when it comes to regular
+expressions. One thing I didn’t discuss back then was that
+the problem is somewhat mitigated by the excellent module
+Scanf, which makes it easy to parse structured data.
+
+
+AAPL).csv file and each
+entry there looks something like APPL,10,150.50. While there
+are many ways to parse this data, I think Scanf is probably
+the simplest and most elegant of them:Scanf.sscanf "AAPL, 10, 150.5" "%[^,], %d, %f" (fun ticker shares price -> (ticker, shares, price));;
+- : string * int * float = ("AAPL", 10, 150.5)
+printf. %[^,] is kind of weird and it means “read string until ,”.
+We can’t use the regular %s format specifier here, as it expects space-separated strings.
+This, however, will work fine with %s:Scanf.sscanf "John Doe 33" "%s %s %d" (fun name surname age -> (name, surname, age));;
+- : string * string * int = ("John", "Doe", 33)
+(* Example portfolio entries as strings *)
+let portfolio_lines = [
+ "AAPL,10,178.23";
+ "GOOG,5,150.50";
+ "MSFT,20,299.01";
+]
+
+(* Parse a line into (ticker, shares, price) *)
+let parse_line line =
+ Scanf.sscanf line "%[^,],%d,%f" (fun ticker shares price ->
+ (ticker, shares, price)
+ )
+
+(* Compute total value of the portfolio *)
+let total_value entries =
+ List.fold_left (fun acc (_ticker, shares, price) ->
+ acc +. float_of_int shares *. price
+ ) 0.0 entries
+
+let () =
+ let open Printf in
+ let portfolio = List.map parse_line portfolio_lines in
+ List.iter (fun (ticker, shares, price) ->
+ printf "%s: %d shares at $%.2f\n" ticker shares price
+ ) portfolio;
+ let total = total_value portfolio in
+ printf "Total portfolio value: $%.2f\n" total
+Scanf has several functions in it and quite a lot of format specifiers that you can
+leverage in various situations.Scanf.Scanning.in_channel. The more general formatted input function reads
+from any scanning buffer and is named bscanf.
+
+
+Scanf.scanf "%s %f\n" (fun name price ->
+ Printf.printf "Item: %s, Price: %.2f\n" name price)
+
+(* input -> Table 100.20 *)
+
+(* output -> Item: Table, Price: 100.20 *)
+- : unit = ()
+Scanf in your OCaml projects and any tips
+you might have about making the best of it.let greeting = "Hello, World!\n"
+let superscript_plus = "\u{207A}";;
+val greeting : string = "Hello, World!\n"
+val superscript_plus : string = "⁺"
+let quoted_greeting = {|"Hello, World!"|}
+let nested = {ext|hello {|world|}|ext};;
+val quoted_greeting : string = "\"Hello, World!\""
+val nested : string = "hello {|world|}"
+
+
+
+{ quoted-string-id | and | quoted-string-id } with the same quoted-string-id on
+both sides. Quoted strings do not interpret any character in a special way but
+requires that the sequence | quoted-string-id } does not occur in the string
+itself. The identifier quoted-string-id is a (possibly empty) sequence of
+lowercase letters and underscores that can be freely chosen to avoid such
+issue.(* regular strings *)
+let greeting = "Hello, World!\n"
+let superscript_plus = "\u{207A}";;
+val greeting : string = "Hello, World!\n"
+val superscript_plus : string = "⁺"
+
+(* quoted strings *)
+let greeting = {|Hello, World!\n|}
+let superscript_plus = {|\u{207A}|};;
+val greeting : string = "Hello, World!\\n"
+val superscript_plus : string = "\\u{207A}"
+'string').let%expect_test _ =
+ let example_html =
+ {|
+ <html>
+ Some random <b>text</b> with a
+ <a href="http://ocaml.org/base">link</a>.
+ And here's another
+ <a href="http://github.com/ocaml/dune">link</a>.
+ And here is <a>link</a> with no href.
+ </html>|}
+ in
+ let soup = Soup.parse example_html in
+ let hrefs = get_href_hosts soup in
+ print_s [%sexp (hrefs : Set.M(String).t)]
+{| |} internally - after all you can easily change this to
+whatever you want.let nested = {xoxo|hello {|world|}|xoxo};;
+val nested : string = "hello {|world|}"
+{|...|} to avoid having to escape backslashes. For example, the
+following expression:let r = Str.regexp {|hello \([A-Za-z]+\)|} in
+ Str.replace_first r {|\1|} "hello world"
+Str.regexp {|\\|}.let r = Str.regexp "hello \\([A-Za-z]+\\)" in
+ Str.replace_first r "\\1" "hello world"
+Str.regexp "\\\\". Pretty ugly, right?{|...|} can be combined with extension
+nodes to
+embed foreign syntax fragments. Those fragments can be interpreted by a
+preprocessor and turned into OCaml code without requiring escaping quotes. A
+syntax shortcut is available for them:
+{%%foo|...|} === [%%foo{|...|}]
+let x = {%foo|...|} === let x = [%foo{|...|}]
+let y = {%foo bar|...|bar} === let y = [%foo{bar|...|bar}]
+
+{%sql|...|} to represent arbitrary SQL statements –
+assuming you have a ppx-rewriter that recognizes the %sql extension."string") are
+unquoted, right? Pretty sure this name doesn’t help with the discoverability of
+this useful feature. Oh, well - naming is hard!
+
+
+Stdlib)Str I’m thinking of strings)#require "str";; in the top-level to load Str.let text = "hello123world" in
+let re = Str.regexp "[0-9]+" in
+if Str.string_match re text 0 then
+ Printf.printf "Matched: %s\n" (matched_string text)
+else
+ Printf.printf "No match\n";;
+
+Str.global_replace (Str.regexp "[0-9]+") "#" "hello123world456";;
+- : string = "hello#world#"
+
+let re = Str.regexp {|hello \([A-Za-z]+\)|} in
+ Str.replace_first re {|\1|} "hello world"
+- : string = "world"
+
+Str.split (Str.regexp "[ \t]+") "hello world";;
+- : string list = ["hello"; "world"]
+Str’s API is quite similar to what you’d find in most imperative
+languages, which is part of the reason the library is frowned upon.{|foo bar|} strange, please consult this article.Str as few people use it these days, especially if they need to do more
+complex tasks with regular expressions. Enter the Re library.
+Before we do something with Re we’ll need to install it:opam install re
+Re is that it supports various flavors of regular expressions:
+
+
+Re.Perl);Re.Posix);Re.Emacs);Re.Glob).utop):#require "re";;
+
+(* basic matching *)
+let re = Re.Perl.re "[0-9]+" |> Re.compile in
+let text = "hello123world" in
+match Re.exec_opt re text with
+| Some group -> Printf.printf "Matched: %s\n" (Re.Group.get group 0)
+| None -> Printf.printf "No match\n"
+;;
+
+(* replace matches *)
+let replace_digits str =
+ let re = Re.Perl.re "[0-9]+" |> Re.compile in
+ Re.replace_string re ~by:"#"
+ str
+;;
+
+print_endline (replace_digits "hello123world456");;
+
+(* use matching groups *)
+let re = Re.Perl.re "(\\w+)-(\\d+)" |> Re.compile in
+match Re.exec_opt re "item-42" with
+| Some group ->
+ let name = Re.Group.get group 1 in
+ let number = Re.Group.get group 2 in
+ Printf.printf "name: %s, number: %s\n" name number
+| None -> print_endline "No match"
+;;
+
+(* composable regular expressions *)
+let word = Re.rep1 Re.wordc;;
+let dash = Re.char '-' ;;
+let digits = Re.rep1 Re.digit;;
+
+let re =
+ Re.seq [word; dash; digits] |> Re.compile
+;;
+
+let input = "hello-123" in
+match Re.exec_opt re input with
+| Some g -> print_endline ("Matched: " ^ Re.Group.get g 0)
+| None -> print_endline "No match"
+;;
+
+(* iterate over all matches *)
+let re = Re.Perl.re "\\d+" |> Re.compile;;
+
+let all_matches str =
+ Re.all re str
+ |> List.iter (fun g -> Printf.printf "Match: %s\n" (Re.Group.get g 0))
+;;
+
+all_matches "a1 b22 c333";;
+Re allows you to program in a more functional way.
+I’ve barely scratched the surface here, as the library has pretty big API,
+that everyone serious about it should eventually explore.
+Below is a list of its most useful combinators:
+
+
+
+
+
+
+
+ Combinator
+ Meaning
+
+
+
+ Re.char cMatch a single char
+
+
+
+ Re.string sMatch exact string
+
+
+
+ Re.alt [r1; r2]Alternation (r1 | r2)
+
+
+
+ Re.seq [r1; r2]Concatenation (r1 r2)
+
+
+
+ Re.rep rZero or more (
+ r*)
+
+
+ Re.rep1 rOne or more (
+ r+)
+
+
+ Re.opt rOptional (
+ r?)
+
+
+ Re.group rCapture group
+
+
+
+
+ Re.compileCompile the regex
+ Str vs Re:
+
+
+
+
+
+
+
+ Feature
+
+ Str (legacy)
+ Re (modern)
+
+ Availability
+ Built-in (kind of)
+ External library (
+ re package)
+
+ API Style
+ Imperative, stateful
+ Functional, composable
+
+
+ Regex Flavor
+ POSIX-like
+ Multiple backends (
+ Perl, Str, Emacs, etc.)
+
+ Unicode support
+ Poor
+ Better (though OCaml string handling is limited)
+
+
+ Match iteration
+ Awkward (
+ search_forward loop)Elegant (
+ Re.all, Re.iter)
+
+ Replacement
+ String only
+ Function or string
+
+
+ Error messages
+ Vague
+ Clear, structured
+
+
+
+Composability
+ Poor (regexp strings only)
+ Excellent (regex combinators like
+ seq, alt)
+
+
+Str only if you want zero dependencies and can tolerate legacy, clunky APIs.Re if you care about code clarity, safety, composability, and are okay with pulling in an external dependency (which you should be in 2025).@tailcall attribute which will trigger a compiler warning if it’s not placed at an actual tail-call.1 It should be used like this:
+
+
+(f [@tailcall]) x y warns if f x y is not a tail-call(* tail-recursive factorial function *)
+let rec fact1 acc x =
+ if x <= 1 then acc else (fact1 [@tailcall]) (acc * x) (x - 1)
+
+(* non tail-recursive factorial function *)
+let rec fact2 x =
+ if x <= 1 then 1 else x * (fact2 [@tailcall]) (x - 1)
+tailcall.ml and compile it with ocamlc:$ ocamlc tailcall.ml
+File "./tailcall.ml", line 5, characters 28-55:
+5 | if x <= 1 then 1 else x * (fact2 [@tailcall]) (x - 1)
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Warning 51 [wrong-tailcall-expectation]: expected tailcall
+fact2 is not tail-recursive, as the tail-call is * instead of fact2.
+Small, but handy feature that helps you ensure your code works the way you intended it to work.
+
+clojure-ts-mode and the various Emacs TreeSitter APIs, I decided to
+start a new experimental project from scratch -
+neocaml, a TreeSitter-powered package for
+OCaml development.1caml-mode is ancient (and probably has to be deprecated), and
+tuareg-mode is a beast. (it’s very powerful, but also very complex) The time
+seems ripe for a modern, leaner, TreeSitter-powered mode for OCaml.
+
+
+
+
+
+neocaml will get farther than
+the other ocaml-ts-modes. Time will tell!
+
+
+treesit-explore-mode and treesit-inspect-mode) and
+reading their bundled queries for font-locking and indentation. As someone who’s
+used to work with Ruby parser I really miss the docs and tools that come with
+something like the Ruby parser library.neocaml kind of works right now,
+but the indentation logic needs a lot of polish, and I’ve yet to implement
+properly structured navigation and some of the newer Emacs TreeSitter APIs
+(e.g. things). We can always “cheat” a bit with the indentation, by
+delegating it to another Emacs package like ocp-indent.el or even Tuareg, but
+I’m hoping to come up with a self-contained TreeSitter implementation in the end
+of the day.M-x package-vc-install <RET> https://github.com/bbatsov/neocaml <RET>
+use-package to both install the package from GitHub
+and configure it:(use-package neocaml
+ :vc (:url "https://github.com/bbatsov/neocaml" :rev :newest))
+neocaml will auto-install the required TreeSitter grammars the
+first time one of the provided major modes is activated.neocaml and how far
+will I be able to push it. Perhaps it will never amount to anything, perhaps it
+will just be a research platform to bring TreeSitter support to Tuareg. And
+perhaps it will become a viable simple, yet modern solution for OCaml
+programming in Emacs. The dream is alive!
+
+neocaml-mode intentionally - many Emacs packages contain more things
+than just major modes, so I prefer a more generic naming. ↩ocaml-mode, so naming something ocaml-ts-mode is not
+strictly needed. But I think an actual ocaml-mode should be blessed by the the maintainers of OCaml,
+hosted in the primary GitHub org, and endorsed as a recommended way to program in OCaml with Emacs.
+Pretty tall order! ↩drop, drop_while, take and
+take_while in the List module.1 What’s weird is that the similar
+Seq module features all those functions
+since OCaml 4.14.List
+functions. It’s interesting that this PR is a follow-up to another PR that was a
+bit more ambitious and was created way back in Oct
+2020. Oh, well - better late than
+never, right?let take n l =
+ let[@tail_mod_cons] rec aux n l =
+ match n, l with
+ | 0, _ | _, [] -> []
+ | n, x::l -> x::aux (n - 1) l
+ in
+ if n < 0 then invalid_arg "List.take";
+ aux n l
+
+let drop n l =
+ let rec aux i = function
+ | _x::l when i < n -> aux (i + 1) l
+ | rest -> rest
+ in
+ if n < 0 then invalid_arg "List.drop";
+ aux 0 l
+
+let take_while p l =
+ let[@tail_mod_cons] rec aux = function
+ | x::l when p x -> x::aux l
+ | _rest -> []
+ in
+ aux l
+
+let rec drop_while p = function
+ | x::l when p x -> drop_while p l
+ | rest -> rest
+@tail_mod_cons - it’s basically tail-call optimization for :: (a.k.a. cons)
+in the final position of a recursive function.2List functions will be shipped with OCaml 5.3. OCaml 5.2 is
+not out at the time I’m writing this, but I’m guessing the PR missed the merge
+window for 5.2. In the mean time - we can continue to rely on the excellent
+Containers
+library
+for that functionality.
+
+List.init:let explode_string s = List.init (String.length s) (String.get s);;
+val explode_string : string -> char list = <fun>
+
+explode_string "hello, world!";;
+- : char list = ['h'; 'e'; 'l'; 'l'; 'o'; ','; ' '; 'w'; 'o'; 'r'; 'l'; 'd'; '!']
+explode_string as the name explode is often used to describe this type of operation (with implode being the name of the inverse operation).string -> char list on Sherlodoc reveals that many libraries offer some version of the above function.
+Perhaps one day we’ll have something like String.to_list in the standard library.let implode_char_list l = String.of_seq (List.to_seq l);;
+val implode_char_list : char list -> string = <fun>
+
+implode_char_list (explode_string "hello");;
+- : string = "hello"
+List.init was added in OCaml 4.06 and String.of_seq was added in OCaml 4.07.# This is Ruby
+
+(1..10).to_a
+# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+
+(1..10).filter(&:even?)
+# => [2, 4, 6, 8, 10]
+
+# And some related functionality, that doesn't use literal ranges
+5.downto(1).to_a # => [5, 4, 3, 2, 1]
+
+10.step(1, -2).to_a # => [10, 8, 6, 4, 2]
+;; This is Clojure
+
+(range 1 10)
+;; => (1 2 3 4 5 6 7 8 9)
+
+(range 1 10 2)
+;; => (1 3 5 7 9)
+
+(range 100 0 -10)
+;; (100 90 80 70 60 50 40 30 20 10)
+.. and ...) and Clojure provides
+a range function to generate ranges (basically a sequences of integer numbers).
+I’m pretty sure you’ve seen something like this. Not the most useful function in
+the world for sure, but it’s handy from time to time.List.init internally:(* simplest/shortest possible version *)
+let range i = List.init i succ
+
+range 10
+- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
+
+(* a version with some boundaries *)
+let range from until =
+ List.init (until - from) (fun i -> i + from)
+
+range 1 10
+- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9]
+
+range 5 15
+- : int list = [5; 6; 7; 8; 9; 10; 11; 12; 13; 14]
+
+(* let's name this -- for good measure *)
+let (--) = range
+
+1 -- 10
+- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9]
+
+(* you can also consider using the names --> and --< for inclusive and exclusive ranges *)
+let range ?(from=1) ?(step=1) until =
+ let cmp = match step with
+ | i when i < 0 -> (>)
+ | i when i > 0 -> (<)
+ | _ -> raise (Invalid_argument "step cannot be 0")
+ in
+ Seq.unfold (function
+ | i when cmp i until -> Some (i, i + step)
+ | _ -> None
+ ) from
+
+
+
+from and step)Seq, meaning it’s lazy?step is the last argument that can never happen.range 10 |> List.of_seq;;
+- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9]
+
+range ~from:10 20 |> List.of_seq;;
+- : int list = [10; 11; 12; 13; 14; 15; 16; 17; 18; 19]
+
+range 100 ~step:10 |> List.of_seq;;
+- : int list = [1; 11; 21; 31; 41; 51; 61; 71; 81; 91]
+
+range ~from:5 20 ~step:5 |> List.of_seq;;
+- : int list = [5; 10; 15]
+
+range ~from:20 5 ~step:(-5) |> List.of_seq;;
+- : int list = [20; 15; 10]
+from and to as
+the parameter names, but I couldn’t use to as it’s a keyword is OCaml:for variable = start_value to end_value do
+ expression
+done
+for loops and Clojure has
+spoiled me with its extremely small set of keywords.Seq.unfold is
+exactly a trivial implementation of range:(* unfold *)
+let () =
+ let range first last =
+ let step i = if i > last then None
+ else Some (i, succ i) in
+ Seq.unfold step first
+ in
+ begin
+ assert ([1;2;3] = !!(range 1 3));
+ assert ([] = !!(range 1 0));
+ end
+;;
+range function in one form or another.Stdlib and it’s the source of much
+“controversy” in the OCaml community. Historically Stdlib was focused only the
+needs of the OCaml compiler (many people called it “the compiler library” for
+that reason) and it was very basic when it comes to the functionality that it
+provided. This is part of the reason why libraries like Jane Street’s Base
+and Core (alternatives to Stdlib), and OCaml Containers (complementary
+extensions to Stdlib) become so popular in the OCaml community.Base and
+Core libraries. I was used to fairly minimal standard library from my time
+with Clojure, but OCaml really outdid Clojure in this regard!Stdlib
+functionality with the expectations of most programmers. That’s obvious when you
+check the recent OCaml releases, that feature many additions to it:
+
+
+Domain, Dynarray, Format, List, Queue, Sys, and Uchar modules).List.take and
+List.drop and I
+think they’ll be quite helpful for newcomers to the language.Stdlib started somewhere around OCaml
+4.07 and has accelerated recently.
+That probably won’t surprise long-term users of OCaml, as the Stdlib
+module didn’t even exist before. I’ll come back to this topic later in the article.Exploring Stdlib
+
+Stdlib module is
+automatically opened at the beginning of each compilation. All components of
+this module can therefore be referred by their short name, without prefixing
+them by Stdlib.Stdlib consists of the following modules:
+
+
+Arg: parsing of command line argumentsArray: array operationsArrayLabels: array operations (with labels)Atomic: atomic referencesBigarray: large, multi-dimensional, numerical arraysBool: boolean valuesBuffer: extensible buffersBytes: byte sequencesBytesLabels: byte sequences (with labels)Callback: registering OCaml values with the C runtimeChar: character operationsComplex: complex numbersCondition: condition variables to synchronize between threadsDomain: Domain spawn/join and domain local variablesDigest: MD5 message digestDynarray: Dynamic arraysEffect: deep and shallow effect handlersEither: either valuesEphemeron: Ephemerons and weak hash tableFilename: operations on file namesFloat: floating-point numbersFormat: pretty printingFun: function valuesGc: memory management control and statistics; finalized valuesHashtbl: hash tables and hash functionsIn_channel: input channelsInt: integersInt32: 32-bit integersInt64: 64-bit integersLazy: deferred computationsLexing: the run-time library for lexers generated by ocamllexList: list operationsListLabels: list operations (with labels)Map: association tables over ordered typesMarshal: marshaling of data structuresMoreLabels: include modules Hashtbl, Map and Set with labelsMutex: locks for mutual exclusionNativeint: processor-native integersOo: object-oriented extensionOption: option valuesOut_channel: output channelsParsing: the run-time library for parsers generated by ocamlyaccPrintexc: facilities for printing exceptionsPrintf: formatting printing functionsQueue: first-in first-out queuesRandom: pseudo-random number generator (PRNG)Result: result valuesScanf: formatted input functionsSeq: functional iteratorsSet: sets over ordered typesSemaphore: semaphores, another thread synchronization mechanismStack: last-in first-out stacksStdLabels: include modules Array, List and String with labelsString: string operationsStringLabels: string operations (with labels)Sys: system interfaceType: type introspectionUchar: Unicode charactersUnit: unit valuesWeak: arrays of weak pointersRuby, Python or Java, but you have the basics covered, at
+least to some extent.Stdlib module, sub-modules are not automatically
+“opened” when compilation starts, or when the toplevel system (e.g. ocaml or
+utop) is launched. Hence it is necessary to use qualified identifiers
+(e.g. List.map) to refer to the functions provided by these modules, or to add
+open directives.List and ListLabels. Both of them have
+the same functions, but the ListLabels module makes heavy use of labeled
+parameters. I’m not sure what’s the reasoning behind this, but I’m guessing this
+was influenced by the Base library, that’s using labels everywhere
+pervasively. Here are a few examples:(* Using List module *)
+let squares_list = List.map (fun x -> x * x) [1; 2; 3; 4; 5]
+(* Result: [1; 4; 9; 16; 25] *)
+
+(* Using ListLabels module *)
+let squares_list_labels = ListLabels.map [1; 2; 3; 4; 5] ~f:(fun x -> x * x)
+(* Result: [1; 4; 9; 16; 25] *)
+
+(* Using List module *)
+let sum_list = List.fold_left (+) 0 [1; 2; 3; 4; 5]
+(* Result: 15 *)
+
+(* Using ListLabels module *)
+let sum_list_labels = ListLabels.fold_left [1; 2; 3; 4; 5] ~init:0 ~f:(+)
+(* Result: 15 *)
+ListLabels make it clear what each parameter means -
+e.g. ~init for the initial value and ~f for the folding function. I’m not
+sure how I feel about labeled arguments in general, as in most cases I don’t
+think they are really needed, but you’ve got the option if you want it.Stdlib is some module for dealing with regular
+expressions. OCaml bundles the (controversial)
+str module, but it’s not part of
+Stdlib and you have to link it to your applications manually:ocamlc other options -I +str str.cma other files
+ocamlopt other options -I +str str.cmxa other files
+re)Stdlib is excellent and I highly
+recommend everyone to peruse it.
+
+Base or Stdlib?Base or Stdlib? I’m guessing there was a time when Base offered bigger
+advantages over Stdlib, but today it’s harder to recommend Base over
+Stdlib. Especially when you factor in the library OCaml
+Containers which provides numerous
+extensions to Stdlib.Stdlib and mix in Containers if
+needed. If you deem they are not enough for you - feel free to explore Base at this point.Base (and Core) are excellent and battle-tested libraries, but I still think
+it’s a good idea for everyone to be familiar with OCaml’s “native” standard library. And for
+all of us to be pushing to make it better, of course.A note about the core library
+
+Core by Jane Street) and you might wonder what’s that exactly.A note about
+
+PervasivesPervasives, that sounded more or less like a standard library.
+Turns out that Pervasives got renamed to Stdlib in OCaml 4.07. Here are a few highlights
+from the release notes of this quite important release:
+
+
+Stdlib, which is
+open by default. This makes it easier to add new modules to the standard
+library without clashing with user-defined modules.Bigarray module is now part of the standard library.Seq, Float were added to the standard library.Pervasives was kept around for a while for backwards compatibility and it seems it’s no
+longer present in OCaml 5.x.Epilogue
+
+Stdlib is often cited as a reason why the language is not popular, and
+I think that’s a valid argument. Still, it seems to me that lately Stdlib has
+been moving in the right direction, and the out-of-the-box OCaml experience got
+improved because of this. I can only hope that this trend will continue and that
+as a result OCaml will become more beginner-friendly and more useful out-of-the-box.# read entire file to string
+content = File.read(filename)
+
+# read lines into an array of lines
+lines = File.readlines(filename)
+
+# process lines one at a time (memory efficient when dealing with large files)
+File.foreach(filename) { |line| puts line }
+;; read entire file into string
+(slurp filename)
+
+;; process lines one at a time
+(use 'clojure.java.io)
+
+(with-open [rdr (reader filename)]
+ (doseq [line (line-seq rdr)]
+ (println line)))
+
+
+
+read_lines function based on the built-in input_line function or using Jane
+Street’s Base library.(* Using Base *)
+open Core.Std
+let contents = In_channel.read_all file
+let lines = In_channel.read_lines file
+
+(* homemade read_lines that gathers all lines in a list *)
+let read_lines name : string list =
+ let ic = open_in name in
+ let try_read () =
+ try Some (input_line ic) with End_of_file -> None in
+ let rec loop acc = match try_read () with
+ | Some s -> loop (s :: acc)
+ | None -> close_in ic; List.rev acc in
+ loop []
+
+let lines = read_lines filename
+
+(* homemade read_lines that processes each line *)
+let read_lines file process =
+ let in_ch = open_in file in
+ let rec read_line () =
+ let line = try input_line in_ch with End_of_file -> exit 0
+ in (* process line in this block, then read the next line *)
+ process line;
+ read_line ();
+in read_line ()
+
+read_lines filename print_endline
+In_channel:1(* read the entire file *)
+let read_file file =
+ In_channel.with_open_bin file In_channel.input_all
+
+(* read lines *)
+let read_lines file =
+ let contents = In_channel.with_open_bin file In_channel.input_all in
+ String.split_on_char '\n' contents
+
+List.iter print_endline (read_lines filename)
+read_file and read_lines
+functions, the implementation is significantly simpler than before. Even more
+importantly, the code is now more reliable as noted by Daniel Bünzli:2
+
+
+input_line is a footgun and has led to more than one bug out there – along with open_in and open_out defaulting to text mode and thus lying by default about your data.input_line will never report an empty final line and performs newline translations if your channel is in text mode. This means you can’t expect to recover the exact file contents you just read by doing String.concat "\n" on the lines you input with input_line.Stdlib. They should not be confused with similarly named
+functions in the new In_channel module.In_channel.input_line to read file contents line by line and
+avoid excessive memory allocation. I’m still missing something like Clojure’s
+line-seq that create a lazy seq from which you can obtain the file lines, but
+I guess this should be doable in OCaml one way or another.3(* Example: copy a file "a" into file "b", removing blank lines: *)
+Iterator.(IO.lines_of "a" |> filter (fun l -> l <> "") |> IO.write_lines "b")
+
+(* By chunks of 4096 bytes: *)
+Iterator.IO.(chunks_of ~size:4096 "a" |> write_to "b")
+
+(* Read the lines of a file into a list: *)
+Iterator.IO.lines "a" |> Iterator.to_list
+Base and
+Containers instead of relying solely on the standard library, perhaps it’s
+not. I’ll leave that for you to decide. If you decide to stick with the standard
+library - I encourage you to peruse the documentation of
+In_channel to learn more about the
+functions it offers and the advantages of using it over the legacy input_line
+function.
+
+read_lines_seq function in the Containers library. ↩hello.ml:let () = print_endline "Hello, world!"
+ocaml:$ ocaml hello.ml
+Hello, world!
+utop
+as well:$ utop hello.ml
+Hello, world!
+utop and then
+do #use "hello.ml" from it. Feel free to do whatever works best for you.#!/usr/bin/env ocaml
+let () = print_endline "Hello, world!"
+$ chmod +x hello.ml
+$ ./hello.ml
+Hello, world!
+#use "topfind";;
+#require "package_name";;
+(* Your OCaml code here *)
+utop.
+topfind is a file that ocamlfind installs
+in the standard library, so that it can be used from the toplevel.
+Don’t forget to install ocamlfind first:opam install ocamlfind
+let () in the simple examples I’ve provided. Technically that’s not
+needed, but you have to keep in mind that when you have multiple expressions in
+the source files with boundaries that the compiler can’t infer you’ll need to
+separate those with ;;. Using let for everything eliminates the need for this:#!/usr/bin/env ocaml
+let () = print_endline "Hello, world!"
+
+let () = print_endline "Bye, world!"
+#!/usr/bin/env ocaml
+print_endline "Hello, world!"
+
+print_endline "Bye, world!"
+;; to help the compiler:#!/usr/bin/env ocaml
+print_endline "Hello, world!";;
+
+print_endline "Bye, world!";;
+; to separate expressions that are
+evaluation only for their side effects (like print_endline) you might be
+tempted to write instead the following:#!/usr/bin/env ocaml
+print_endline "Hello, world!";
+print_endline "Bye, world!";;
+let a = 1 in
+let b = 2 in
+print_int a;
+print_int b;
+a + b
+
+
+
+The Language
+
+printfn "Hello, World!"
+
+let greet name =
+ printfn "Hello, %s!" name
+
+greet "World"
+
+type Shape =
+ | Circle of radius: float
+ | Rectangle of width: float * height: float
+
+let area shape =
+ match shape with
+ | Circle radius -> System.Math.PI * radius * radius
+ | Rectangle (width, height) -> width * height
+
+let circle = Circle 5.0
+let rectangle = Rectangle(4.0, 3.0)
+
+printfn "Circle area: %f" (area circle)
+printfn "Rectangle area: %f" (area rectangle)
+open System
+
+// Sample data - simple sales records
+type SalesRecord = { Date: DateTime; Product: string; Amount: decimal; Region: string }
+
+// Sample dataset
+let sales = [
+ { Date = DateTime(2023, 1, 15); Product = "Laptop"; Amount = 1200m; Region = "North" }
+ { Date = DateTime(2023, 2, 3); Product = "Phone"; Amount = 800m; Region = "South" }
+ { Date = DateTime(2023, 1, 20); Product = "Tablet"; Amount = 400m; Region = "North" }
+ { Date = DateTime(2023, 2, 18); Product = "Laptop"; Amount = 1250m; Region = "East" }
+ { Date = DateTime(2023, 1, 5); Product = "Phone"; Amount = 750m; Region = "West" }
+ { Date = DateTime(2023, 2, 12); Product = "Tablet"; Amount = 450m; Region = "North" }
+ { Date = DateTime(2023, 1, 28); Product = "Laptop"; Amount = 1150m; Region = "South" }
+]
+
+// Quick analysis pipeline
+let salesSummary =
+ sales
+ |> List.groupBy (fun s -> s.Product) // Group by product
+ |> List.map (fun (product, items) -> // Transform each group
+ let totalSales = items |> List.sumBy (fun s -> s.Amount)
+ let avgSale = totalSales / decimal (List.length items)
+ let topRegion =
+ items
+ |> List.groupBy (fun s -> s.Region) // Nested grouping
+ |> List.maxBy (fun (_, regionItems) ->
+ regionItems |> List.sumBy (fun s -> s.Amount))
+ |> fst
+
+ (product, totalSales, avgSale, topRegion))
+ |> List.sortByDescending (fun (_, total, _, _) -> total) // Sort by total sales
+
+// Display results
+salesSummary
+|> List.iter (fun (product, total, avg, region) ->
+ printfn "%s: $%M total, $%M avg, top region: %s"
+ product total avg region)
+Sales.fsx and running it like this:dotnet fsi Sales.fsx
+dotnet fsi by itself
+will pop an F# REPL where you can explore the language at your leisure.// line comments
+(* the classic ML comments are around as well *)
+
+// mutable values
+let mutable x = 5
+x <- 6
+
+// ranges and slices
+let l = [1..2..10]
+name[5..]
+
+// C# method calls look pretty natural
+let name = "FOO".ToLower()
+
+// operators can be overloaded for different types
+let string1 = "Hello, " + "world"
+let num1 = 1 + 2
+let num2 = 1.0 + 2.5
+
+// universal printing
+printfn "%A" [1..2..100]
+async/await (of C# and later JavaScript fame) originated in… F# 2.0.
+
+
+async/await keyword pairing. This feature allowed you to
+write code with all the benefits of hand-written asynchronous code, such as not
+blocking the UI when a long-running process started, yet read like normal
+synchronous code. This async/await pattern has now found its way into many
+modern programming languages such as Python, JS, Swift, Rust, and even C++.async/await
+but achieves the same goal (in fact, async/await is a cut-down version of F#’s
+approach, which was introduced a few years previously, in F#2).
+
+
+Ecosystem
+
+
+
+
+
+
+
+Documentation
+
+
+
+
+Dev Tooling
+
+
+
+
+fsharp-mode)fsautocomplete (naming is hard!) is quite robust and
+any editor with good LSP support gets a lot of functionality for free.
+
+
+fsharp-mode doesn’t use TreeSitter (yet) and doesn’t seem to be very actively developed (looking at the code - it seems it was derived from caml-mode)Fantomas), including the F# team, which is great!
+The linter story in F# is not as great (seems the only popular linter FSharpLint is abandonware these days), but when your
+compiler is so good, you don’t really need a linter as much.Community
+
+Use Cases
+
+# If you want to transpile to JavaScript
+dotnet fable
+
+# If you want to transpile to TypeScript
+dotnet fable --lang typescript
+
+# If you want to transpile to Python
+dotnet fable --lang python
+F# vs OCaml
+
+.ml and .mli file extensions for F# code. Over time
+the languages started to diverge a bit, though.3
+
+
+
+
+
+
+
+
+
+
+
+
+
+ null)
+
+ snake_case way more than camelCase and PascalCase
+
+ Closing thoughts
+
+
+
+pacman, running on Mono. This was way back in 2007. ↩
diff --git a/data/planet/cwn/ocaml-weekly-news-04-feb-2025.md b/data/planet/cwn/ocaml-weekly-news-04-feb-2025.md
new file mode 100644
index 0000000000..702b8e75e2
--- /dev/null
+++ b/data/planet/cwn/ocaml-weekly-news-04-feb-2025.md
@@ -0,0 +1,12 @@
+---
+title: OCaml Weekly News, 04 Feb 2025
+description:
+url: https://alan.petitepomme.net/cwn/2025.02.04.html
+date: 2025-02-04T12:00:00-00:00
+preview_image:
+authors:
+- Caml Weekly News
+source:
+---
+
+
diff --git a/data/planet/cwn/ocaml-weekly-news-04-mar-2025.md b/data/planet/cwn/ocaml-weekly-news-04-mar-2025.md
new file mode 100644
index 0000000000..973d516f6b
--- /dev/null
+++ b/data/planet/cwn/ocaml-weekly-news-04-mar-2025.md
@@ -0,0 +1,12 @@
+---
+title: OCaml Weekly News, 04 Mar 2025
+description:
+url: https://alan.petitepomme.net/cwn/2025.03.04.html
+date: 2025-03-04T12:00:00-00:00
+preview_image:
+authors:
+- Caml Weekly News
+source:
+---
+
+
diff --git a/data/planet/cwn/ocaml-weekly-news-06-may-2025.md b/data/planet/cwn/ocaml-weekly-news-06-may-2025.md
new file mode 100644
index 0000000000..5fd1821478
--- /dev/null
+++ b/data/planet/cwn/ocaml-weekly-news-06-may-2025.md
@@ -0,0 +1,12 @@
+---
+title: OCaml Weekly News, 06 May 2025
+description:
+url: https://alan.petitepomme.net/cwn/2025.05.06.html
+date: 2025-05-06T12:00:00-00:00
+preview_image:
+authors:
+- Caml Weekly News
+source:
+---
+
+
diff --git a/data/planet/cwn/ocaml-weekly-news-07-jan-2025.md b/data/planet/cwn/ocaml-weekly-news-07-jan-2025.md
new file mode 100644
index 0000000000..642de918e7
--- /dev/null
+++ b/data/planet/cwn/ocaml-weekly-news-07-jan-2025.md
@@ -0,0 +1,12 @@
+---
+title: OCaml Weekly News, 07 Jan 2025
+description:
+url: https://alan.petitepomme.net/cwn/2025.01.07.html
+date: 2025-01-07T12:00:00-00:00
+preview_image:
+authors:
+- Caml Weekly News
+source:
+---
+
+
diff --git a/data/planet/cwn/ocaml-weekly-news-08-apr-2025.md b/data/planet/cwn/ocaml-weekly-news-08-apr-2025.md
new file mode 100644
index 0000000000..7afc0552ce
--- /dev/null
+++ b/data/planet/cwn/ocaml-weekly-news-08-apr-2025.md
@@ -0,0 +1,12 @@
+---
+title: OCaml Weekly News, 08 Apr 2025
+description:
+url: https://alan.petitepomme.net/cwn/2025.04.08.html
+date: 2025-04-08T12:00:00-00:00
+preview_image:
+authors:
+- Caml Weekly News
+source:
+---
+
+
diff --git a/data/planet/cwn/ocaml-weekly-news-11-feb-2025.md b/data/planet/cwn/ocaml-weekly-news-11-feb-2025.md
new file mode 100644
index 0000000000..e162c75e46
--- /dev/null
+++ b/data/planet/cwn/ocaml-weekly-news-11-feb-2025.md
@@ -0,0 +1,12 @@
+---
+title: OCaml Weekly News, 11 Feb 2025
+description:
+url: https://alan.petitepomme.net/cwn/2025.02.11.html
+date: 2025-02-11T12:00:00-00:00
+preview_image:
+authors:
+- Caml Weekly News
+source:
+---
+
+
diff --git a/data/planet/cwn/ocaml-weekly-news-11-mar-2025.md b/data/planet/cwn/ocaml-weekly-news-11-mar-2025.md
new file mode 100644
index 0000000000..a76c7fc460
--- /dev/null
+++ b/data/planet/cwn/ocaml-weekly-news-11-mar-2025.md
@@ -0,0 +1,12 @@
+---
+title: OCaml Weekly News, 11 Mar 2025
+description:
+url: https://alan.petitepomme.net/cwn/2025.03.11.html
+date: 2025-03-11T12:00:00-00:00
+preview_image:
+authors:
+- Caml Weekly News
+source:
+---
+
+
diff --git a/data/planet/cwn/ocaml-weekly-news-14-jan-2025.md b/data/planet/cwn/ocaml-weekly-news-14-jan-2025.md
new file mode 100644
index 0000000000..a05c1994a2
--- /dev/null
+++ b/data/planet/cwn/ocaml-weekly-news-14-jan-2025.md
@@ -0,0 +1,12 @@
+---
+title: OCaml Weekly News, 14 Jan 2025
+description:
+url: https://alan.petitepomme.net/cwn/2025.01.14.html
+date: 2025-01-14T12:00:00-00:00
+preview_image:
+authors:
+- Caml Weekly News
+source:
+---
+
+
diff --git a/data/planet/cwn/ocaml-weekly-news-15-apr-2025.md b/data/planet/cwn/ocaml-weekly-news-15-apr-2025.md
new file mode 100644
index 0000000000..dc707ed3d8
--- /dev/null
+++ b/data/planet/cwn/ocaml-weekly-news-15-apr-2025.md
@@ -0,0 +1,12 @@
+---
+title: OCaml Weekly News, 15 Apr 2025
+description:
+url: https://alan.petitepomme.net/cwn/2025.04.15.html
+date: 2025-04-15T12:00:00-00:00
+preview_image:
+authors:
+- Caml Weekly News
+source:
+---
+
+
diff --git a/data/planet/cwn/ocaml-weekly-news-18-feb-2025.md b/data/planet/cwn/ocaml-weekly-news-18-feb-2025.md
new file mode 100644
index 0000000000..1aba1b841f
--- /dev/null
+++ b/data/planet/cwn/ocaml-weekly-news-18-feb-2025.md
@@ -0,0 +1,12 @@
+---
+title: OCaml Weekly News, 18 Feb 2025
+description:
+url: https://alan.petitepomme.net/cwn/2025.02.18.html
+date: 2025-02-18T12:00:00-00:00
+preview_image:
+authors:
+- Caml Weekly News
+source:
+---
+
+
diff --git a/data/planet/cwn/ocaml-weekly-news-18-mar-2025.md b/data/planet/cwn/ocaml-weekly-news-18-mar-2025.md
new file mode 100644
index 0000000000..2b19e69cae
--- /dev/null
+++ b/data/planet/cwn/ocaml-weekly-news-18-mar-2025.md
@@ -0,0 +1,12 @@
+---
+title: OCaml Weekly News, 18 Mar 2025
+description:
+url: https://alan.petitepomme.net/cwn/2025.03.18.html
+date: 2025-03-18T12:00:00-00:00
+preview_image:
+authors:
+- Caml Weekly News
+source:
+---
+
+
diff --git a/data/planet/cwn/ocaml-weekly-news-21-jan-2025.md b/data/planet/cwn/ocaml-weekly-news-21-jan-2025.md
new file mode 100644
index 0000000000..9a78291635
--- /dev/null
+++ b/data/planet/cwn/ocaml-weekly-news-21-jan-2025.md
@@ -0,0 +1,12 @@
+---
+title: OCaml Weekly News, 21 Jan 2025
+description:
+url: https://alan.petitepomme.net/cwn/2025.01.21.html
+date: 2025-01-21T12:00:00-00:00
+preview_image:
+authors:
+- Caml Weekly News
+source:
+---
+
+
diff --git a/data/planet/cwn/ocaml-weekly-news-22-apr-2025.md b/data/planet/cwn/ocaml-weekly-news-22-apr-2025.md
new file mode 100644
index 0000000000..f217c42260
--- /dev/null
+++ b/data/planet/cwn/ocaml-weekly-news-22-apr-2025.md
@@ -0,0 +1,12 @@
+---
+title: OCaml Weekly News, 22 Apr 2025
+description:
+url: https://alan.petitepomme.net/cwn/2025.04.22.html
+date: 2025-04-22T12:00:00-00:00
+preview_image:
+authors:
+- Caml Weekly News
+source:
+---
+
+
diff --git a/data/planet/cwn/ocaml-weekly-news-25-feb-2025.md b/data/planet/cwn/ocaml-weekly-news-25-feb-2025.md
new file mode 100644
index 0000000000..25559f5308
--- /dev/null
+++ b/data/planet/cwn/ocaml-weekly-news-25-feb-2025.md
@@ -0,0 +1,12 @@
+---
+title: OCaml Weekly News, 25 Feb 2025
+description:
+url: https://alan.petitepomme.net/cwn/2025.02.25.html
+date: 2025-02-25T12:00:00-00:00
+preview_image:
+authors:
+- Caml Weekly News
+source:
+---
+
+
diff --git a/data/planet/cwn/ocaml-weekly-news-25-mar-2025.md b/data/planet/cwn/ocaml-weekly-news-25-mar-2025.md
new file mode 100644
index 0000000000..9d23093851
--- /dev/null
+++ b/data/planet/cwn/ocaml-weekly-news-25-mar-2025.md
@@ -0,0 +1,12 @@
+---
+title: OCaml Weekly News, 25 Mar 2025
+description:
+url: https://alan.petitepomme.net/cwn/2025.03.25.html
+date: 2025-03-25T12:00:00-00:00
+preview_image:
+authors:
+- Caml Weekly News
+source:
+---
+
+
diff --git a/data/planet/cwn/ocaml-weekly-news-28-jan-2025.md b/data/planet/cwn/ocaml-weekly-news-28-jan-2025.md
new file mode 100644
index 0000000000..5f30ae5a01
--- /dev/null
+++ b/data/planet/cwn/ocaml-weekly-news-28-jan-2025.md
@@ -0,0 +1,12 @@
+---
+title: OCaml Weekly News, 28 Jan 2025
+description:
+url: https://alan.petitepomme.net/cwn/2025.01.28.html
+date: 2025-01-28T12:00:00-00:00
+preview_image:
+authors:
+- Caml Weekly News
+source:
+---
+
+
diff --git a/data/planet/cwn/ocaml-weekly-news-29-apr-2025.md b/data/planet/cwn/ocaml-weekly-news-29-apr-2025.md
new file mode 100644
index 0000000000..bbd2a67b8d
--- /dev/null
+++ b/data/planet/cwn/ocaml-weekly-news-29-apr-2025.md
@@ -0,0 +1,12 @@
+---
+title: OCaml Weekly News, 29 Apr 2025
+description:
+url: https://alan.petitepomme.net/cwn/2025.04.29.html
+date: 2025-04-29T12:00:00-00:00
+preview_image:
+authors:
+- Caml Weekly News
+source:
+---
+
+
diff --git a/data/planet/dinosaure/bstr-a-synthetic-library-for-bigstrings.md b/data/planet/dinosaure/bstr-a-synthetic-library-for-bigstrings.md
new file mode 100644
index 0000000000..de533d5372
--- /dev/null
+++ b/data/planet/dinosaure/bstr-a-synthetic-library-for-bigstrings.md
@@ -0,0 +1,12 @@
+---
+title: Bstr, a synthetic library for bigstrings
+description:
+url: https://blog.osau.re/articles/bstr.html
+date: 2025-04-29T00:00:00-00:00
+preview_image:
+authors:
+- Romain Calascibetta
+source:
+---
+
+A small library to manipulate bstr
diff --git a/data/planet/gallium/florian-compiler-weekly-11-december-2023.md b/data/planet/gallium/florian-compiler-weekly-11-december-2023.md
index 64a471d78c..56841d9aaf 100644
--- a/data/planet/gallium/florian-compiler-weekly-11-december-2023.md
+++ b/data/planet/gallium/florian-compiler-weekly-11-december-2023.md
@@ -17,9 +17,9 @@ release of OCaml 5.1.1, some review work on occurrences analysis for
OCaml projects and a bit of on-going work on structured logs.
OCaml 5.1.0 has been released nearly three months ago, in those months we have discovered a few significant bugs that were impeding the @@ -179,7 +179,7 @@ increase to a less dramatic factor 6.
experimental runtime. And while waiting for a better solution, the collection of bug fixes integrated in OCaml 5.1.1 should made it possible to use numerical code in OCaml. -Beyond the release of OCaml 5.1.1, I have been working on reviewing PRs.
With Gabriel Scherer and Ulysse Gérard, we spend an afternoon reading @@ -248,6 +248,3 @@ identifiers are as fresh as we need them to be:
module New_record(Root:Def)(): Record with type root := Root.id module New_sum(Root:Def)(): Sum with type root := Root.id - - - diff --git a/data/planet/gallium/florians-compiler-weekly-13-january-2025.md b/data/planet/gallium/florians-compiler-weekly-13-january-2025.md new file mode 100644 index 0000000000..4423073914 --- /dev/null +++ b/data/planet/gallium/florians-compiler-weekly-13-january-2025.md @@ -0,0 +1,369 @@ +--- +title: "Florian\u2019s compiler weekly, 13 January 2025" +description: +url: https://gallium.inria.fr/blog/florian-cw-2025-01-13 +date: 2025-01-13T08:00:00-00:00 +preview_image: +authors: +- GaGallium +source: +--- + + + +This series of blog post aims to give a short weekly glimpse into my +(Florian Angeletti) work on the OCaml compiler. This week subject is my +personal retrospective on the release of OCaml 5.3.0.
+ + + + + +The beginning of 2025 and the release of OCaml 5.3 feels like a good +period for some introspection on my work on the compiler during this +release.
+Looking backk at the changelog, I have participated to more or less +50 changes in the 5.3 release. Most of those changes (~40) can be +classified into five major themes:
+Retrospecting, I have been quite busy this release with background +work, and I still have more background work planned for OCaml 5.4. +Hopefully, this background work will bear more visible fruits in the +next releases.
+The first theme for this release is recurrent for me, and I hope that +I would be able to spend even more time on this subject soon. Indeed for +this release, most of the error message improvements were relatively +quick improvements on various topics (first class modules, function +labelled arguments, functors and type clashes). Nevertheless, I still +have many larger projects planned for improving error messages in the +longer terms, in particular:
+For more details, here are the corresponding changelog entries +extracted from the 5.3 changelog:
+#12980: +Explain type mismatch involving first-class modules by including the +module level error message (Florian Angeletti, review by Vincent +Laviron)
#12985, +#12988: Better +error messages for partially applied functors. (Florian Angeletti, +report by Arthur Wendling, review by Gabriel Scherer)
#13034, +#13260: Better +error messages for mismatched function labels (Florian Angeletti, report +by Daniel Bünzli, review by Gabriel Scherer and Samuel Vivien)
#13341: +a warning when the pattern-matching compiler pessimizes code because +side-effects may mutate the scrutinee during matching. (This warning is +disabled by default, as this rarely happens and its performance impact +is typically not noticeable.) (Gabriel Scherer, review by Nick Roberts, +Florian Angeletti and David Allsopp)
#13255: +Re-enable warning 34 for unused locally abstract types (Nick Roberts, +review by Chris Casinghino and Florian Angeletti)
#12182: +Improve the type clash error message. For example, this message: This +expression has type … is changed into: The constant “42” has type … +(Jules Aguillon, review by Gabriel Scherer and Florian +Angeletti)
#13170:
+Fix a bug that would result in some floating alerts
+[@@@alert ...] incorrectly triggering Warning 53. (Nicolás
+Ojeda Bär, review by Chris Casinghino and Florian Angeletti)
#13203: +Do not issue warning 53 if the compiler is stopping before attributes +have been accurately marked. (Chris Casinghino, review by Florian +Angeletti)
This release I ended up spending a sizeable amount of time
+refactoring or improving the various display mechanism in the compiler.
+In particular, OCaml 5.3 comes with a new internal format for error
+messages, a new graphical debugger printer for type expressions, and the
+correction on many smaller printing bugs for booleans and the
+mod operator.
This trend will continue in OCaml 5.4 since I have already launched a +project on updating the formatting of error messages and warnings, and I +am hoping to finally integrate my work on structured diagnostics in this +version of OCaml.
+However, beyond this (significant) piece of work, don’t really have +longer plans on this subject.
+The related changelog entries for OCaml 5.3 are:
+#13049: +graphical debugging printer for types (Florian Angeletti, review by +Gabriel Scherer)
#13169,
+#13311:
+Introduce a document data type for compiler messages rather than relying
+on Format.formatter -> unit closures. (Florian
+Angeletti, review by Gabriel Scherer)
#12891: +Improved styling for initial prompt (Florian Angeletti, review by +Gabriel Scherer)
#13263, +#13560: fix +printing true and false in toplevel and error messages (no more +unexpected #true) (Florian Angeletti, report by Samuel Vivien, review by +Gabriel Scherer)
#13151, +name conflicts explanation as a footnote (Florian Angeletti, review by +Gabriel Scherer)
#13053:
+Improved display of builtin types such as _ list when
+aliased. (Samuel Vivien, review by Florian Angeletti)
#13336:
+compiler-libs, split the Printtyp in three to only keep
+“user-friendly” functions in the Printtyp module. (Florian
+Angeletti, review by Gabriel Scherer)
#12888:
+fix printing of uncaught exceptions in .cmo files passed on
+the command-line of the toplevel. (Nicolás Ojeda Bär, review by Florian
+Angeletti, report by Daniel Bünzli)
#13099: +Fix erroneous loading of cmis for some module type errors. (Nick +Roberts, review by Florian Angeletti)
#13251: +Register printer for errors in Emitaux (Vincent Laviron, review by Miod +Vallat and Florian Angeletti)
#13391,
+#13551: fix a
+printing bug with -dsource when using raw literal inside a
+locally abstract type constraint
+(i.e. let f: type \#for. ...) (Florian Angeletti, report by
+Nick Roberts, review by Richard Eisenberg)
#13603,
+#13604: fix
+source printing in the presence of the escaped raw identifier
+\#mod. (Florian Angeletti, report by Chris Casinghino,
+review by Gabriel Scherer)
Another important subject during this release was the improvement of +the metadata generated for Merlin. OCaml 5.3.0 metadata now track more +accurately identifiers across implementation and interfaces, and record +precisely how implementation identifiers were matched to interface +identifiers in a module. For the next release, I am planning on +improving tooling integration with merlin by reducing the difference +between merlin typechecker and the compiler typechecker. The exact +specification can be found following the link in the corresponding +changelog entry:
+#13308: +keep track of relations between declaration in the cmt files. This is +useful information for external tools for navigation and analysis +purposis. (Ulysse Gérard, Florian Angeletti, review by Florian Angeletti +and Gabriel Scherer)
#13286:
+Distinguish unique identifiers Shape.Uid.t according to
+their provenance: either an implementation or an interface. (Ulysse
+Gérard, review by Florian Angeletti and Leo White)
In a similar way, I have also worked on improving the compatibility +of the internal compiler library with ppxlib and MetaOCaml, and +implemented a new command line flag to improve the backward +compatibility of the lexer:
+#11129,
+#11148:
+enforce that ppxs do not produce parsetrees with an empty
+list of universally quantified type variables
+(. int -> int instead of
+'a . int -> int') (Florian Angeletti, report by Simmo
+Saan, review by Gabriel Scherer)
#13471:
+add -keywords <version?+list> flag to define the list
+of keywords recognized by the lexer, for instance
+-keywords 5.2 disable the effect keyword.
+(Florian Angeletti, review by Gabriel Scherer)
#13257: +integrate MetaOCaml in the Menhir grammar to ease MetaOCaml maintenance. +This is a purely internal change: there is no support in the lexer, so +no change to the surface OCaml grammar. (Oleg Kiselyov, Gabriel Scherer +and Florian Angeletti, review by Jeremy Yallop)
As an author, I have documented the modest Unicode support introduced +in 5.3 and found the time to write down the compiler release cycles. +However, most on my time working on the documentation has been focused +on reviewing PRs, ranging from a small change to the manual css to an +update to the manual section on polymorphic recursion proposed by a new +contributor.
+#13668: +Document the basic support for unicode identifiers and the switch to +UTF-8 encoded Unicode text for OCaml source file (Florian Angeletti, +review by Nicolás Ojeda Bär and Daniel Bünzli)
#12949:
+document OCaml release cycles and version strings in
+release-info/introduction.md. (Florian Angeletti, review by
+Fabrice Buoro, Kate Deplaix, Damien Doligez, and Gabriel
+Scherer)
#12298: +Manual: emphasize that Bigarray.int refers to an OCaml integer, which +does not match the C int type. (Edwin Török, review by Florian +Angeletti)
#12868: +Manual: simplify style colours of the post-processed manual and API HTML +pages, and fix the search button icon (Yawar Amin, review by Simon +Grondin, Gabriel Scherer, and Florian Angeletti)
#12976: +Manual: use webman/version/ * .htmlandwebman/version/api/ +for OCaml.org HTML manual generation (Shakthi Kannan, review by Hannes +Mehnert, and Florian Angeletti)
#13295: +Use syntax for deep effect handlers in the effect handlers manual page. +(KC Sivaramakrishnan, review by Anil Madhavapeddy, Florian Angeletti and +Miod Vallat)
#13469, +#13474, #13535: Document +that [Hashtbl.create n] creates a hash table with a default minimal +size, even if [n] is very small or negative. (Antonin Décimo, Nick +Bares, report by Nikolaus Huber and Jan Midtgaard, review by Florian +Angeletti, Anil Madhavapeddy, Gabriel Scherer, and Miod Vallat)
#13666: +Rewrite parts of the example code around nested lists in Chapter 6 +(Polymorphism and its limitations -> Polymorphic recursion) giving +the “depth” function [in the non-polymorphically-recursive part of the +example] a much more sensible behavior; also fix a typo and some +formatting. (Frank Steffahn, review by Florian Angeletti)
At last, but not least, I spent many hours this release fixing +internal errors due to inconsistent type constraints in the module +systems. I have also reviewed many bug fixes and in particular a serie +of issues in the typechecker related to the handling of non-injective +type parameters.
+#12959, +#13055: Avoid +an internal error on recursive module type inconsistency (Florian +Angeletti, review by Jacques Garrigue and Gabriel Scherer)
#13388,
+#13540: raises
+an error message (and not an internal compiler error) when two local
+substitutions are incompatible (for instance
+module type S:=sig end type t:=(module S)) (Florian
+Angeletti, report by Nailen Matschke, review by Gabriel Scherer, and Leo
+White)
#13185, +#13192: Reject +type-level module aliases on functor parameter inside signatures. +(Jacques Garrigue, report by Richard Eisenberg, review by Florian +Angeletti)
#13306: +An algorithm in the type-checker that checks two types for equality +could sometimes, in theory, return the wrong answer. This patch fixes +the oversight. No known program triggers the bug. (Richard Eisenberg, +review by Florian Angeletti)
#13495, +#13514: Fix +typechecker crash while typing objects (Jacques Garrigue, report by +Nicolás Ojeda Bär, review by Nicolas Ojeda Bär, Gabriel Scherer, Stephen +Dolan, Florian Angeletti)
#13579, +#13583: +Unsoundness involving non-injective types + gadts (Jacques Garrigue, +report by @v-gb, review +by Richard Eisenberg and Florian Angeletti)
#13598: +Falsely triggered warning 56 [unreachable-case] This was caused by +unproper protection of the retyping function. (Jacques Garrigue, report +by Tõivo Leedjärv, review by Florian Angeletti)
Beyond those major themes, I also had my hands or eyes at few of the +changes in the language, the standard library and the compiler build +system.
+In term of language features, I partipated to the review for the +newly introduced syntax for effect handler (#12309, #13158) on the +type system and documentation (#13295) +sides.
+let with_gen f = match f () with
+| effect Random_float, k -> Effect.Deep.continue k (Random.float 1.)
+| x -> xI also finalized the support the new modest support of utf-8 encoded +Unicode source files in OCaml 5.3 (#11736, #12664, #13628)
+type saison = Printemps | Été | Automne | Hiverand documented it (#13668).
+On the pure type system side, I have participated to the review on +the extended support for annotating types in GADT pattern. (#11891, #12507). It is +now possible to give a name to all type variables introduced by pattern +matching on a GADT constructor
+type _ t =
+| S: string -> char array t
+| A: 'a array -> 'a array t
+
+let len (type a) (x:a t) = match x with
+| A (type a) (x:a array) -> Array.length x
+| S x -> String.length xwhereas it was only possible to name existentially quantified type +variables before.
+I have reviewed two standard library Pull Requests (PR)
+#13168:
+In Array.shuffle, clarify the code that validates the result of the
+user-supplied function rand, and improve the error message
+that is produced when this result is invalid. (François Pottier, review
+by Florian Angeletti, Daniel Bünzli and Gabriel Scherer)
#13296: +Add mem, memq, find_opt, find_index, find_map and find_mapi to Dynarray. +(Jake H, review by Gabriel Scherer and Florian Angeletti)
and authored one PR exposing support for a hidden feature of the +Format module:
+During the release, I also happened to review some build system +changes:
+#13285: +continue the merge of the sub-makefiles into the root Makefile started +with #11243, +#11248, #11268, #11420, #11675, #12198, #12321, #12586, #12616, #12706 and #13048. +(Sébastien Hinderer, review by David Allsopp and Florian +Angeletti)
(breaking change) #13070: On
+Windows, when configured with bootstrapped flexdll, don’t add +flexdll
+to the search path when -nostdlib is specified (which then means
+-L path-to-flexdll no longer gets passed to the system
+linker). (David Allsopp, review by Florian Angeletti)
Advent of Code is an annual Advent calendar +featuring small programming puzzles created by Eric Wastl, which has been running +every December since 2015. Being the puzzle-lovers we are, a bunch of us at +Jane Street participate in Advent of Code every year.
+ + diff --git a/data/planet/janestreet/how-we-accidentally-built-a-better-build-system-for-ocaml.md b/data/planet/janestreet/how-we-accidentally-built-a-better-build-system-for-ocaml.md new file mode 100644 index 0000000000..483076d5bc --- /dev/null +++ b/data/planet/janestreet/how-we-accidentally-built-a-better-build-system-for-ocaml.md @@ -0,0 +1,20 @@ +--- +title: How we accidentally built a better build system for OCaml +description: "A \u201Cbuild system\u201D is one of the most important tools in a developer\u2019stoolbox. + Roughly, it figures out how to create runnable programs froma bunch of different..." +url: https://blog.janestreet.com/how-we-accidentally-built-a-better-build-system-for-ocaml-index/ +date: 2025-01-24T00:00:00-00:00 +preview_image: https://blog.janestreet.com/how-we-accidentally-built-a-better-build-system-for-ocaml-index/dune-jenga.png +authors: +- Jane Street Tech Blog +source: +--- + +A “build system” is one of the most important tools in a developer’s +toolbox. Roughly, it figures out how to create runnable programs from +a bunch of different source files by calling out to the compiler, +setting up and executing test suites, and so on. Because you interact +with it daily, above all it has to be fast – +but it also has to be flexible.
+ + diff --git a/data/planet/melange/announcing-melange-5.md b/data/planet/melange/announcing-melange-5.md new file mode 100644 index 0000000000..7fa96573eb --- /dev/null +++ b/data/planet/melange/announcing-melange-5.md @@ -0,0 +1,217 @@ +--- +title: Announcing Melange 5 +description: +url: https://melange.re/blog/posts/announcing-melange-5 +date: 2025-03-05T00:00:00-00:00 +preview_image: +authors: +- Melange Blog +source: +--- + +We are excited to announce the release of Melange 5, the compiler for OCaml +that targets JavaScript.
+A lot of goodies went into this release! While our focus was mostly on features
+that make it easy to express more JavaScript constructs and supporting OCaml
+5.3, we also managed to fit additional improvements in the release: better
+editor support for Melange externals, code generation improvements, and
+better compiler output for generated JS. The most notable feature we're
+shipping in Melange 5 is support for JavaScript's dynamic
+import(),
+which we'll describe in detail below.
Read on for the highlights.
+import() without sacrificing type safety Support for JavaScript's dynamic import() is probably what I'm most excited
+about in this Melange release. In Melange 5, we're releasing support for
+JavaScript's import() via a new function in melange.js, Js.import: 'a -> 'a promise. I gave a small preview of dynamic import() during my Melange
+talk at Fun OCaml
+2024.
Js.import is type-safe and build system-compatible. Let's break it
+down:
import()s in Melange work with Dune
+out of the box: as usual, you must specify your (library ..) dependencies
+in the dune file. At compile time, Melange will be aware of the
+dynamically imported module locations to emit the arguments to
+import("/path/to/module.js) automatically.import ..
+declarations if the values are exclusively used through Js.import(..).The example below makes it clear: we import the entire Stdlib.Int module,
+specify its type signature, and observe that no static imports appear in the
+resulting JavaScript:
// dynamic_import_int.re
+
+module type int = (module type of Int);
+
+let _: Js.Promise.t(unit) = {
+ Js.import((module Stdlib.Int): (module int))
+ |> Js.Promise.then_((module Int: int) =>
+ Js.Promise.resolve(Js.log(Int.max))
+ );
+};
+// dynamic_import_int.js
+
+import("melange/int.js").then(function (Int) {
+ return Promise.resolve((console.log(Int.max), undefined));
+});
+One of Melange's primary operating principles is the ability to support
+seamless interop with JavaScript constructs. As such, we implemented import()
+in a way that also allows importing JS modules dynamically: you can call
+Js.import on JavaScript values declared with external. The abstractions
+compose nicely to produce the expected result. Check out this example of
+dynamically importing React.useEffect:
// dynamically_imported_useEffect.re
+
+[@mel.module "react"]
+external useEffect:
+([@mel.uncurry] (unit => option(unit => unit))) => unit = "useEffect";
+
+let dynamicallyImportedUseEffect = Js.import(useEffect);
+And the JS output:
+// dynamically_imported_useEffect.js
+
+const dynamicallyImportedUseEffect = import("react").then(function (m) {
+ return m.useEffect;
+});
+
+export {
+ dynamicallyImportedUseEffect,
+}
+@mel.as in variants This release of Melange includes a major feature that improves the compilation +of variants, including really good support for representing discriminated +unions, +a common pattern to represent polymorphic objects with a discriminator in +JavaScript/TypeScript.
+In melange-re/melange#1189, +we introduced support for 2 attributes in OCaml types that define variants:
+@mel.as Specifying [@mel.as ".."] changes the variant emission in JavaScript to that
+string value.
type t =
+ | [@mel.as "World"] Hello;
+
+let t = Hello
+const t = /* Hello */ "World";
+@mel.tag A @mel.as variant type combined with @mel.tag allows expressing
+discriminated unions in an unobtrusive way:
[@mel.tag "kind"]
+type t =
+ | [@mel.as "Foo"] Foo({ a: string, b: string, })
+ | [@mel.as "Bar"] Bar({ c: string, d: string, });
+
+let x = Foo({ a: "a", b: "b", });
+
+let y = Bar({ c: "c", d: "d", });
+The Reason code above produces the following JavaScript:
+const x = {
+ kind: /* Foo */ "Foo",
+ a: "a",
+ b: "b"
+};
+
+const y = {
+ kind: /* Bar */ "Bar",
+ c: "c",
+ d: "d"
+};
+In summary:
+[@mel.tag "kind"] specifies that each variant containing a payload should
+be tagged with "kind".[@mel.as ".."] attribute in each variant type specifies what that
+payload should be for each branch of the variant type.@mel.send is way, way better When binding to methods of an object in JavaScript, Melange has historically
+supported 2 different ways of achieving the same: @mel.send and
+@mel.send.pipe. The only real reason why 2 constructs existed to do the same
+was to support two alternatives for chaining them in OCaml:
+pipe-first and
+pipe-last. But
+this always felt like an afterthought, and code using @mel.send.pipe never
+felt intuitive to look at (e.g. in external say: unit [@mel.send.pipe: t],
+one had to mentally place the t before unit, since the real signature is t -> unit).
In Melange 5, we wanted to remove this weird split and further reduce the +cognitive overhead of writing bindings to call JavaScript methods on an object +or instance.
+We're introducing a way to mark the "self" instance argument with
+@mel.this and recommending only the use of @mel.send going forward.
+Starting from this release, @mel.send.pipe has been deprecated, and will be
+removed in the next major release of Melange. Here's an example:
[@mel.send]
+external push: (~value: 'a=?, [@mel.this] array('a)) => unit = "push";
+
+let () = push([||], ~value=3);
+The code above marks the array('a) argument as the instance to call the
+push method, which produces the following JavaScript:
[].push(3);
+Besides being more versatile, having an explicit marker with @mel.this is
+also more visually intuitive: when scanning Melange code containing external
+bindings, it becomes easier to spot which is the "this" argument. This feature
+is fully backwards compatible with @mel.send: in the absence of @mel.this,
+the instance argument defaults to the first one declared in the signature, as
+previously supported.
Since Melange's
+inception, one
+of its goals has been to keep it up to date with the latest OCaml releases.
+This
+release
+brings Melange up to speed with OCaml 5.3, including upgrades to the Stdlib
+library as well. We're also releasing Melange 5 for OCaml
+4.14,
+5.1
+and
+5.2.
Starting from this release, we're shipping NPM packages with the precompiled
+Melange runtime. This feature, requested by a few users in
+melange#620 allows to use
+Melange without compiling its own runtime and stdlib (essentially, in
+combination with (emit_stdlib false) in (melange.emit ..)).
This can be useful in monorepos that compile multiple Melange applications but, +perhaps most importantly, it enables Melange libraries and packages to also +be published in NPM without the weight of the full runtime / stdlib.
+externals Melange bindings to JavaScript, specified through external declarations, used
+to propagate internal information in the native
+payload. In
+practice, hovering over one of these in your editor could end up looking a bit
+weird:

Since +melange-re/melange#1222, +Melange now propagates this information via internal attributes that only the +Melange compiler recognizes. These don't show up when hovering over +declarations in editors, making the resulting output much less jarring to look +at:
+
In Melange 5, we modernized the JavaScript emitter to produce cleaner, more +readable, and better-indented code. Melange 5 generated JS looks remarkably +closer to hand-written JavaScript, with this release enhancing that quality +even further.
+Melange 5 crosses a major milestone for JavaScript expressivity, bringing great
+features like idiomatic dynamic import()s and support for discriminated
+unions. Compatibility with OCaml 5.3 marks Melange's commitment to parity with
+the latest OCaml versions. In this latest version, Melange raises the bar for
+increasingly prettier JavaScript prettification, and the Melange precompiled
+runtime starts to be available on NPM.
Check out the full +changelog +for detailed information on all the changes that made it into this release. If +you find any issues or have questions, feel free to open an issue on our +GitHub issue tracker.
+This release was sponsored by the generous support of +Ahrefs and the OCaml Software +Foundation.
+ diff --git a/data/planet/mirage/easy-distributed-analytics-with-irmin-10.md b/data/planet/mirage/easy-distributed-analytics-with-irmin-10.md index d0463d79f7..2ba9ab2066 100644 --- a/data/planet/mirage/easy-distributed-analytics-with-irmin-10.md +++ b/data/planet/mirage/easy-distributed-analytics-with-irmin-10.md @@ -45,7 +45,7 @@ operations that are available and how to define atomic operations; andIrmin now exposes Irmin.Type to create new mergeable contents more
easily. For instance, the following type defines the property of
simple metrics, where name is a human-readable name and gauge is a
-metric counting the number of occurences for some kind of event:
type metric = {
name : string;
gauge: int64;
@@ -232,5 +232,3 @@ operations to read and write atomically. Finally, flexible first-class
support for immutable trees has also been added.
Send us feedback on the MirageOS mailing-list or on the Irmin
issue tracker on GitHub.
-
-
diff --git a/data/planet/ocamlpro/2022-at-ocamlpro.md b/data/planet/ocamlpro/2022-at-ocamlpro.md
index 4c44f200a6..86613ba4e0 100644
--- a/data/planet/ocamlpro/2022-at-ocamlpro.md
+++ b/data/planet/ocamlpro/2022-at-ocamlpro.md
@@ -22,7 +22,7 @@ source:
Clear skies on OCamlPro's way of life.
-
+
For 12 years now, OCamlPro has been empowering a large range of customers,
@@ -155,7 +155,7 @@ application (COBOL).
The M language, designed in the 80s to compute the French Income Tax, is still being rewritten in OCaml!
-
+
In 2022, our work on MLANG has passed a significant milestone: our work may no
@@ -188,7 +188,7 @@ post on the matter.
Cobol is ran in gargantuan infrastructures of many an insurance companies and banks across the globe.
-
+
In 2022, we started contributing to the GnuCOBOL
@@ -229,7 +229,7 @@ standard. More on this next year, hopefully !
Kind words sent our way by Florian Gilcher (skade), managing director at Ferrous Systems!
-
+
OCamlPro's culture is one of human values and appeal for everything scientific.
@@ -257,7 +257,7 @@ contributions and missions, some of which we will share with you right now.
Ecore is the code generator at the heart of the EMF Architecture.
-
+
In 2022, we have seized the opportunity to work at the threshold between Java
@@ -296,7 +296,7 @@ future iterations of their internal projects.
Ferris the Crab is the mascot of the Rust Language. No wonder why we converged as well!
-
+
As we continue scouring the market for more and more Rust projects, and whenever
@@ -373,7 +373,7 @@ DNS provider, and strives to offer a user-friendly and easy configuration.
WebAssembly is used to compile many languages to an efficient portable code for web-browsers.
-
+
Late 2022 was finally time for us to put into practice the knowledge we have
@@ -444,7 +444,7 @@ of Paris-Saclay.
Alt-Ergo proves mathematical formulas corresponding to software program properties.
-
+
@@ -469,7 +469,7 @@ full-time R&D engineer.
The dedicated members of the Club!
-
+
This is the reason why we would like to thank our partners from the Alt-Ergo
@@ -527,7 +527,7 @@ the Formal Methods Lab (LMF) and the ProofinUse consortium members. Stay tuned!<
Dolmens are Neolithic megalithic structures composed of menhirs and they can range from a few centimeters to several meters high!
-
+
Dolmen is an OCaml Library developed by
@@ -568,7 +568,7 @@ scientists.
opam, the OCaml Package Manager, remains one of OCamlPro's greatest achievements!
-
+
2022 has been the theatre of a sustained and continuous effort from the opam
@@ -616,7 +616,7 @@ repository, OCaml tooling) and the public's feedback and vision.
Flambda2 is a powerful code optimizer for the OCaml compiler strong of many years of R&D.
-
+
OCamlPro is proud to be working on Flambda2, an ambitious OCaml
@@ -667,7 +667,7 @@ OCaml distribution!
Camels going to their pluri-annual OUPS Meet-up.
-
+
Just under 10 years ago, Fabrice Le Fessant initiated the very first OCaml
@@ -709,14 +709,14 @@ Foundation who graciously pays for the pizzas.
Toulouse also has its set of enthousiastic OCaml supporters.
-
+
Fortunately for OCaml Users that live in the French South-West, a new Meet-up
is now available to
them. On the 11th of October 2022, the first OCaml meet-up in
Toulouse happened.
-The first occurence of the OCaml Users in Toulouse Meetup kicked off with Erik
+
The first occurrence of the OCaml Users in Toulouse Meetup kicked off with Erik
Martin-Dorel (OCaml Software Foundation) presenting
Learn-OCaml who was then followed by
David Declerck (OCamlPro) presenting his
@@ -742,7 +742,7 @@ OCaml.
ICFP 2022 took place in the beautiful town of Ljubjana, Slovenia.
-
+
The OCaml Workshop is an international conference that focuses on everything
@@ -771,7 +771,7 @@ editions of the conference!
the JFLA'2022 took place in the beautiful Domaine d'Essendiéras in Périgord, France.
-
+
Among the many scientific conferences we attend on an annual basis, the
@@ -803,4 +803,3 @@ support and collaboration throughout the year,
And to you, dear reader, thank you for tagging along,
Since 2011 with love,
The OCamlPro Team
-
diff --git a/data/planet/ocamlpro/better-inlining-progress-report.md b/data/planet/ocamlpro/better-inlining-progress-report.md
index 11f0c1a003..29aba1b17f 100644
--- a/data/planet/ocamlpro/better-inlining-progress-report.md
+++ b/data/planet/ocamlpro/better-inlining-progress-report.md
@@ -74,7 +74,7 @@ and b = (a.v, a.v)
I have a few solutions, but not sure yet which one is best. This probably won't appear in any normal test. This bug manifests through a segmentation fault (cmmgen fails to compile that recursive value reasonably).
The new passes assume that every identifier is declared only once in a given module, but this assumption can be broken on some rare pattern matching cases. I will have to dig through matching.ml to add a substitution in these cases. (the only non hand-built occurence that I found is in ocamlnet)
The new passes assume that every identifier is declared only once in a given module, but this assumption can be broken on some rare pattern matching cases. I will have to dig through matching.ml to add a substitution in these cases. (the only non hand-built occurrence that I found is in ocamlnet)
I would now like to add back cross-module information, and after a bit of cleanup the first series of patches should be ready to propose upstream.
- diff --git a/data/planet/ocamlpro/flambda2-ep-4-how-to-write-a-purely-functional-compiler.md b/data/planet/ocamlpro/flambda2-ep-4-how-to-write-a-purely-functional-compiler.md new file mode 100644 index 0000000000..97225ad91f --- /dev/null +++ b/data/planet/ocamlpro/flambda2-ep-4-how-to-write-a-purely-functional-compiler.md @@ -0,0 +1,533 @@ +--- +title: 'Flambda2 Ep. 4: How to write a purely functional compiler' +description: Welcome to a new episode of The Flambda2 Snippets! Today, we will cover + key high-level aspects of the algorithm of Flambda2. We will do our best to explain + the fundamental design decisions pertaining to the architecture of the compiler. + We will touch on how we managed to make a purely functional opt... +url: https://ocamlpro.com/blog/2025_02_19_the_flambda2_snippets_4 +date: 2025-02-19T08:24:40-00:00 +preview_image: https://www.ocamlpro.com/blog/assets/img/F2S_picture_son_doong_cave.jpg +authors: +- "\n Pierre Chambart\n " +source: +--- + + ++
+
+
+
+
Today, we will cover key high-level aspects of the algorithm of Flambda2. We
+will do our best to explain the fundamental design
+decisions
+pertaining to the architecture of the compiler. We will touch on how we managed
+to make a purely functional optimising compiler (leveraging
+tail-recursion,
+backtracking, and non-linear traversal) by covering how the code is traversed,
+what actions this design facilitates, and more!
All feedback is welcome, thank you for staying tuned and happy reading!
+++The F2S blog posts aim at gradually introducing the world to the +inner-workings of a complex piece of software engineering: The
+Flambda2 Optimising Compilerfor OCaml, a technical marvel born from a 10 year-long +effort in Research & Development and Compilation; with many more years of +expertise in all aspects of Computer Science and Formal Methods.
Here's a code snippet we would like to be able to optimise and that +demonstrates a set of properties that we want our code optimiser to have.
+(* original code *)
+let bar x =
+ let d = x + x
+ let y = x, d in
+ y
+
+let foo z =
+ let x, d = bar z in
+ if x = z then x + 1 else d
+
+We will optimise this code to :
+let foo z =
+ z + 1
+
+And we will do that in a single pass that is both efficient and maintainable.
+Here are the key transformations we would like to apply to this codeblock:
+bar function is an alias
+to the z argument of foo, thus the if condition in foo
+always evaluates to true.
+d variable is never used, since the else branch in
+foo is never executed.
+That being said, to discover the aliased values x and z, we have to
+follow the z variable from foo to bar and back again. And to
+discover that the let d = x + x is unused in bar we have to know about
+the alias and then go back from the d used in foo to the let in
+bar. The point is, there is a complex order of dependencies between
+these properties that we have to follow in order to learn about the code.
Keep in mind that we aim for our compiler to remain reasonably fast. In
+order to do that, we conduct all code transformations at the same time as the
+analysis. This entails that we cannot just plug a constraint solver inside
+of Flambda2 in order to discover these properties.
You have to understand that there are two kinds of properties that we want to
+track.
+One of them, like discovering that the if condition always evaluates
+to true, flows in the order of evaluation, i.e: top-down. While the others,
+like finding dead code, like let d = x + x, and thus eliminating it, can
+only be done in the reverse order of the evaluation, i.e: bottom-up.
++Interesting detail: properties of the first category, sometimes help discover +properties from the second, like in that specific example, but never the +other way round.
+
And now, we will explain, how we have designed Flambda2 to be able to operate
+within these constraints while transforming the code at the same time.
(*
+ CPS-converted version
+ Same code as before in the FL2 IR.
+ All variables with names starting with `k` are continuations.
+*)
+let bar x k_ret =
+ let d = x + x in
+ apply_cont k_ret x d
+
+let foo z k_ret =
+ let_cont k x d =
+ let r = x + 1 in
+ if x = z then
+ apply_cont k_ret r
+ else
+ apply_cont k_ret d
+ in
+ apply bar z k
+
+If you recall our very first F2S
+snippet, we
+mentioned one of the fundamental design decisions of Flambda2 which consists
+in representing programs using
+CPS. One of
+the main reasons for that is that inlining becomes very simple.
But there's a catch…
+If you refer back to the original version of the CPS-converted codeblock above,
+you will see that if x = z then x + 1 else d is inside the scope of the
+the bar function call. It's no longer the case once the function
+has been converted to CPS. This shrinking of the scope, is inherent to CPS
+representation. In an expression language, value analysis can simply be written
+as a recursive function on expressions, propagating properties through an
+environment. That is how the simplification pass was written on our previous IR Flambda1. It did
+produce some imprecisions here and there, but the trade-off in code simplicity
+favoured this route rather than the one we have taken with Flambda2 today.
In direct-style language representations, traversals in the order of evaluation +may be roughly emulated by simply traversing the tree recursively. On the other +hand, in CPS-style language representations, this doesn't hold.
+That's the catch: analysing CPS code entails more complex algorithms.
+Reasoning about code requires having a specific kind of data structure.
+This data structure must behave like a kind of database of properties of
+expressions, we naturally attach a name to each expression, and the data
+structure itself keeps track of the properties related to them. This data
+structure will be named acc in the following code blocks (short for
+accumulator).
A design decision we made early was that we wanted to traverse the code only +once while doing the maximum amount of simplifications. Of course, there are +exceptions to this rule but that’s a topic for another time.
+Experience gained from designing Flambda1 guided this decision. In practice,
+this overarching traversal manifests itself as two distinct passes: one
+downwards and one upwards. The downwards pass performs static analysis and
+inlining, while the upwards one handles code reconstruction and dead code
+elimination, we call this whole process "Simplify".
As mentioned in +F2S1, the +FL2 AST is simple and represented with only 6 different cases. You can find it +again below:
+type expr =
+ | Let_val of { var; prim; body : expr }
+ | Let_cont of { k; param; handler : expr ; body : expr }
+ | Apply_cont of { k; arg }
+ | Apply_val of { f; k_return; arg }
+ | Switch of { arg; cases }
+ | Invalid
+
+We are going to cover each of these cases separately and explain how each behave +and their role in how they help us reason about the code. Then, once all that is +clear, we will explain how we traverse each constructor. This should help you +understand what information we accumulate during both passes, and what +exactly we can do with them.
+type expr =
+ | Let_val of
+ {
+ var : variable ;
+ prim : named ;
+ body : expr ;
+ }
+[…]
+
+The Let_val constructor evaluates a named primitive, and binds it to a
+variable inside the body and then evaluates that body. A named
+primitive is a single atomic operation applied to some variables. Primitives
+have no impact on control flow, for instance they cannot raise exceptions.
This is the easy case, we just follow the evaluation order above. We analyse
+the named primitive, extend the acc data structure with the discovered
+properties, and proceed with analysing the body using the new acc.
The most important thing about this process on the way down is this specific
+extension of the acc data structure. Most other constructors will pipe the
+acc smartly all along the computation rather than extending it.
One interesting thing to note: we can discover properties on the arguments of
+the primitive and not only on the bound variable. For example, the primitive
+that reads the field of a value allows us to discover that the argument is a
+block where that field exists in the current acc.
type expr =
+ | Let_cont of
+ {
+ body : expr;
+ k : continuation;
+ params : variable list;
+ handler : expr;
+ }
+[…]
+
+The Let_cont constructor evaluates body. body is allowed to refer
+to the k continuation, and when encountering an application of k the
+control flow will evaluate handler after binding the arguments of the given
+application to params.
The first thing to note is that there might be several apply_cont to k
+inside the body, and since we want to analyse the handler only once, we
+cannot just follow the evaluation order naively like with the Let_expr case.
Therefore, we first analyse the body, and collect all the data about the
+applications of k (see the apply_cont case below).
Once we have that, we can analyse and deduce the properties that we can know
+about the arguments given to k. We can then bind these properties to the
+corresponding parameters and then analyse the handler itself.
Let's consider the following code snippet.
+let foo_d b k_ret =
+ let_cont k x =
+ let y = (x <= 1) in
+ apply_cont k_ret y
+ in
+ if b then apply_cont k 0
+ else apply_cont k 1
+
+When we analyse the let_cont we first analyse the body and see the
+conditional on b. We'll see the two apply_conts to k and we'll be able
+to deduce that the argument given to k is either 0 or 1. With that
+knowledge, we can analyse the handler of k and deduce that y is always
+true.
So far, we've only considered the case where let_conts are not recursive.
+We also allow let_cont to be recursive, namely to represent the control-flow
+of loops, which means that the handler can contain apply_cont k. Since we
+won't be able to see all apply_conts before analysing the handler we will
+have to stay conservative by over-approximating the properties we know about
+the parameters.
type expr =
+ | Apply_cont of
+ {
+ k : continuation;
+ args : variable list;
+ }
+[…]
+
+As described in Let_cont this only transfers the control to the handler
+associated to k using the args to populate the value of the parameters of
+k.
In this constructor, we extend the acc by associating the current context to
+k. This will be retrieved later (see Let_cont case) to know
+which contexts led to this continuation, and thus setup a context for the
+handler.
Furthermore, Apply_cont has no underlying field of type expr so it is
+a leaf of the on-going traversal. Assuming that there was a Let_cont earlier,
+the traversal will forward the acc to the last Let_cont encountered and
+proceed from there again as explained above.
If there is no remaining Let_cont then it means that the analysis of the
+function is over and that we've traversed all the live code.
See the Let_cont example.
type expr =
+ | Apply_val of
+ {
+ f : variable;
+ args : variable list;
+ k_return : continuation;
+ }
+[…]
+
+Apply_val is the usual function application. f is interpreted as a
+functional value so control-flow jumps to the associated code, binding args
+to the function parameters. Since this is CPS, when the function returns, the
+control is transfered to k_return, same as for an Apply_cont, its return
+value is bound to the parameter of k_return.
It closely ressembles something like:
+let x = f args in
+apply_cont k_return x
+
+But since we don't allow normal function applications inside of a Let_val, we
+have an Apply_val constructor to handle it.
The first thing we do is: recover the known properties about f from our acc.
Depending on what properties we have discovered so far, we decide whether to
+inline f or not:
+If we choose not to inline f, we handle this Apply_val as another
+Apply_cont to k_return, but if we do decide to inline it, we replace
+the current Apply_val with the body of the f function and continue the
+traversal from there.
The properties that matter for the inlining decision include:
+f is a variable,
+and we may or may not know which function it refers to)
+f such as
+[@inline], or at the application, with [@inlined]
+f is important too because inlining large functions may be
+detrimental
+args matter because, for instance, when we know nothing
+about the arguments, inlining f is less likely to be benefitial
+Some static analysis requires a whole-view of the program, or at least, the +current function.
+So when the downwards pass has traversed the whole term, we trigger a few
+analyses that we could not do on-the-fly like properties that involve
+loops. Such properties can't be computed during a single pass, they usually
+require a fix-point. Once that is done, we use the result of the downward pass,
+we can use that to initialise the upward environment (uenv).
You will be happy to learn that the upward traversal is much easier to break +down than the downward one! 🎉
+Since we have all the data accumulated on our way down at our disposal, we only +have a few more properties to track on our way back up. As said previously, we +gathered the properties inside an accumulator while following the evaluation +order, on our way down. On our way up, we will feed something more akin to an +environment.
+(* example of a rebuilding step function *)
+val rebuild_let : var -> prim -> args : var list -> body : (term * uenv) -> term * uenv
+
+This upward environment (uenv) will mainly hold data about:
These properties are inherently structural. Thus, tracking them is easily done +while traversing the tree in the structural order.
+Furthermore, these are properties of the rebuilt version of the term, not the +original one:
+That is why we could not have tracked them on the way down, thus relegating +them to the way back up. Hence, we have designed the upward pass to follow the +structural order to track that effortlessly.
+Free variables are useful for dead code elimination.
+Dead code is code which can be removed from the term without altering its +semantic.
+There are two kinds of dead code:
+The first one can be detected by looking for variables which are never +mentioned outside of their definition.
+The second is the same, but relative to continuation names.
+In order to understand how it is done, let's see how we do it for a simple
+let binding, and then we'll see how it is done through continuations with
+let_cont.
Let-bindings: when rebuilding the let, we have the rebuilt body, and the
+set of free-variables of the rebuilt body, and if the variable bound by the
+let is not part of the free-variables, we delete that let.
Rundown:
+let x = 1 in
+(* Step 2 *)
+let z = 0 in
+(* Step 1 *)
+let y = x + 1 in
+(* Step 0 *)
+42 + z
+
+The rebuilding order follows the Step annotations of the example from 0 to 2.
Step 0: the free-variable of the body of the let y is { z }. y is not
+present in that set so we don't rebuild the let. Had we rebuilt it, x would
+have been part of the free-variables set. So we just keep { z } as the set of
+free-variables.
Step 1: We rebuild the let z and remove z from the free-variables set
+because it is now bound.
Step 2: And now we continue onto the let x that we also remove.
We can observe that this method can remove all useless lets in a single traversal.
Let's see now, how we can extend this method to rebuilding let_conts while
+still maintaining this property.
Let_conts: As for let_cont, we want to be able to remove the unused
+parameters of the continuation. We can see that a parameter is unused after
+having analysed the continuation handler: when the parameter is absent from the
+set of free-variables of the handler.
Furthermore, we need to change the apply_cont of the continuation from which
+we remove parameters. We need to only pass arguments for live parameters.
That entails to go through the body of the continuation after traversing
+its handler, because it's inside the body that apply_conts to that
+continuation appear (we are going to put aside recursive continuations for the
+sake of simplicity).
And so, we keep track of which of these continuations' parameters we removed in
+order to rebuild the apply_cont.
Rundown:
+(* Step 0 *)
+let_cont k0 z =
+(* Step 1 (k0 handler) *)
+ return 42
+in
+(* Step 2 (k0 body) *)
+let_cont k1 y =
+(* Step 4 (k1 handler) *)
+ let z1 = y + 1 in
+(* Step 3 (k1 handler) *)
+ apply_cont k0 z1
+in
+(* Step 5 (k1 body) *)
+apply_cont k1 420
+
+The rebuilding order follows the Step annotations of the example from 0 to 5,
+though, we will only mention the relevant steps.
Step 1: Parameter z of k0 is dead, so we get rid of it.
Step 3: So now, we have to update the apply_cont k0 z1 which in turn becomes
+apply_cont k0 (the argument disappears).
Step 4: Since z1 was deleted, its let is then removed, and y becomes
+useless and in turn, eliminated.
Step 5: Eventually, we can replace the apply_cont k1 420 with apply_cont k1
+because the y parameter was previously eradicated.
It is this traversal order, which allows to conduct simplications on CPS in one +go and perform dead code elimination on the upwards traversal.
+In this episode of The Flambda2 Snippets, we have explored how the Flambda2 +Optimising Compiler performs upwards and downwards traversals to analyze and +transform OCaml code efficiently. By structuring our passes in this way, we +ensure that static analysis and optimizations are performed in a single +traversal while maintaining precision and efficiency.
+The downward traversal enables us to propagate information about variables, +functions, and continuations, allowing for effective inlining and +simplification. Meanwhile, the upward traversal facilitates optimizations such +as dead code elimination by identifying and removing unnecessary expressions in +a structured and efficient manner.
+Through these mechanisms, Flambda2 is able to navigate the complexities
+introduced by CPS conversion while still achieving significant performance
+gains. Understanding these traversal strategies is key to grasping the power
+behind Flambda2’s approach to optimization and why it stands as a robust
+solution for compiling OCaml code.
Thank you all for reading! We hope these articles keep the community eager to +dive even deeper with us into OCaml compilation. Until next time, mind the +stalactites! +⛏️🔦
+ diff --git a/data/planet/ocamlpro/opam-220-release.md b/data/planet/ocamlpro/opam-220-release.md index ddc9bc45d1..5704862082 100644 --- a/data/planet/ocamlpro/opam-220-release.md +++ b/data/planet/ocamlpro/opam-220-release.md @@ -46,7 +46,7 @@ support! A big thank you is due to Andreas Hauptmann (WODI and OCaml for Windows projects were for many years the principal downstream way to obtain OCaml on Windows, Jun Furuse (@camlspotter) whose -initial experimentation with OPAM from Cygwin +initial experimentation with OPAM from Cygwin formed the basis of opam-repository-mingw, and, most recently, Jonah Beckford (@jonahbeckford) whose DkML distribution kept - and keeps - a full diff --git a/data/planet/robur.coop/dnsvizor---run-your-own-dhcp-and-dns-mirageos-unikernel---gets-some-testing.md b/data/planet/robur.coop/dnsvizor---run-your-own-dhcp-and-dns-mirageos-unikernel---gets-some-testing.md new file mode 100644 index 0000000000..8ace3a141a --- /dev/null +++ b/data/planet/robur.coop/dnsvizor---run-your-own-dhcp-and-dns-mirageos-unikernel---gets-some-testing.md @@ -0,0 +1,14 @@ +--- +title: DNSvizor - run your own DHCP and DNS MirageOS unikernel - gets some testing +description: +url: https://blog.robur.coop/articles/dnsvizor02.html +date: 2025-04-10T00:00:00-00:00 +preview_image: +authors: +- Robur Cooperative +source: +--- + + + The NGI-funded DNSvizor provides core network services on your network; DNS resolution and DHCP. + diff --git a/data/planet/robur.coop/git-carton-and-emails.md b/data/planet/robur.coop/git-carton-and-emails.md new file mode 100644 index 0000000000..a4f49827da --- /dev/null +++ b/data/planet/robur.coop/git-carton-and-emails.md @@ -0,0 +1,12 @@ +--- +title: Git, Carton and emails +description: +url: https://blog.robur.coop/articles/2025-01-07-carton-and-cachet.html +date: 2025-01-07T00:00:00-00:00 +preview_image: +authors: +- Robur Cooperative +source: +--- + +A way to store and archive your emails diff --git a/data/planet/robur.coop/pushing-the-opam-repository-into-a-sustainable-repository.md b/data/planet/robur.coop/pushing-the-opam-repository-into-a-sustainable-repository.md new file mode 100644 index 0000000000..2a15bb1610 --- /dev/null +++ b/data/planet/robur.coop/pushing-the-opam-repository-into-a-sustainable-repository.md @@ -0,0 +1,14 @@ +--- +title: Pushing the opam-repository into a sustainable repository +description: +url: https://blog.robur.coop/articles/2025-03-26-opam-repository-archive.html +date: 2025-03-26T00:00:00-00:00 +preview_image: +authors: +- Robur Cooperative +source: +--- + + + The main opam-repository was only ever growing by collecting all releases of all packages. We worked hard on reducing the load for all clients by archiving packages. + diff --git a/data/planet/robur.coop/spf-dkim-dmarc-and-arc.md b/data/planet/robur.coop/spf-dkim-dmarc-and-arc.md new file mode 100644 index 0000000000..b3d895ec13 --- /dev/null +++ b/data/planet/robur.coop/spf-dkim-dmarc-and-arc.md @@ -0,0 +1,12 @@ +--- +title: SPF, DKIM, DMARC and ARC +description: +url: https://blog.robur.coop/articles/2025-04-23-email-verification.html +date: 2025-04-23T00:00:00-00:00 +preview_image: +authors: +- Robur Cooperative +source: +--- + +how emails are verified? diff --git a/data/planet/robur.coop/tcp-miou-and-unikernels.md b/data/planet/robur.coop/tcp-miou-and-unikernels.md new file mode 100644 index 0000000000..a3f771a98d --- /dev/null +++ b/data/planet/robur.coop/tcp-miou-and-unikernels.md @@ -0,0 +1,12 @@ +--- +title: "\u03BCTCP, Miou and unikernels" +description: +url: https://blog.robur.coop/articles/utcp_and_effects.html +date: 2025-03-24T00:00:00-00:00 +preview_image: +authors: +- Robur Cooperative +source: +--- + +My experiment about the TCP/IP stack, effects and unikernels diff --git a/data/planet/robur.coop/whats-new-with-mollymawk.md b/data/planet/robur.coop/whats-new-with-mollymawk.md new file mode 100644 index 0000000000..02d1d27199 --- /dev/null +++ b/data/planet/robur.coop/whats-new-with-mollymawk.md @@ -0,0 +1,14 @@ +--- +title: What's new with Mollymawk? +description: +url: https://blog.robur.coop/articles/mollymawk-first-milestone.html +date: 2025-04-07T00:00:00-00:00 +preview_image: +authors: +- Robur Cooperative +source: +--- + + + In this article we explore the journey Mollymawk has been on, inlcuding getting an (NGI0 core) NLnet grant, updates and more. + diff --git a/data/planet/sfletcher/conversion-operations-of-the-lambda-calculus-.md b/data/planet/sfletcher/conversion-operations-of-the-lambda-calculus-.md index cc078fa42b..f27e769123 100644 --- a/data/planet/sfletcher/conversion-operations-of-the-lambda-calculus-.md +++ b/data/planet/sfletcher/conversion-operations-of-the-lambda-calculus-.md @@ -13,8 +13,8 @@ source: - - + +
auto add = [](int x) { return [=](int y) { return x + y; }; };
auto sub = [](int x) { return [=](int y) { return x - y; }; };
- [=](int x) {
+ [=](int x) {
return [=](int x) {
- return add (sub (x) (1));
- } (x) (3);
+ return add (sub (x) (1));
+ } (x) (3);
} (9) ; //is the value '11'
@@ -103,7 +103,7 @@ source:
way to look at $\beta$ conversion is that it is saying something
about $\lambda$-expressions that look different but mean the same
thing.
-
+
@@ -124,7 +124,7 @@ source:
does not occur free in $f$, then $\lambda x.f\;x
\underset{\eta}{\leftrightarrow} f$. For example, in OCaml if we
define f by
- let f x = x + 1 then clearly
+ let f x = x + 1 then clearly
fun x -> f x produces the same results for
all values x in the domain of f.
References:
[1] The Implementation of Functional Programming Languages by Simon L. Peyton Jones. 1987.
-According to the lexical conventions of OCaml, characters different from \ and " can be enclosed in single quotes and appear in strings. The special characters \ and " are represented in these contexts by their escape sequences. The
+According to the lexical conventions of OCaml, characters different from \ and " can be enclosed in single quotes and appear in strings. The special characters \ and " are represented in these contexts by their escape sequences. The
escape sequence \\ denotes the character \ and \" denotes the character ".
Here we print the string "Hello world!". The quotes delimit the string and are not themselves part of the string.
@@ -33,14 +33,14 @@ To capture the quotes we need to write them into the string by their escape sequ
What now if we wish to quote a string within a string? -
utop[3]> Caml.Printf.printf
+utop[3]> Caml.Printf.printf
"\"A quoted string with \\\"a nested quoted string\\\"\"";;
"A quoted string with \"a nested quoted
string\""- : unit = ()
We see that in rendering the above string, printf has rendered the escape sequence \" as " and \\\" as \" as required. The pattern continues if we now wish to quote a string within a quoted string within a quoted string.
-
utop[4]> Caml.Printf.printf
+utop[4]> Caml.Printf.printf
"\"A quoted string with \\\"a nested \\\\\\\"nested\\\\\\\"
quoted string\\\"\"";;
"A quoted string with \"a nested \\\"nested\\\"
@@ -49,24 +49,23 @@ quoted string\""- : unit = ()
As you can see, things get crazy pretty quickly and you can easily drive yourself mad working out the correct escape sequences to get the desired nesting!
-Here's a hack : If the string has k levels of quoting, then count how many occurences of \s precede the " at that level. Let that number be n say. To get the next level of quoting you need to concatenate a sequence of n + 1 \s to them to get a total of 2n + 1 \s. To illustrate, look again at the last example:
-
utop[4]> Caml.Printf.printf
+Here's a hack : If the string has k levels of quoting, then count how many occurrences of \s precede the " at that level. Let that number be n say. To get the next level of quoting you need to concatenate a sequence of n + 1 \s to them to get a total of 2n + 1 \s. To illustrate, look again at the last example:
+
utop[4]> Caml.Printf.printf
"\"A quoted string with \\\"a nested \\\\\\\"nested\\\\\\\"
quoted string\\\"\"";;
"A quoted string with \"a nested \\\"nested\\\"
quoted string\""- : unit = ()
That's three level of quoting. At the third level we have the sequence \\\\\\\". That's 7 \s. To quote to the fourth level then we need 8 + 7 = 15 \s:
-utop[5]> Caml.Printf.printf
+utop[5]> Caml.Printf.printf
"\"A quoted string with \\\"a nested \\\\\\\"nested
\\\\\\\\\\\\\\\"nested\\\\\\\\\\\\\\\" \\\\\\\" quoted string\\\"\"";;
"A quoted string with \"a nested \\\"nested
\\\\\\\"nested\\\\\\\" \\\" quoted string\""- : unit = ()
-
+
In general, the number of \s required for n levels of quoting is 2n - 1 (that is, an exponential function). The solution follows from the recurrence relation Q0 = 0 and Qn = 2Qn - 1 + 1 which in fact establishes a connection to the "Towers of Hanoi" problem.
-
diff --git a/data/planet/sfletcher/preprocessor-extensions-for-code-generation.md b/data/planet/sfletcher/preprocessor-extensions-for-code-generation.md
index 7f8c7198ca..8a0a6203a5 100644
--- a/data/planet/sfletcher/preprocessor-extensions-for-code-generation.md
+++ b/data/planet/sfletcher/preprocessor-extensions-for-code-generation.md
@@ -13,7 +13,7 @@ source:
-
+
PPX
@@ -26,8 +26,8 @@ source:
The problem treated here is one posed in Whitequark's blog :
"Implement a syntax extension that would accept type declarations of
- the form
- type t = A [@id 1] | B of int [@id 4] [@@id_of]
+ the form
+ type t = A [@id 1] | B of int [@id 4] [@@id_of]
to generate a function mapping a value of type t to its
integer representation."
@@ -39,7 +39,7 @@ source:
items. Type declarations are structure items as are let-bindings to
functions.
- In this program, analysis of an inductive type declaration t
+
In this program, analysis of an inductive type declaration t
may result in the production of a new structure item, the AST of an of_id function
to be appended to the structure containing t.
@@ -96,7 +96,7 @@ source:
type_declaration = type_declaration_mapper;
constructor_declaration = constructor_declaration_mapper
}
-
+
Implementing the mappings
To warm up, lets start with the easy mappers.
The role of type_declaration_mapper is a function
@@ -130,7 +130,7 @@ source:
{(default_mapper.constructor_declaration mapper decl)
with pcd_attributes=attrs}
Now to the raison d'etre of the
- ppx, structure_mapper.
+ ppx, structure_mapper.
First, a utility function that computes from
a constructor_declaration with an @id
@@ -164,7 +164,7 @@ source:
(*Many "@id"s*)
| (_ :: _) ->
raise (Location.Error (Location.error ~loc
- "[@id] : Multiple occurences"))
+ "[@id] : Multiple occurrences"))
One more utility function is required.
eval_structure_item item acc computes structure
items to push on the front of acc. If item
@@ -194,7 +194,7 @@ source:
(*No [@@id_of]*)
| [] -> default_mapper.structure_item mapper item :: acc
- (*At least one [@@id_of] (treat multiple occurences as if
+ (*At least one [@@id_of] (treat multiple occurrences as if
one)*)
| _ ->
(*Cases of an [id_of] function for [t], one for each
@@ -219,7 +219,7 @@ source:
(*Case this structure item is something other than a single type
declaration*)
| _ -> default_mapper.structure_item mapper item :: acc
-
+
Finally we can write structure_mapper itself as a
simple fold over a structure.
let structure_mapper
@@ -233,7 +233,7 @@ source:
ocamlc -o ppx_id_of.exe -I +compiler-libs ocamlcommon.cma ppx_id_of.ml
When built, it can be tested with a command like
ocamlc -dsource -ppx ppx_id_of.exe test.ml.
-
+
For example, when invoked on the following program,
type t =
@@ -278,7 +278,7 @@ source:
let id_of = function | U -> 0 end
end
end
-
+
References:
@@ -288,6 +288,3 @@ source:
-
-
-
diff --git a/data/planet/signalsandthreads/finding-signal-in-the-noise-with-in-young-cho.md b/data/planet/signalsandthreads/finding-signal-in-the-noise-with-in-young-cho.md
new file mode 100644
index 0000000000..ac1916ae18
--- /dev/null
+++ b/data/planet/signalsandthreads/finding-signal-in-the-noise-with-in-young-cho.md
@@ -0,0 +1,13 @@
+---
+title: Finding Signal in the Noise with In Young Cho
+description:
+url: https://signals-threads.simplecast.com/episodes/finding-signal-in-the-noise-with-in-young-cho-qBmfD9v_
+date: 2025-03-12T14:41:48-00:00
+preview_image:
+authors:
+- Signals and Threads
+source:
+---
+
+In Young Cho thought she was going to be a doctor but fell into a trading internship at Jane Street. Now she helps lead the research group’s efforts in machine learning. In this episode, In Young and Ron touch on the porous boundaries between trading, research, and software engineering, which require different sensibilities but are often blended in a single person. They discuss the tension between flexible research tools and robust production systems; the challenges of ML in a low-data, high-noise environment subject to frequent regime changes; and the shift from simple linear models to deep neural networks.
You can find the transcript for this episode on our website.
+
diff --git a/data/planet/tarides/-fosdem-2025-report-from-the-friendly-functional-languages-bof-room.md b/data/planet/tarides/-fosdem-2025-report-from-the-friendly-functional-languages-bof-room.md
new file mode 100644
index 0000000000..b13ec5473b
--- /dev/null
+++ b/data/planet/tarides/-fosdem-2025-report-from-the-friendly-functional-languages-bof-room.md
@@ -0,0 +1,34 @@
+---
+title: ' FOSDEM 2025: Report from the Friendly Functional Languages BOF Room'
+description: Several of my colleagues and I went to FOSDEM 2025 and met many functional
+ programming enthusiasts, sparking great conversations for next year's FOSDEM.
+url: https://tarides.com/blog/2025-03-28-fosdem-2025-report-from-the-friendly-functional-languages-bof-room
+date: 2025-03-28T00:00:00-00:00
+preview_image: https://tarides.com/blog/images/Brussels-fosdem-1360w.webp
+authors:
+- Tarides
+source:
+---
+
+On Thursday, January 30, 2025, I spontaneously decided to join three of my colleagues, Jules Aguillon, Xavier Van de Woestyne, and Paul-Elliot Angles d'Auriac, in attending FOSDEM 2025.
+When I realised that FOSDEM was still taking proposals for Birds-of-a-Feather (BOF) sessions, I submitted a proposal to organise a Functional Languages BOF session around the idea of gathering the Functional Programming community to showcase projects and elegant solutions to real world programming problems.
+I was surprised and happy when the proposal was accepted by the FOSDEM organisers late Saturday night, during the conference, leaving just enough time to prepare and call in the Functional Programming community at FOSDEM for our Sunday afternoon session. Thankfully, FOSDEM runs a Matrix chat server for the conference, so it was simple to announce this last-minute addition to the conference schedule.
+Arriving at the Friendly Functional Languages BOF Room
+On Sunday afternoon, almost 50 functional programming enthusiasts filled room H.3242 for our "Friendly Functional Languages Show and Tell" session. The turnout exceeded our expectations by far and represented a diverse cross-section of the functional programming community. We had developers from various language communities including OCaml, Gleam, Elm, Elixir, Haskell, and several others.
+Show and Tell
+The format was intentionally casual – a space for practitioners to share real-world code they're proud of and discuss practical applications of functional programming principles.
+During these open sessions, participants presented programming techniques, API choices, interaction with an IDE, concepts inherent to programming and, of course, their projects! We saw parser combinators (live-coded in Haskell), the use of a monad to implement ‘undo’ functionality over composable operations (which Paul-Elliot presented in the context of his personal OCaml project Slipshow), compile-time SQL query generation in Gleam, effect abstraction in Haskell, and a turn-based videogame with a frontend built on Elm. It was clear that people are active and that they have a lot to say in 5 minutes!
+I found it very interesting to see people demonstrate how functional programming can elegantly solve complex problems.
+Looking Forward: Organising an FP Dev Room 2026
+Perhaps the most exciting outcome of our BOF session was the discussion we initiated towards the end of the session about establishing a Functional Programming dev room at FOSDEM 2026. Since only the most mainstream programming languages have a realistic chance at having their application for a dev room at FOSDEM accepted, many attendees expressed interest in creating a space for FP languages next year, and several volunteered to help organise it.
+We did a quick brainstorming session on what kinds of sessions an FP dev room at FOSDEM should host in order to achieve more visibility of functional programming in the Open Source scene. A major theme that emerged is that we need to show what can be and is being built with these languages in production environments. For example, OCaml is used by Ahrefs to build their leading SEO platform and by Jane Street to power their trading operations, which handled $17 trillion worth of securities trades in 2020 using a codebase of 65 million lines of OCaml. Erlang is another example, as it is used to power WhatsApp, supporting billions of active users.
+Engaging the Open Source Community
+As a Developer Advocate for OCaml at Tarides, I was happy to connect with developers and organisations within the Open Source software ecosystem. Several developers curious about OCaml volunteered to participate in recorded user testing sessions for the OCaml tooling we're developing at Tarides.
+These kinds of direct interactions are invaluable for understanding how developers approach OCaml, what challenges they face, and what opportunities exist to make the language more accessible and powerful. People's willingness to contribute time to help improve the ecosystem speaks volumes about the collaborative spirit of the open-source community.
+Final Thoughts
+Organising a BOF session at FOSDEM was a somewhat spontaneous decision, but it proved to be an excellent opportunity to bring together functional programming enthusiasts in an informal setting.
+I'm looking forward to seeing how the seeds planted during this session grow into a more established functional programming presence at FOSDEM 2026. If you're interested in joining the organising team for next year's prospective FP dev room at FOSDEM, feel free to reach out to sabine@tarides.com.
+Thank you to everyone who attended and made the session such a success!
+Connect with Tarides online on Bluesky, Mastodon, Threads, and LinkedIn or sign up for our mailing list to stay updated on our latest projects.
+Sabine Schmaltz is a Developer Advocate at Tarides, focusing on OCaml community engagement and developer experience.
+
diff --git a/data/planet/tarides/expanding-dune-package-management-to-the-rest-of-the-ecosystem.md b/data/planet/tarides/expanding-dune-package-management-to-the-rest-of-the-ecosystem.md
new file mode 100644
index 0000000000..95c1629ffd
--- /dev/null
+++ b/data/planet/tarides/expanding-dune-package-management-to-the-rest-of-the-ecosystem.md
@@ -0,0 +1,71 @@
+---
+title: Expanding Dune Package Management to the Rest of the Ecosystem
+description: Explanation of our learnings from attempting to build all Dune packages
+ in opam-repository using Dune package management.
+url: https://tarides.com/blog/2025-04-11-expanding-dune-package-management-to-the-rest-of-the-ecosystem
+date: 2025-04-11T00:00:00-00:00
+preview_image: https://tarides.com/blog/images/dunepkgmain-1360w.webp
+authors:
+- Tarides
+source:
+---
+
+Since we published The Dune Developer Preview a lot of things have improved on the package management front. While the developer preview has demonstrated how Dune can manage dependencies in a unified workflow, we have been working on making it practical for more projects to adopt Dune to handle their package dependencies. Our goal is to slowly move from a developer preview to a mature feature that the general public can use and rely on.
+What do we mean by maturation? The goal is fuzzy (as with every software, it is never 'done'), but we want to get Dune package management into a shape where we can consistently recommend that people use it for their projects. They should be confident that their workflows will continue to work while unlocking the new features that Dune package management brings.
+The core points of this are:
+
+- The OCaml Platform Tools should work at least as well with Dune package management as they work with
opam. With the new features in Dune, this interoperability should work even better as users do not have to share dependencies with the project in the local switch since tools can be installed automatically, possibly even from precompiled binaries. Do you want MDX? Declare a dependency, and voila, you have MDX.
+- Most projects can start using Dune with little to no adjustments. The majority will work out of the box, and the most frequent fix required is to correct the list of project dependencies. No substantial code changes are necessary, and all projects should continue to be compatible with both
opam and dune; there is no lock-in to one tool or the other.
+
+Our goal is to successfully build as many projects as possible using Dune's package management feature. But to evaluate what we have left to do, we need to know where we stand now. This blog post will give you an overview of the project's scope and biggest challenges.
+Building "All" Packages
+What if we want to try to build all the existing OCaml packages? Opam-repository to the rescue! While it might not include proprietary code bases, there are still a significant number of projects we can try to build with it. Fortunately, there has already been prior work done on this subject. Opam-health-check is an existing tool mostly written by Kate that can determine whether packages can be installed on different historical, current, and future OCaml versions. It continuously monitors the state of the opam ecosystem, which inspired its name.
+Tarides is running and maintaining multiple opam-health-check instances for the community. The most well-known is check.ci.ocaml.org which regularly builds thousands of opam packages on Linux, freebsd.check.ci.dev which does the same thing but on FreeBSD, and windows.check.ci.dev which as the name implies builds packages on Windows to help us with the effort to deliver a better OCaml experience on Windows.
+We were wondering whether we could use the tool when building with Dune instead of opam. Fortunately, the software is free, so we could extend the functionality to build Dune projects instead of installing opam packages. This gave rise to the next instance of opam-health-check, dune.check.ci.dev which, instead of using opam, builds them using Dune package management.
+Which Packages are we Building, Actually?
+Wer misst, misst Mist. – German proverb
+Opam takes its installation instructions from the opam metadata files that are collected in opam repositories like opam-repository. This is how the regular opam health check works, it selects (nearly all) packages, and attempts to build them.
+However, only projects that already use Dune to build can use package management. This happens because, when building a project, you need to know which dependencies to build, where these dependencies get built and installed, and which paths to pass to the compiler so it can find the modules that the dependencies install. Unlike in opam, the packages don't get installed into a location containing all installed libraries (a switch), but into separate directories that will be composed together when building.
+That means we need to be a bit more selective about which packages we are going to pick for testing. Picking projects that don't use Dune will fail in 100% of the cases and will not let us draw useful conclusions besides telling us that you need Dune projects to use Dune package management, which we already know.
+So, when determining which packages we want to include as our candidates, we need to filter the list of packages to ones that use Dune. The opam-health-check tool expects to call a shell command to generate the list. However, the process of determining which packages count as 'are using Dune' is more complicated, since the best way to determine that would be to detect whether dune build is used in a package and whether the package depends on the dune package.
+It's a bit fuzzy, but we decided to only include packages that depend on the 'dune' package. This leaves us with a few false positives (e.g. packages that don't support the most recent versions of Dune) and also some false negatives (packages that accidentally capture a 'dune' dependency through their own dependencies), so this will probably need a bit of revision in the future, but for now, it should be good enough.
+What About the Rest?
+There are a significant number of projects using Dune and this is far from all of them. While we can't build them directly because every build system works differently, all opam packages can be used as dependencies and should just work.
+How do we know this? We run different kinds of tests before using an internal tool that is quite similar but less sophisticated than opam-health-check. In a previous run on OCaml 4.14, we tested using an opam package as a dependency, attempting to build a project, and then checking the results. For that test, we selected 2505 opam packages (since they were compatible with 4.14, opam install could find a solution) and ran it over a few days. Ultimately, we only had 36 failures; thus, our success rate was a whopping 98%! This means that users can safely start using Dune for package management in their projects as the overwhelming majority of dependencies are compatible.
+What is Building a Package, Really?
+The biggest challenge is that much of the package metadata in the source archives is incorrect. As a result, dune pkg lock almost certainly picks invalid versions of dependencies. Why is that?
+Dependencies Galore
+Opam installs packages by inspecting the files in its own metadata repository, opam-repository. This repository is created by authors submitting their packages on release, and from there on, it is maintained by the opam-repository maintainers. They will make sure to add dependencies that have been accidentally left out or adjust when new, incompatible versions of dependencies get published. Older package definitions will be updated to include upper version constraints.
+However, if we check out a repository via git or download the source archive and try to build it with Dune, we don't have all these updates. Without them, many packages will fail to build (be it with opam or Dune).
+These issues can often be fixed very easily by the author of the package, and having Dune fail to build packages due to invalid dependencies is very disappointing. If the dependencies were to be fixed, the project would either work just fine with Dune package management (success, hooray!) or at least fail with a more interesting error. Marking it as a dependency failure does us a disservice by hiding potential errors.
+Our hack to test for Dune package management compatibility rather than accurate dependency declarations was to replace the dependencies from the source archive with information from opam-repository. This was a two-step process:
+
+- Overwriting the
opam files with the opam files from opam-repository.
+- Removing the dependency information from
dune-project because Dune prioritises the information in this file by default.
+
+Step two had an additional challenge as the dune-project file is in S-expression syntax, but the usual helpful processing tools like jq do not support S-expressions. So, we used Jane Street's sexp tool to do the processing, along with a generous helping of common Unix shell tools.
+This is not to say that users should be migrating their dependency specifications out of dune-project (they shouldn't), but for our automated processing it was easier to take the updated opam files and use them as-is, instead of migrating them back into the dune-project syntax.
+What is a Package, Actually?
+When opam builds a package that uses Dune, it usually calls dune build -p <package-name>, which makes Dune ignore everything in the source repository that is not attached to the package name. However, it doesn't work for the health check, as you want all projects in the source archive to be built, not just the current one that is to be tested. But you also don't want to build every package from the source archive, as that might introduce additional dependencies and unrelated failures. Likewise, you don't want to build code that is not part of any package (e.g. examples, benchmark, utilities).
+In the end, we solve it by determining the internal dependencies of the project to be built and then collecting these dependencies. We start the build by calling dune build --only-packages <packages-discovered> to restrict the build to only these packages.
+Ok, Ok, but Show Me the Results!
+
+The output of these runs is published on dune.check.ci.dev, where we build the candidate packages on Linux amd64 using the Dune developer preview binaries. We chose this platform because it will give us the biggest set of candidates since most packages are developed on systems similar to it. On the website, you can see all the packages we selected and the result of the build. At the time of writing we have selected 2243 packages to build and 1866 have completed the build successfully, which means that, at the time of writing, we have an 83% success rate in building projects directly! For the remaining 377 packages, the failures can be seen when clicking the entries since opam-health-check keeps logs of all the builds. It is our main tool to determine which issues to tackle next. So as we go forward we expect the success rate to rise to match opam as closely as possible!
+Where Do We Go From Here?
+Now that we have opam-health-check running and reporting build successes and failures, we can look into the build issues that it has revealed. A lot of them were small stumbling blocks which could have nevertheless been blockers to adoption:
+
+- The potentially simplest issue arose from the Dune not supporting packages distributed in ZIP archives. Due to OCaml's strong origins on Unix, most packages are distributed as compressed tarballs (often
gzip or bzip2 compressed). However, especially on Windows, the ZIP format is more popular and is also supported in opam. In #11511, we added Dune support for uncompressing ZIP files. We usually call programs to decompress the data to avoid shipping implementations of compression algorithms. However, to use these programs, they need to be available, and what is available depends on the platform. The simplest command to call is unzip from the Info-ZIP project. Still, on some platforms, the tar command also supports decompressing ZIP files as if they were tarballs, so we're trying to use whatever the user might have available.
+- When pinning a package, we assume it uses Dune. This works most of the time because a significant number of packages use Dune to build, but if a package does not, we will have to build and install it using the commands that it declares in its
opam file. #11513 does just that. It extracts the commands when pinned and uses them when the pinned package needs to be built.
+- A somewhat obscure semantic of the way dependencies and conflicts are represented in
opam files is that packages which are dependencies are implicitly conjunctions (depending on foo, bar means depending on foo AND bar); however, for conflicts, they are implicitly disjunctions (conflicting with foo and bar means to conflict with foo OR bar). This makes a lot of sense intuitively but is easily forgotten. Dune used to accept a conflict only if all packages were conflicting, and this behaviour flew under the radar for a long time because conflicts are rare. Most of the time, the conflict is only a single package, in which case it doesn't make a difference. This was fixed in #11515, which also simplified the code.
+- When solving a project's dependencies, the solver has to go through all of them and find a solution that satisfies all constraints, or it will display an error. These constraints are usually declared in your
dune-project or .opam files, but when using Dune package management, there is an additional constraint: the solution needs to be buildable with the currently running version of Dune. Unfortunately, in such a case, the solver would crash. In #11554, we solved the issue to some degree: instead of crashing, the solver will display an error message, which will hopefully make it clearer why it can't find a solution.
+- Opam has a little-known but very useful feature when declaring package dependencies. Instead of depending on a specific version, the user can use the current version of the package as a variable. This allows projects that consist of multiple packages to depend on each other without having to update all dependencies on every release (an example of this is
ocaml-zmq, which comes with async and lwt variants which depend on a common core). However, these constraints don't matter much when building the packages, so we always set the version to dev. Unfortunately, this can cause subtle issues where no solution can be found, so in #11517, the code was changed to attempt to read the version fields to populate the variable with the value the user declared.
+- At the moment, Dune handles the compiler in a special way. When attempting to build the compiler, instead of building it in the project, it will build it in a separate location in the user's home directory. This is due to the fact that the compiler can't be moved to a different location at the moment (work is underway to improve the situation - that effort is called "relocatable OCaml"). How OCaml 5.3.0 is packaged in
opam-repository changed and introduced a new transitive dependency for the compiler. Thus, the code would not be able to properly detect which opam package is the compiler. This was fixed in #11310 by computing the dependency cone of all possible compiler packages that are currently used to detect which package contains the compiler.
+- Opam has a way to mark a package as 'do not pick this package unless requested explicitly' -
avoid-version. This is, for example, used to mark beta versions of packages that can be installed manually but should not be automatically picked. The solver in Dune does not have such a feature, so originally, Dune sorted these packages to the end of the candidate list, but it would not match the semantics of opam. Dune would then interpret them as forbidden dependencies. However, some older packages failed to build without access to these dependencies, so #11494 was implemented where, instead of failing, the solver tries to minimise the number of dependencies picked that have the avoid-version flag.
+- Findlib, the tool whose package specification format is prevalent in the OCaml ecosystem and is also used by Dune, has a feature where parts of packages are installed in subdirectories. These subdirectories can also be optional when certain package features are enabled or disabled during building. It is a rare feature, but some real-world packages use it. Unfortunately, Dune would always assume that these directories existed if they were declared and try to read their contents. But if the directory does not exist (e.g. the feature is disabled), this would lead to a crash. The fix in #11569 is short and shows that all bugs are shallow if enough eyes inspect the code.
+
+Fixing these issues has gotten us to an (at the time of writing) 83% success rate in building projects according to opam-heath-check. That's a pretty good result and makes us confident that the package management feature is on the right track.
+The issues above, as well as future issues related to package coverage and their status, are collected in a tracking issue on the Dune bug tracker.
+How You Can Help
+If you want to take part in improving our OCaml ecosystem to have a simple, one-stop-shop for building and installing packages check out the nightly developer preview and try it with your projects. The team is looking for feedback on how they can improve Dune package management, so please share your thoughts on Discuss, and report any issues on GitHub!
+Stay in touch with us on Bluesky, Mastodon, Threads, and LinkedIn. We look forward to hearing from you!
+
diff --git a/data/planet/tarides/feature-parity-series-restoring-the-msvc-port.md b/data/planet/tarides/feature-parity-series-restoring-the-msvc-port.md
new file mode 100644
index 0000000000..2c11d4f293
--- /dev/null
+++ b/data/planet/tarides/feature-parity-series-restoring-the-msvc-port.md
@@ -0,0 +1,46 @@
+---
+title: 'Feature Parity Series: Restoring the MSVC Port'
+description: Discover everything that went into bringing MSVC support to OCaml 5 including
+ C11 atomics, bug reports, winpthreads, and CI updates.
+url: https://tarides.com/blog/2025-04-23-feature-parity-series-restoring-the-msvc-port
+date: 2025-04-23T00:00:00-00:00
+preview_image: https://tarides.com/blog/images/manycomputers-1360w.webp
+authors:
+- Tarides
+source:
+---
+
+After the release of OCaml 5, restoring any features that were left out of the initial release has been a high priority for our teams and collaborators. We call this effort our 'feature parity' project, and compaction is one example of a feature being brought back to OCaml 5 under its banner.
+In this post, we look at another returning property, MSVC support, and the steps along the path to implementation. If you want to skip straight to the code, check out the #12954 pull request (and the dozen more linked from it!) in the OCaml repo. Let's dive in!
+MSVC Support for OCaml on Windows
+First, let's explain what 'MSVC support' means. In general, OCaml supports compilation to Windows through three separate toolchains: Cygwin, mingw-w64, and MSVC. The mingw-w64 toolchain was available for OCaml 5 from the moment the update launched. Cygwin was restored in OCaml 5.1, but MSVC support has lagged behind until now!
+The delay stemmed from MSVC's initial incompatibility with C11 atomics, which the OCaml 5 runtime requires. David Allsopp had been exploring possible ways to overcome this incompatibility, testing how C++ atomics worked with older compilers. Eventually, however, Microsoft introduced support, albeit experimentally.
+To restore the port, the team needed to ensure that the C11 atomics support was reliable, port winpthreads onto MSVC, and create a continuous integration (CI) workflow.
+Since it is Microsoft's own C/C++ compiler, MSVC is popular and well-known to many developers and Windows users. Bringing compatibility with the compiler to OCaml 5 is an important step towards enabling more users to adopt the latest version of OCaml and explore its new features!
+C11 Atomics and Bug Reports
+C11 is a version of the C standard. Since the OCaml runtime is written almost entirely in C, the general standard lets us specify which properties of the C compiler can be used to build OCaml. If we didn't rely on a standard, we would have to list supported C compiler versions individually (GCC from one such version, Clang from another such version, etc). Instead, developers know that the OCaml runtime environment supports any C compiler that is C11-compliant.
+For OCaml 5 and onwards, the C compiler must be C11 compliant and support C11 atomics. All we need to know about atomics for this post is that the C11 atomic spec enables the compiler to help the programmer access data that can be shared between multiple cores. Without it, the developer would need to use other synchronisation mechanisms such as mutexes, that require more code, both to write and to run. So C11 atomics go beyond what most of us associate with atomicity and are key to writing sound and efficient code in a multicore setting.
+Once the Visual Studio 2022 release introduced experimental support for C11 atomics, it provided a much clearer path for the team to work on restoring MSVC support. This team included David Allsopp, Antonin Décimo, and Samuel Hym from Tarides, but of course, the success of the project relied on the collaboration, input, and reviews of many open-source OCaml community members. With C11 atomics support in Visual Studio 2022 being experimental, the team needed to ensure that all the sequential tests passed and identify places where parallel tests failed due to bugs in MSVC. The team created several bug reports against the MSVC compiler as a result of this project.
+The bug reports include:
+
+- Missing Atomic Stores When Dereferencing Pointer to Atomics: MSVC version 19.38.33128 lacked support for pointers to atomic values and was not emitting atomic stores when writing to a dereferenced pointer for an atomic variable.
+- Compound Assignment Operators are not Atomics on Pointers to Atomic Values: Again, MSVC 19.38.33128 lacked support for pointers to atomic values, and this bug report highlighted that MSVC did not generate atomic code for compound assignment operators with a pointer to an atomic value.
+- Pointers to Atomic Values Should be Reloaded: In version 19.38.33128 MSVC failed to generate atomic code, meaning that pointers were not reloaded when they needed to be, causing threads to spin indefinitely.
+
+Thanks to great support from Microsoft, these bugs were resolved, and C11 atomics support was satisfactory to enable MSVC support for multicore OCaml. This was the biggest roadblock to the project's success, and with it cleared, the team turned their attention to new challenges.
+Winpthreads and MSVC
+The next hurdle on the road to success was another Windows-specific compatibility issue. OCaml 4.* had limited support for threading in the form of the optional systhread library, but the runtime itself made no use of it. That completely changed with OCaml 5! The abstraction used to enable threading support was Unix's posix threads, known as 'pthreads'. At the time, the runtime was prepared in the hope that a Windows version could be implemented in the future.
+However, the original multicore PR could use the winpthreads part of the mingw-w64 library to provide a pthread implementation for the Windows MinGW port. The intention then was that it would be a temporary workaround allowing all the existing pthreads code to be reused, partly due to the belief that it would only work for mingw-w64 and not for MSVC.
+Upon further investigation, David discovered a library demonstrating that winpthreads could be compiled with MSVC without introducing too many dependencies. Samuel and Antonin worked on formalising the process of extracting the winpthreads sources from the mingw-w64 project to use them for the MSVC port. Antonin also contributed directly to the mingw-w64 project to patch its winpthreads component.
+Thanks to this work, the initially temporary winpthreads workaround has been implemented as a submodule for MSVC. This lets the new MSVC port use pthread.h via the winpthreads submodule (instead of using winpthreads implicitly as provided by the mingw-w64 GCC compiler).
+The future of pthreads in OCaml is still up for discussion, with one school of thought being that reimplementing OCaml's use of pthreads in a more abstract way would allow its primitives to function without the full weight of the POSIX spec, resulting in better performance. Work has started to remove winpthreads and use modern Windows APIs for the MSVC and MinGW-w64 ports.
+CI
+Finally, the team added a continuous integration workflow enabling GitHub Actions for MSVC testing.
+As most OCaml compiler developers use a different port than MSVC, and since there are many differences between MSVC and the other ports, being able to test MSVC in CI helps the developer be confident that their modifications do not break the code. In particular, part of the CI workflow includes a check to make sure the assembly used in the MSVC port (meaning that it's written in MASM syntax) is kept consistent with the assembly used in the MinGW port (written in GNU Assembler syntax). This check has already allowed OCaml core developers to catch a few PRs that only updated the assembler in GNU syntax, catching a problem early and preventing it from affecting the program.
+A Note on the Unloadable Runtime
+Before we leave you, let's briefly review another feature parity project, the return of the unloadable runtime. This project was paired with the return of the MSVC port as two features that were important to the community. The 'unloadable runtime' is a feature that cleans up OCaml resources, including the stack, heap sections, code fragments, buffers, tables, and more, when OCaml is used as a shared library. For example, if a host program uses OCaml as a library, when control returns to the host program, the unloadable runtime ensures proper resource cleanup.
+The return of this feature was requested by the community, and our team worked hard to make the restoration a reality. The PR associated with this effort is #12964, which you can check out to learn more about the process behind the changes. The PR has been merged and is expected to be released with the 5.4 update.
+Stay in Touch!
+Keep an eye out for future updates on restored features on our blog. For a broader overview of the 5.3 update, you can check out our release blog post covering the changes.
+You can connect with us on Bluesky, Mastodon, Threads, and LinkedIn or sign up for our mailing list to stay updated on our latest projects. We look forward to hearing from you!
+
diff --git a/data/planet/tarides/feature-parity-series-statmemprof-returns.md b/data/planet/tarides/feature-parity-series-statmemprof-returns.md
new file mode 100644
index 0000000000..38fc5ba526
--- /dev/null
+++ b/data/planet/tarides/feature-parity-series-statmemprof-returns.md
@@ -0,0 +1,68 @@
+---
+title: 'Feature Parity Series: Statmemprof Returns!'
+description: Statmemprof, a statistical memory profiler for OCaml, is now compatible
+ with OCaml 5! Discover the technical background to the return of the feature.
+url: https://tarides.com/blog/2025-03-06-feature-parity-series-statmemprof-returns
+date: 2025-03-06T00:00:00-00:00
+preview_image: https://tarides.com/blog/images/statmemprof-1360w.webp
+authors:
+- Tarides
+source:
+---
+
+Welcome to part two of our feature parity series! In it, we present returning features that were originally lost when OCaml gained multicore support. The addition of multiple domains means that the underpinning design decisions behind certain features have had to change significantly, and work is ongoing to adapt them and return them to OCaml 5.
+One of these features is memory profiling, which, after much theoretical consideration, has been successfully adapted to OCaml 5. Memory profiling is an important tool for developers who want to optimise their programs, and our post today delves into OCaml 5’s statistical memory profiler, statmemprof, and its now multicore-compatible design. Let’s explore the journey to its return!
+What is a Memory Profiler?
+Developers use memory profilers to understand how their programs use memory. Whether they think it’s using too much, is behaving suspiciously, or want to survey it for comparison’s sake, attaching a memory profiler lets them see how their program allocates memory and keep track of it when it runs. It sounds straightforward, but this is where the challenges begin!
+One of the first hurdles to clear is the sheer volume of allocated memory. Many programs, and in fact, many of the programs that are likely to be interesting from a memory perspective, allocate millions of bytes of memory over their run time. Running a memory profiler that monitors them all would significantly slow down the entire system. OCaml used to have a memory profiler that monitored all allocations (see spacetime), but it was removed because it was too resource-expensive.
+The solution to this first conundrum is to use a statistical memory profiler (the ‘stat’ in Statmemprof). A statistical memory profiler monitors a random sample of memory allocations in the program. This method still allows users to find allocations that stand out. Large allocations of memory tend to be more noteworthy, and consequently, if you have a program that allocates small and large pieces of memory, you want the random sampler to sample the bigger ones more often.
+Implementing this solution first brought Statmemprof to OCaml 4, but that still left the multicore issue, which, apart from making things generally tricky, required the developers to make some key decisions about how memory profiling should work with multiple domains.
+How Statmemprof Works With OCaml 5
+Memory Allocation in OCaml
+There are a few things one needs to wrap one’s head around to understand how statmemprof does its magic. This includes the way OCaml allocates memory with an inline pointer-bump allocator. If you are already familiar with memory allocation in the minor and major heap, jump ahead to the next section!
+OCaml needs to be able to allocate millions of objects a second and, therefore, needs very efficient memory allocation. Most programming languages call a function in the language library (such as malloc) that determines which memory to allocate. This process is too slow to work well in OCaml and for many other garbage collected languages such as Haskell, which also use bump-pointer allocators.
+In OCaml, a large part of the total memory available is reserved in what is known as the minor heap. In the minor heap, an allocation register points to the lowest address of allocated memory or to the boundary between what is allocated and free. Say a new object needs 32 bytes of memory: the system subtracts 32 bytes from where the allocation register is pointing and this space is used for the new object. When the minor heap’s garbage collector (GC) runs, it checks which objects can be deleted and which need to be kept. Surviving objects are promoted to the major heap, and the allocator register moves to the top of the minor heap since it is now empty.
+The minor heap has a ‘limit’, most commonly set to where the heap’s space ends, that, when reached, triggers a jump into the runtime system. The runtime can then take one of several actions, including garbage collection. This design makes memory allocation in OCaml very fast. Crucially for our topic today, this limit can be used to trigger a number of important events. Signal handling, for example, is achieved by tripping the limit in the minor heap to get into the runtime, which then runs the signal handlers. The runtime decides what actions to take and where to set the limit in the minor heap, allowing it to perform many different behaviours.
+OCaml can also bypass the minor heap and allocate objects directly in the major heap. This is useful for very large objects, which tend to live longer and survive the minor heap’s GC anyway. That's a topic for another time.
+With this basic overview of how OCaml allocates memory in our back pocket, let’s look at how statmemprof profiles memory in this system.
+Statistical Memory Profiling in OCaml
+The key to how statmemprof profiles memory lies in how the ‘statistical’ aspect is defined. To sample only a subset of memory allocations we need to define a workflow by which we get a random selection of samples. Since it only profiles every n number of allocations the user can leave the profiler running in the background without introducing significant overhead.
+So how does it work? We need to generate a number for both the minor and major heap to help us select the sample we want to profile. We need the number to be random, meaning that every number has an equal probability of being generated. Statmemprof achieves this through statistical sampling using a so-called Bernoulli trial, meaning that it samples every word of memory allocation with the same probability.
+Say the event we’re interested in is the allocation of a single word of memory to the minor heap. We have a parameter called ‘lambda’ for any such event, which represents the likelihood that statmemprof will sample that particular event. The random number we get, called a geometric random variable, stands for how many Bernoulli trials for some given lambda (or likelihood). You can also think of it as how long do we wait (how many events happen) before we sample one event.
+This choice of distributions is driven by the sampling mechanics in each heap. For the minor heap, we need to know "when is the next sample due?" which is naturally modeled by a geometric distribution - it tells us how many trials (allocations) until we hit our first success (sample). For the major heap, since we're dealing with larger blocks of memory, we need to know "how many samples should we take in N words?" This is naturally modeled by a binomial distribution, as it represents the number of successes (samples) in a fixed number of trials (N words). The geometric distribution is also computationally efficient for triggering the GC mechanism at the right time, while the binomial distribution provides a more systematic way to sample larger memory blocks.
+Now, let's imagine we get a random number, say 137. That number is subtracted from the allocation register in the minor heap, and the limit is set there. When the limit is reached, we go into the runtime, and the action we take is to take a memory profile sample. Statmemprof then generates a new number, and the process repeats. The process is the same for the major heap, but we use a binomial random variable instead of a geometric one.
+The benefit of statistical memory profiling is that smaller-sized objects in the minor and major heaps are less likely to be sampled since they don’t take up as much space as larger objects. This is good because the larger objects tend to be more interesting from a memory profiling perspective.
+What Happens When Statmemprof Samples an Object?
+Statmemprof was designed to be a flexible mechanism that gives the programmer a lot of choice. There is no hardwired action set up for when statmemprof samples an allocation. Instead, there are a number of actions to choose from left open for users to configure. They include determining the size of the object, whether it came from the minor or major heap, and what the program was doing at the time of the object’s allocation.
+When statmemprof samples an allocation it executes a callback (a construct that essentially works like a function) which is provided with details about the allocation and a backtrace. A backtrace refers to the sequence of functions that called a particular function. Backtraces are used to trace backwards from the function that triggered the allocation to the functions that called it, and so on, until it reaches the entry point of the program. What this means for statmemprof is that the API provides enough details for tools like memtrace to generate visual representations of memory use for the user's programs.
+There are five different kinds of events that can trigger the callback:
+
+alloc_minor: an object is allocated to the minor heap
+alloc_major: an object is allocated to the major heap
+promote: an object survives garbage collection and is moved to the major heap
+dealloc_minor: an object does not survive garbage collection and is freed from the minor heap
+dealloc_major: an object does not survive garbage collection and is freed from the major heap
+
+So, the hypothetical lifecycle of an object could be as follows: it gets stored in the minor heap with alloc_minor. The limit is tripped in the minor heap, and the garbage collector runs. The object survives garbage collection and is moved to the major heap with promote. The garbage collector runs in the major heap, and if the object is not needed anymore, it gets freed with dealloc_major. As an object's lifecycle progresses, statmemprof will execute a callback for each event and a complete picture of it can be built up. Statmemprof is designed to be flexible and configurable, and, for example, users can choose to set the profiler to retain callback information or opt to discard it.
+Memtrace
+For many users, delving into the code to configure statmemprof would add an undesirable level of complexity to their workflow. The solution is to use tools like Memtrace, a profiling library that uses the statmemprof interface. By building on the statmemprof functionality, these tools enable users to profile memory in the way they want to without having to worry about the specifics of how statmemprof works. Memtrace can accumulate the allocations and callstacks from the program to get a picture of which code locations are responsible for triggering allocations. (Note that, as of writing, the 5.3 compatible version of Memtrace has yet to be released by JaneStreet, but work is underway).
+Memtrace was created at Jane Street to help them pinpoint memory issues like space leaks. It uses the callback API implemented by statmemprof to record allocation events in the binary format Common Trace Format (CTF). Memtrace also comes with a viewer, a helpful tool that lets developers visualise their programs and see how memory is allocated.
+Generating a trace is straightforward, and Luke Maurer from Jane Street outlines the process in a great blog post on their website, and, if you want to learn more about the design of Memtrace, check out this excellent guide.
+This is just one example of how restoring statmemprof support brings powerful options to users of OCaml 5. Its features support the creation and implementation of tools that let users manage and understand how their programs use memory in new and detailed ways.
+Considerations for Multiple Domains
+So how do multiple domains affect the design choices for a memory profiler? Let’s take a look at some examples:
+
+- Let’s say you have two domains running at the same time doing different jobs separately, then one domain starts profiling its memory allocations. Should memory allocated by the other domain be sampled? The answer is: No! Behaviour in separate domains should be treated independently of each other.
+- Say you are in one domain and you start profiling, then, from this domain, you spawn another. Should the allocations in the new domain be profiled? The answer is: Yes! Because the new domain was created to achieve the work of the original domain.
+- In the multi-domain world, one domain can start a ‘profile’ by calling the start function of
statmemprof and sets up all the callbacks and sampling separately from all other domains. In theory, you could apply entirely different profiling tools, like memtrace, in different domains in the same program.
+- Let’s say you run a program on multiple domains and run a profile on one domain which allocates some objects, samples them, and runs the allocated callbacks. Let’s then suppose that that domain terminates but the profile keeps running (say if another domain is running the same profile) and an allocation callback is promoted in the GC and continues its lifecycle. It is generally the rule that callbacks should be run by the domain that allocated the object, but if that original domain has terminated the callback may be run by a different domain because the object might still be alive on the major heap. When the object is freed and
statmemprof would need to run a deallocation callback, it can also run that callback from a different domain if the original domain has been terminated.
+- Should call-backs keep running after the profiler has called
stop? In OCaml 4, after stop was called statmemprof would essentially throw away all of its sampled information. In OCaml 5 the user can determine whether to ask the profiler to stop sampling, where statmemprof stops sampling new allocations but keeps the information, or stop and discard where the profiler discards all the information held for that profile. This wasn’t a relevant feature for OCaml 4 since a terminated domain meant the program had ended and statmemprof could just disregard that information. With OCaml 5, longer running memory profiling is more likely, and we need to be able to distinguish between the two stop calls.
+- Lastly, a lot of work went into synchronisation and ensuring that no domain was ever waiting for
statmemprof before being able to continue its jobs. Statmemprof only uses one lock to enforce synchronisation, which occurs when a domain terminates while statmemprof is still running. Its data is put on the orphans list which is protected by a lock. Any other domain can then adopt this data.
+
+Are you using statmemprof? Please provide feedback and raise any issues in the OCaml repo and on the OCaml Discuss forum.
+Until Next Time!
+Curious about how we maintain and restore features to OCaml 5? Read more of our multicore and compiler blog posts, such as compaction, compiler maintenance, and catching data races.
+Connect with Tarides online on Bluesky, Mastodon, Threads, and LinkedIn or sign up for our mailing list to stay updated on our latest projects.
+Acknowledgements
+A huge thank you to Nick Barnes and Tim McGilchrist for their invaluable and extensive input on this post.
+
diff --git a/data/planet/tarides/full-blown-productivity-in-vscode-with-ocaml.md b/data/planet/tarides/full-blown-productivity-in-vscode-with-ocaml.md
new file mode 100644
index 0000000000..b2defca85a
--- /dev/null
+++ b/data/planet/tarides/full-blown-productivity-in-vscode-with-ocaml.md
@@ -0,0 +1,92 @@
+---
+title: Full blown productivity in VSCode with OCaml
+description: More features for ocaml-lsp that enhance productivity when writing OCaml.
+url: https://tarides.com/blog/2025-02-28-full-blown-productivity-in-vscode-with-ocaml
+date: 2025-02-28T00:00:00-00:00
+preview_image: https://tarides.com/blog/images/adopt_ocaml-1360w.webp
+authors:
+- Tarides
+source:
+---
+
+Happy New Year, OCamlers! 🎉
+As we usher in another year, we have something special to celebrate — a New Year's gift that promises to make your coding experience even better!
+We have been working on exciting new features in VSCode designed to boost productivity, streamline workflows, and make your development journey smoother and more enjoyable.
+For users of Emacs, we have a brand new emacs mode for interacting with the lsp server that will make your coding experience as enjoyable as it should be. Check out the Discuss announcement at Release of ocaml-eglot 1.0.0 and the project repository at ocaml-eglot.
+Without further ado, let's "unwrap" 🎁 these features for your viewing pleasure.
+1. Type of Selection
+This feature enhances code comprehension by allowing you to grow or shrink the selection to view updated types at different levels of granularity. You can adjust the verbosity of the type information to suit your needs, providing either a concise or detailed view. This information can be accessed conveniently through the default hover pop-up or via a dedicated output panel, making it adaptable to your workflow.
+Command name: Get the Type of the Selection
+
+- Command shortcut: Alt + T
+- Grow Selection: continously press Alt + T
+- Shrink Selection: Alt + Shift + T
+- Add Verbosity: Alt + V
+
+
+Using a dedicated Output Panel
+In the settings/preferences of the ocaml platform extension, you can toggle an option to display the results of type selection in a dedicated output panel.
+
+
+2. Search by Type or Polarity
+Looking for functions or values that match a specific type?
+The Search by Type/Polarity feature let's you input a type signature, e.g., int -> string or a polarity -int +string, and then it fetches all matching functions and values across your project.
+Command name: Search a value by type or polarity
+Command shortcut: Alt + F
+
+-
+
Search by Type
+
+
+-
+
Search by Polarity
+
+
+
+3. Construct Typed Holes
+This feature let's you construct possible values for a given typed hole.
+Command name: List values that can fill the selected typed-hole
+Command shortcut: Alt + C
+
+This feature also comes with a configurable option that allows it to construct values for the next typed hole automatically.
+
+
+4. Jump to a specific Target
+Traditional navigation, while it works, falls short when it comes to navigation in OCaml. This feature provides a seamless way to jump to specific targets which are closest to your cursor in the source code. For example, a large match construct and you could jump from one case to the next effortlesly.
+Command name: List possible parent targets for jumping
+Command shortcut: Alt + J
+At this point, we support the following targets:
+
+- Modules
+- Functions
+- Let statements
+- Match statements
+- Match cases (previous and next)
+
+
+5. Navigate Typed Holes
+This feature let's you navigate to typed holes.
+Command name: List typed holes in the file for navigation
+
+- As you move through the list with your arrow keys, the cursor jumps to the typed hole to give you a preview.
+When you make a selection, the cursor stays there.
+
+
+
+- If you toggle the Navigate
+
+
+
+
+- If you don't feel like jumping to a typed hole yet, just hit Esc and your cursor will portal back to it's original position.
+
+
+Hope you are excited to try out these new features. It is our wish that you have a much better and smoother experience while coding OCaml in VsCode.
+Please feel free to open issues if you discover a problematic behaviour:
+
+You can connect with Tarides on Bluesky, Mastodon, Threads, and LinkedIn or sign up for our mailing list to stay updated on our latest projects. We look forward to hearing from you!
+
diff --git a/data/planet/tarides/mirageos-on-ocaml-5.md b/data/planet/tarides/mirageos-on-ocaml-5.md
new file mode 100644
index 0000000000..9fec9bdd69
--- /dev/null
+++ b/data/planet/tarides/mirageos-on-ocaml-5.md
@@ -0,0 +1,41 @@
+---
+title: MirageOS on OCaml 5!
+description: Discover the ongoing work to make MirageOS compatible with OCaml 5 including
+ the Solo5, Unikraft, and improved cross-compilation efforts.
+url: https://tarides.com/blog/2025-02-06-mirageos-on-ocaml-5
+date: 2025-02-06T00:00:00-00:00
+preview_image: https://tarides.com/blog/images/junction-1360w.webp
+authors:
+- Tarides
+source:
+---
+
+OCaml 5 brought significant changes to fundamental parts of the language – notably concurrency using effects and multithreaded parallelism. This has caused some features and tools compatible with OCaml 4.14 to be incompatible with the new update, and several projects at Tarides aim to restore compatibility where that is the case. In today’s post, we will focus on the efforts toward creating a MirageOS port for OCaml 5.
+The main benefit of making MirageOS compatible with OCaml 5 is to make it possible to explore how to best take advantage of features unique to the latest version of the language. An early proof-of-concept thus experimented with replacing Lwt with the new concurrency library Eio. This is just one example, but it illustrates why bringing OCaml 5 support to MirageOS is a high priority for our team.
+To port MirageOS to OCaml 5 we have been contributing to the port using Solo5 backends but we are also exploring the possibility to use Unikraft backends. The Solo5 port has been released recently, with the versions 1.x of the ocaml-solo5 package on opam. As a complementary effort, work has also been ongoing to make cross-compilation easier in general. This will benefit not just the MirageOS project but also any cross-compilation projects for OCaml in the future. Let’s dive into the updates!
+The Solo5 Backend
+The first goal was to restore support for a backend that was available to OCaml 4.14 MirageOS users: Solo5. Solo5 is popular with developers building MirageOS unikernels, and since it is currently the only fully supported base for building a freestanding Mirage application, getting Solo5 support for OCaml 5 is a big step in the right direction for OCaml users.
+The PRs that introduce OCaml 5.2.1 support via the Solo5 API is the fruit of the collective effort of many developers, including, in no particular order: Pierre Alain, Fabrice Buoro, Romain Calascibetta, Christiano Haesbert, Samuel Hym, @kit-ty-kate, Hannes Mehnert, and many more helpful eyes.
+So, what is Solo5? Mirage applications can run in a hypervisor, such as KVM or Xen, without a full OS, and Solo5 provides minimal services (such as telling the time, reading or writing a block on the disk or network, etc.) for running an application there. OCaml-Solo5 adds the extra libraries required to build the OCaml runtime on top of Solo5.
+What has Changed?
+As you can imagine, the significant features introduced in OCaml 5 depend on correspondingly large changes to its underlying design. These include assumptions of how the OS works, how the C compiler works, and how the build system is set up. All of these modifications have a direct impact on what OCaml-Solo5 must provide to build the OCaml runtime for MirageOS unikernels.
+If you prefer to dive right into the code changes, I recommend that you visit the PR directly. Otherwise, let’s take a look at what’s new!
+Nolibc Extensions
+The usage of (OS) threads has changed, as well as how memory is managed (relying in particular on mmap/munmap), some C features such as thread-local storage and C11 atomics are now required. Support for all of these must be added in a freestanding setting such as MirageOS, even if Solo5 remains monocore. Therefore, to make OCaml-Solo5 compatible with the latest release, developers have amended the nolibc library and the way the C compiler is invoked. The modifications come in the form of extensions to nolibc, and most of the nolibc extensions included in the PR are inherited from previous PRs by @kit-ty-kate, Romain Calascibetta, and Pierre Alain, including changes to pthread, mmap and TLS.
+Build System
+To address the build system changes, the PR applies version-specific patches to sources when fetched. This replaces the previous method of modifying the OCaml build system with seds and echos. The reasoning behind this change is twofold: Firstly, all bar one of the patches have been designed to improve how the compiler build system supports cross-compilation, simplifying the maintenance of the OCaml and Solo5 compatibility for MirageOS. Secondly, making the modification system into separate patches with full explanation messages makes reviewing them easier and clarifies to the user which modified build system they rely on. A neat bonus of this restructuring is that the .opt versions of the compiler are now also built, which should result in better performance, particularly when building large unikernels.
+Toolchain
+The update also means that the ocaml-solo5 package now installs a new {aarch64,x86_64}-solo5-ocaml-* toolchain. Creating a toolchain avoids baking the build-time directories containing nolibc and openlibm into the generated OCaml compilers. The package generates two versions of the toolchain: one with built-time directories, that is added to PATH only when the compiler builds, and the other with the final destination directories, installed in the bin directory by opam.
+Beside adding compatibility with OCaml 5 for Solo5, we are also exploring alternative options. In particular, we are working on adding Unikraft support to test whether it can provide better performance.
+The Unikraft Backend
+Unikraft is a Unikernel Development Kit that lets users create custom unikernels with a large support for standard APIs to help port applications. It is an open-source project maintained and supported by over fifty active contributors. The main benefit of adding support for a Unikraft backend to MirageOS is to improve I/O performance in comparison to the Solo5 API.
+Because of these potential benefits, adding the Unikraft backend is a high priority. Currently, there are several repositories being worked on, the most mature of which is ocaml-unikraft. The project is still in an exploratory phase, and more updates will follow when we have more to share.
+Making the Build of OCaml Cross Compilers Easier
+In addition to new backends, part of improving the user experience with OCaml 5 has focused on improving the compiler’s build system, in particular regarding cross compilers, which is helpful for MirageOS users since OCaml-Solo5 is really a cross compiler to the Solo5 target.
+The first step to streamlining the compiler’s build system involved reducing the number of Makefiles down to one, the root Makefile. By bringing all the build logic into one place and avoiding duplication and stacking dependencies, the compiler’s build system is more consistent and easier to use. The effort is split into many PRs, both big and small, including #11243, #11248, #11268, #11420, #11675.
+In addition to reducing the number of Makefiles, the build system improvements also involved improving ocamldep. It needed to be able to distinguish between source vs build trees and have support for lex and yacc input files. The effort also included breaking the dynlink library’s dependency on compilerlibs to make the build system simpler and faster (#11996).
+Continuing on this work, several PRs have brought more fixes and improvements to cross-compilation, with more on the way. Currently, the largest upstreamed PRs are #13281, #13282, #13526, and #13312. If you use the new OCaml 5 capabilities in Mirage with either the Solo5 or Unikraft, you can take advantage of the simplified build systems and cross-compilation.
+Try it Yourself and Stay in Touch
+OCaml-Solo5 is released and you can get started simply by building MirageOS in a 5.2.1 switch, with no pinning involved. Samuel wrote a quick guide on how to get started with ocaml-solo5 in a Discuss post, and we recommend you give it a try. For Unikraft, keep an eye out for updates as work continues behind-the-scenes.
+You can connect with us on Bluesky, X, Mastodon, Threads, and LinkedIn or sign up for our mailing list to stay updated on our latest projects. We look forward to hearing from you!
+
diff --git a/data/planet/tarides/ocaml-53-features-and-fixes.md b/data/planet/tarides/ocaml-53-features-and-fixes.md
new file mode 100644
index 0000000000..5ac6a30af5
--- /dev/null
+++ b/data/planet/tarides/ocaml-53-features-and-fixes.md
@@ -0,0 +1,60 @@
+---
+title: 'OCaml 5.3: Features and Fixes!'
+description: OCaml 5.3 is released! This post gives you an overview of returning features,
+ optimisations, and fixes as well as a taste for what's to come.
+url: https://tarides.com/blog/2025-01-09-ocaml-5-3-features-and-fixes
+date: 2025-01-09T00:00:00-00:00
+preview_image: https://tarides.com/blog/images/OCaml53-1360w.webp
+authors:
+- Tarides
+source:
+---
+
+We have a brand new OCaml release on our hands! 5.3 comes packed with features, fixes, and optimisations, including the return of some ‘familiar faces’. Support for the MSVC port is returning, as is statistical memory profiling now compatible with multicore projects.
+This post highlights new and restored features, notable changes and user experience improvements, plus some bug fixes. There is no way that I can cover everything in this update, so I recommend that you check out the Changes document on GitHub for the full list of contributions!
+MSVC
+The 5.3 release restores support for the MSVC port of OCaml on Windows, marking the last remaining platform from 4.x to regain support in 5.x. This is part of a wider effort to achieve feature parity between OCaml 4.14 and OCaml 5, of which compaction is a previous example, making the transition between versions as smooth as possible. The bulk of the effort is summarised in PRs #12954 and #12909 opened by David Allsopp, Antonin Décimo, and Samuel Hym (review by Miod Vallat and Nicolás Ojeda Bär).
+Since the OCaml 5 runtime uses C11 atomics, supported platforms need to be compatible with them as well. Visual Studio 2022 introduced experimental support for C11 atomics which made the MSVC port of OCaml 5 possible, but the team needed to test out the feature first. This exploratory effort led to several bug reports addressed by Microsoft, and once these were completed (alongside a lot of other work, including fixing the winpthreads library of the mingw-w64 project to that it builds with MSVC), the MSVC port was ready for public release.
+As part of the project bringing MSVC back the team explored clang-cl, an alternative command line interface to Clang designed to be compatible with the MSVC compiler cl.exe. This was helpful because clang-cl has a different set of warnings and tips to MSVC, and using it effectively gave them a ‘second opinion’ on their code. The main PR for this side of the project is #13093.
+Statmemprof
+OCaml 4.14 had support for statistical memory profiling, a feature of the language that can sample memory allocations allowing tools like Memtrace to help users identify how their programs are using memory. The multicore update introduced significant complexity to the process which made it necessary to drop support for 5.0; but work soon commenced to restore support under our feature parity banner! In 5.3, statmemprof makes its return, now equipped with multicore capabilities.
+So how does it work? Statmemprof can check the allocation of memory at some given frequency (lambda) per word or unit of data. By sampling a fraction of allocations at random, we are able to monitor programs in a language like OCaml which allocates high rates of memory. It would be far too expensive performance-wise to monitor every allocation.
+The new design has a lot in common with the OCaml 4 implementation of statmemprof, but with several tricky optimisations and changes to account for the significant complication of multiple domains and threads. Delve into the details in the PRs #12923 and #11911 by Nick Barnes (external reviews by Stephen Dolan, Jacques-Henri Jourdan, and Guillaume Munch-Maccagnoni).
+Deep Effect Handlers
+OCaml 5.0 came with experimental support for algebraic effects, which allow users to describe computations and what effects they are expected to create. A handler essentially manages a computation by monitoring its execution and keeping track of resulting effects. This ‘management’ can be done in two ways, deeply or shallowly. A shallow effect handler monitors a computation until it either terminates or generates one effect, only handling that effect. A deep effect handler always manages a computation until it terminates and handles all of the effects performed by it.
+PR #12309 (Leo White, Tom Kelly, Anil Madhavapeddy, KC Sivaramakrishnan, Xavier Leroy and Florian Angeletti, review by the same, Hugo Heuzard, and Ulysse Gérard) introduces effect syntax for deep effect handlers, rules that define the structure for writing them, compatible with the type checker and with support for pattern matching. This change aims to simplify the code needed to use deep effect handlers, improving user experience. Note that you can still use shallow effect handlers, and there is a good tutorial for using both in the correspondingly updated manual page.
+Debugging Improvements
+Another long-term project coming to fruition in this update are the several improvements to debugging on macOS. The platform is popular with a wide variety of OCaml users, including compiler developers, and they need good debugging workflows for their programs.
+LLDB is the only supported native debugger on macOS, for both the ARM64 and x86_64 architectures. The improvements enable several new features:
+
+- #13163 enable frame pointers on macOS x86_64 (Tim McGilchrist, review by Sébastien Hinderer and Fabrice Buoro): This PR introduces support for a common technique used by profiling tools including Linux perf, eBPF, FreeBSD, and LLDB, called stack-walking. Various performance tools use stack walking to reconstruct call graphs for programs, and frame pointers are what enable them to do so.
+- #13241, #13261, #13271, add CFI_SIGNAL_FRAME to arm64 and RISC-V runtimes for the purpose of displaying backtraces correctly in GDB (Tim McGilchrist, review by Miod Vallat, Gabriel Scherer and KC Sivaramakrishnan): This change helps sync up the runtime for the arm64 architecture for macOS (and the RISC-V runtime) with the amd64 and s390x runtimes. The two additional PRs add improvements to the first.
+- #13136 Compatible LLDB and GDB Python extensions (Nick Barnes): This PR replaces some old GDB macros (used to debug OCaml programs) with faster and more capable extensions, and makes those extensions available in LLDB. This is especially useful to macOS users who can’t use GDB.
+
+OS-Based Synchronisation for Stop-the-World Sections
+PR #12579 (B. Szilvasy, review by Miod Vallat, Nick Barnes, Olivier Nicole, Gabriel Scherer and Damien Doligez) improves user experience by replacing generic busy-wait synchronisation with OS-based synchronisation primitives, namely barriers and futexes. The change has significant performance benefits, especially on Windows machines, where spinning was causing long wait times. You can learn more about it in our blog post on the project.
+User Experience Improvements
+
+- #12868 Refresh HTML manual/API docs style (Yawar Amin, review by Simon Grondin, Gabriel Scherer, and Florian Angeletti): An update to the OCaml Manual which simplifies the colours, removes the gradients, and fixes the search button. It’s a nice improvement to a part of the OCaml ecosystem that is visible to users of all different backgrounds and contexts.
+- #13201, #13244 (Sébastien Hinderer, review by Miod Vallat, Gabriel Scherer and Olivier Nicole), and #12904 (Olivier Nicole, suggested by Sébastien Hinderer and David Allsopp, external review by Gabriel Scherer) various improvements to TSan: These three PRs represent the continuous work being put in to bring improvements to TheadSanitizer or TSan. They include speedups and the ability for users to choose which PRs they want to run the TSan testsuite on.
+- #13014 (Miod Vallat, review by Nicolás Ojeda Bär) add per function sections support to the missing compiler backends: This PR is an example of how much focus there is on ensuring that each native backend is equally supported, having features available across all Tier-1 platforms. Here, the compile-time option
function–sections was re-enabled on all previously unsupported (POWER, riscv64, and s390x) native backends.
+- #11996 emancipate
dynlink from compilerlibs (Sébastien Hinderer and Stephen Dolan, review by Damien Doligez and Hugo Heuzard): The dynlink library used to depend on compilerlibs, having to embed a copy of compilerlibs meaning that it would be compiled twice, costing the user in time and performance. After the change, the build time and size of both dynlink.cma and dynlink.cmxawere reduced.
+
+Miscellaneous Bug Fixes
+These two bug fixes grew out of internship projects at Tarides, it's great to see how these projects can benefit the language as a whole.
+
+- #13419 (B. Szilvasy and Nick Barnes, review by Miod Vallat, Nick Barnes, Tim McGilchrist, and Gabriel Scherer): This PR addressed resource leaks that caused memory bugs in the runtime events system.
+- #13535 (Antonin Décimo, Nick Barnes, report by Nikolaus Huber and Jan Midtgaard, review by Florian Angeletti, Anil Madhavapeddy, Gabriel Scherer, and Miod Vallat): Expanded the documentation for
Hashtbl.create to explain that negative values are allowed in the hash table but will be disregarded.
+
+These bug fixes stem from discoveries made during the release cycle of the 5.3 update. Catching and fixing broken bits of code is an important but often lengthy part of the release process.
+
+- #13138 (Gabriel Scherer, review by Nick Roberts): This PR is an old one, first opened eight years ago in 2016! Optimised pattern matching with mutable and lazy patterns was observed to result in occasions where seemingly impossible cases were taken, causing unsoundness issues. After lengthy efforts to narrow down the cause, the problem has been fixed for 5.3!
+- #13519 (Sébastien Hinderer, report by William Hu, review by David Allsopp): This PR restored backward compatibility lost when renaming some items in
Makefile.config.
+- #13591 (Antonin Décimo, review by Nick Barnes, report by Kate Deplaix): This PR fixed a problem whereby compiling C++ code using the OCaml C API resulted in a name-mangled
caml_state on Cygwin. The fix ensured that installed headers were compatible with C++ and protected the ones that were not with CAML_INTERNALS.
+- #13471 (Florian Angeletti, review by Gabriel Scherer): Added a flag to define the list of keywords recognisable by the lexer, making adding future keywords to OCaml easier.
+- #13520 (David Allsopp, review by Sébastien Hinderer and Miod Vallat): Fixed the compilation of native-code versions of systhreads.
+
+What’s Next?
+Work on OCaml continues! The next few months will bring more features and bug fixes to the language, with focus on big changes like the relocatable compiler, unloadable runtime, and laying the ground work for project-wide renaming and other powerful navigation and refactoring features. The OCaml changelog is the place to go to keep up with what’s new, as well as the OCaml Discuss forum.
+You can connect with us on Bluesky, X, Mastodon, Threads, and LinkedIn or sign up for our mailing list to stay updated on our latest projects. We look forward to hearing from you!
+
diff --git a/data/planet/tarides/ocaml-in-space-spaceos-is-on-a-satellite.md b/data/planet/tarides/ocaml-in-space-spaceos-is-on-a-satellite.md
new file mode 100644
index 0000000000..b451115212
--- /dev/null
+++ b/data/planet/tarides/ocaml-in-space-spaceos-is-on-a-satellite.md
@@ -0,0 +1,25 @@
+---
+title: 'OCaml in Space: SpaceOS is on a Satellite!'
+description: Our sister company Parsimoni sends OCaml into space aboard DPhi Space's
+ Clustergate ride-sharing platform to test the cababilities of SpaceOS.
+url: https://tarides.com/blog/2025-04-03-ocaml-in-space-spaceos-is-on-a-satellite
+date: 2025-04-03T00:00:00-00:00
+preview_image: https://tarides.com/blog/images/rocket-launch-1360w.webp
+authors:
+- Tarides
+source:
+---
+
+OCaml is in space! With its impressive performance and security guarantees, OCaml is a great choice for the many interconnected devices that power our world. Satellites are not only crucial to the functioning of these devices, but the new generation of satellites are beginning to function like Cloud servers, where one device hosts more than one software and performs more than one service.
+The natural next step, considering the growing need for agile multi-purpose satellites and the suitability of OCaml-based solutions, is to put OCaml to work in space! Following up on our sister company Parsimoni’s SpaceOS project, there has been an exciting development on this front. Parsimoni has partnered with DPhi Space and put SpaceOS software aboard their Clustergate ride-sharing platform for hosted payloads. OCaml launched into space aboard Transporter-13 on the 15th of March.
+The Clustergate Platform
+Clustergate is a payload platform developed by DPhi Space and deployed on a host satellite. The goal of this platform is to offer the power and computing capabilities of a larger satellite to cube-sat-sized payloads at a lower cost, where customers only pay for what they need. Making satellite deployment more accessible and agile will change the future of space innovation, incentivising new actors and services.
+Parsimoni is changing the way that satellite software is designed, centring on the security, efficiency, and scalability of satellite payload management. By utilising unikernel technology written in OCaml, SpaceOS can host multiple applications with a reduced attack surface, safe from a multitude of common security vulnerabilities, without the overhead of typical virtual machines.
+What’s on the Satellite?
+DPhi Space has embedded its own Clustergate computer on Transporter-13, and the team behind SpaceOS has onboarded OCaml 5 software on the satellite. As part of a larger ‘rideshare’ mission, DPhi Space’s computer hosts a variety of software and hardware from different partners.
+So, what OCaml code is on the satellite? All in all, the team have included a simple version of Petrel to manage unikernels, the necessary ‘glue code’ to give unikernels access to orbital data, the basic functionality needed to manage data transfers and send commands, plus a ‘hello world’ unikernel. Petrel is an experimental unikernel manager and orchestrator (written in OCaml 5 with Eio concurrency) based on Albatross by Robur. Instead of hardware virtualisation (which is not available on this flight), our team uses the solo5-spt backend of MirageOS as the unikernel runtime, which leverages Linux’s seccomp feature to isolate the software payloads.
+During the mission, they will test whether the system works by sending new MirageOS unikernels using the data onboard. Parsimoni expects to start testing the software onboard in May. The goal is to show SpaceOS in action, sending and receiving interesting data and deploying self-contained applications on a limited bandwidth. They will start with the hello world and move on to more complex tasks utilising orbital data.
+Until Next Time
+You can watch the launch of Transporter-13 to see the moment when OCaml travels through the atmosphere! If you want to discuss the opportunities that SpaceOS offers for deploying specialised and secure applications that use limited resources efficiently, you can contact us or Parsimoni to find out more.
+Connect with Tarides online on Bluesky, Mastodon, Threads, and LinkedIn or sign up for our mailing list to stay updated on our latest projects.
+
diff --git a/data/planet/tarides/tarides-2024-in-review.md b/data/planet/tarides/tarides-2024-in-review.md
new file mode 100644
index 0000000000..adefa66ed6
--- /dev/null
+++ b/data/planet/tarides/tarides-2024-in-review.md
@@ -0,0 +1,544 @@
+---
+title: 'Tarides: 2024 in Review'
+description: Tarides advanced OCaml in 2024 with the Dune Developer Preview, the first
+ stable multicore release, 8x WebAssembly boosts, and expanded Windows support.
+url: https://tarides.com/blog/2025-01-20-tarides-2024-in-review
+date: 2025-01-20T00:00:00-00:00
+preview_image: https://tarides.com/blog/images/review-1360w.webp
+authors:
+- Tarides
+source:
+---
+
+At Tarides, we believe in making OCaml a
+mainstream programming language by improving its tooling and
+integration with other successful ecosystems. In 2024, we focused our
+efforts on initiatives to advance this vision by addressing key
+technical challenges and engaging with the community to build a
+stronger foundation for OCaml’s growth. This report details our work,
+the rationale behind our choices, and the impact achieved. We are very
+interested in getting your feedback: please get in
+touch (or respond to the
+Discuss thread)
+if you believe we are going in the right direction.
+TL;DR – In 2024, Tarides focused on removing adoption friction with
+better documentation and tools; and on improving adoption via the
+integration with three key thriving ecosystems: multicore programming,
+web development, and Windows support. Updates to
+ocaml.org improved onboarding and documentation,
+while the Dune Developer Preview
+simplified workflows with integrated package management. Merlin added
+support for project-wide reference
+support
+and odoc 3,
+which is about to be released. OCaml 5.3 marked the first stable
+multicore release, and js_of_ocaml achieved up to 8x performance
+boosts in real-world commercial applications thanks to added support
+for WebAssembly. On Windows, opam 2.2 brought full compatibility and
+CI testing to all Tier 1 platforms on opam-repository, slowly moving
+community packages towards reliable and better support for
+Windows. Tarides’ community support included organising the first FUN
+OCaml conference, many local meetups, and two
+rounds of Outreachy internships.
+Better Tools: Toward a 1-Click Installation of OCaml
+Our primary effort in 2024 was to continue delivering on the OCaml
+Platform roadmap published
+last year. We focused on making it easier to get started with OCaml
+by removing friction in the installation and onboarding process. Our
+priorities were guided by the latest OCSF User
+Survey,
+direct user interviews, and
+feedback gathered from
+the OCaml community. Updates from Tarides and other OCaml Platform
+maintainers were regularly shared in the OCaml Platform
+Newsletter.
+OCaml.org
+OCaml.org is the main entry point for new users of OCaml. Tarides
+engineers are key members of the OCaml.org team. Using
+privacy-preserving analytics,
+the team tracked visitor behaviour to identify key areas for
+improvement. This led to a redesign of the installation
+page, simplifying the setup process, and a
+revamp of the guided tour of
+OCaml to better introduce the
+language. Both pages saw significant traffic increases compared to
+2023, with the installation page recording 69k visits, the tour
+reaching 65k visits and a very encouraging total number of visits
+increasing by +33% between Q3 and Q4 2024
+
+Efforts to improve user experience included a satisfaction survey
+where 75% of respondents rated their experience positively, compared
+to 17% for the previous version of the site. User testing sessions
+with 21 participants provided further actionable insights, and these
+findings informed updates to the platform. The redesign of OCaml.org
+community sections was completed using this feedback. It introduced
+several new features: a new Community landing
+page, an academic institutions
+page with course listings, and an
+industrial users showcase. The
+team also implemented an automated event
+announcement system to inform the community
+of ongoing activities.
+Progress and updates were regularly shared through the OCaml.org
+newsletters,
+keeping the community informed about developments. Looking ahead, the
+team will continue refining the platform by addressing feedback,
+expanding resources, and monitoring impact through analytics to
+support both new and experienced OCaml users. Lastly, the
+infrastructure they build is starting to be used by other communities:
+Rocq just announced their brand new
+website, built using the same codebase as ocaml.org!
+Dune as the Default Frontend of the OCaml Platform
+One of the main goals of the OCaml Platform is to make it easier for
+users—especially newcomers—to adopt OCaml and build projects with
+minimal friction. A critical step toward this goal is having a single
+CLI to serve as the frontend for the entire OCaml development
+experience (codenamed
+Bob in
+the past). This year, we made significant progress in that direction
+with the release of the Dune Developer
+Preview.
+Setting up an OCaml project currently requires multiple tools: opam
+for package management, dune for builds, and additional
+installations for tools like OCamlFormat or Odoc. While powerful, this
+fragmented workflow can make onboarding daunting for new users. The
+Dune Developer Preview consolidates these steps under a single CLI,
+making OCaml more approachable. With this preview, setting up and
+building a project is as simple as:
+
+dune pkg lock to lock the dependencies.
+dune build to fetch the dependencies and compile the project.
+
+This effort is also driving broader ecosystem improvements. The
+current OCaml compiler relies on fixed installation paths, making it
+difficult to cache and reuse across environments, so it cannot be
+shared efficiently between projects. To address this, we are working
+on making the compiler relocatable (ongoing
+work). This change will enable
+compiler caching, which means faster project startup times and fewer
+rebuilds in CI. As part of this effort, we also
+maintain
+patches to core OCaml projects to make them relocatable – and we
+worked with upstream to merge (like for
+ocamlfind). Tarides
+engineers also continued to maintain Dune and other key Platform
+projects, ensuring stability and progress. This included organising
+and participating in regular development meetings (for
+Dune,
+opam,
+Merlin,
+ppxlib, etc.)
+to prioritise community needs and align efforts across tools like Dune
+and opam to avoid overlapping functionality.
+The Dune Developer Preview is an iterative experiment. Early user
+feedback has been promising (the Preview’s NPS went from +9 in Q3
+2024 to +27 in Q4 2024), and future updates will refine the
+experience further. We aim to ensure that experimental features in the
+Preview are upstreamed into stable releases once thoroughly
+tested. For instance, the package management feature is already in
+Dune 3.17. We will announce and document it more widely when we believe
+it is mature enough for broader adoption.
+Editors
+In 2024, Tarides focused on improving editor integration to lower
+barriers for new OCaml developers and enhance the experience for
+existing users. Editors are the primary way developers interact with
+programming languages, making seamless integration essential for
+adoption. With more than 73% of developers using Visual Studio Code
+(VS
+Code),
+VS Code is particularly important to support, especially for new
+developers and those transitioning to OCaml. As part of this effort,
+Tarides wrote and maintained the official VS Code plugin for
+OCaml,
+prioritising feature development for this editor. We also support
+other popular editors like Emacs and Vim—used by many Tarides
+engineers—on a best-effort basis. Improvements to
+OCaml-LSP and
+Merlin, both maintained by Tarides,
+benefit all supported editors, ensuring a consistent and productive
+development experience.
+
+While several plugins for OCaml exist (OCaml and Reason
+IDE–128k
+installs,
+Hackwaly–90k
+installs), our OCaml VS Code
+plugin
+–now with over 208k downloads– is a key entry point for developers
+adopting OCaml in 2024. This year, we added integration with the Dune
+Developer Preview, allowing users to leverage Dune's package
+management and tooling directly from the editor. Features such as
+real-time diagnostics, autocompletion, and the ability to fetch
+dependencies and build projects without leaving VS Code simplify
+development and make OCaml more accessible for newcomers.
+The standout update in 2024 was the addition of project-wide
+reference
+support,
+a long-requested feature from the OCaml community and a top priority
+for commercial developers. This feature allows users to locate all
+occurrences of a term across an entire codebase, making navigation and
+refactoring significantly easier—especially in large
+projects. Delivering this feature required coordinated updates across
+the ecosystem, including changes to the OCaml compiler, Merlin, OCaml
+LSP, Dune, and related tools. The impact is clear: faster navigation,
+reduced cognitive overhead, and more efficient workflows when working
+with complex projects.
+Additional improvements included support for new Language Server
+Protocol features, such as signature_help and inlay_hint, which
+enhance code readability and provide more contextual
+information. These updates enabled the introduction of new commands,
+such as the "Destruct" command. This little-known but powerful
+feature
+automatically expands a variable into a pattern-matching expression
+corresponding to its inferred type, streamlining tasks that would
+otherwise be tedious.
+
+
+
+Documentation
+Documentation was identified as the number one pain point in the
+latest OCSF
+survey. It
+is a critical step in the OCaml developer journey, particularly after
+setting up the language and editor. Tarides prioritised improving
+odoc to make it easier for developers to find information, learn the
+language, and navigate the ecosystem effectively. High-quality
+documentation and tools to help developers get "unstuck" are essential
+to reducing friction and ensuring a smooth adoption experience.
+Tarides is the primary contributor and maintainer of
+odoc, OCaml’s main documentation
+tool. In preparation for the odoc 3
+release, our
+team introduced two significant updates. First, the odoc Search
+Engine
+was integrated, allowing developers to search directly within OCaml
+documentation via the Learn page. Second,
+the odoc
+Cheatsheet
+provides a concise reference for creating and consuming OCaml
+documentation. We would like to believe that these updates, deployed
+on ocaml.org, were the main cause of a 45% increase in package
+documentation usage on
+https://ocaml.org/pkg/ in Q4 2024!
+
+Another area where developers often get stuck is debugging programs
+that don’t work as expected. Alongside reading documentation, live
+debuggers are crucial for understanding program issues. Tarides worked
+to improve native debugging for OCaml, focusing on macOS, where LLDB
+is the only supported debugger. Key progress included a name mangling
+fix to improve symbol
+resolution, restoring ARM64 backtraces, and introducing Python shims
+for code sharing between LLDB and GDB.
+OCaml’s error messages remain a common pain point, particularly for
+syntax errors. Unlike Rust’s error
+index, OCaml
+does not (yet!) have a centralised repository of error
+explanations. Instead, we are focused on making error messages more
+self-explanatory. This requires developing new tools, such as
+lrgrep, a domain-specific
+language for analysing grammars built with Menhir. lrgrep enables
+concise definitions of error cases, making it possible to identify and
+address specific patterns in the parser more effectively. This
+provides a practical way to improve error messages without requiring
+changes to the compiler. In December 2024, @let-def successfully
+defended his PhD (a collaboration between Inria and Tarides) on this
+topic, so expect upstreaming work to start soon.
+OCaml Package Ecosystem
+The last piece of friction we aimed to remove in 2024 was ensuring
+that users wouldn’t encounter errors when installing a package from
+the community. This required catching issues early—before packages are
+accepted into opam-repository and made available to the broader
+ecosystem. To achieve this, Tarides has built and maintained extensive
+CI infrastructure, developed tools to empower contributors, and guided
+package authors to uphold the high quality of the OCaml package
+ecosystem.
+In 2024, Tarides’ CI infrastructure supported the OCaml community at
+scale, handling approximately 20 million jobs on 68 machines
+covering 5 hardware architectures. This infrastructure continuously
+tested packages to ensure compatibility across a variety of platforms
+and configurations, including OCaml’s Tier 1 platforms: x86, ARM,
+RISC-V, s390x, and Power. It played a critical role during major
+events, such as new OCaml releases, by validating the ecosystem’s
+readiness and catching regressions before they impacted
+users. Additionally, this infrastructure supported daily submissions
+to opam-repository, enabling contributors to identify and resolve
+issues early, reducing downstream problems. To improve transparency
+and accessibility, we introduced a CI pipeline that automates
+configuration updates, ensuring seamless deployments and allowing
+external contributors to propose and apply changes independently.
+In addition to maintaining the infrastructure, Tarides developed and
+maintained the CI framework running on top of it. A major focus in
+2024 was making CI checks available as standalone CLI tools
+distributed via opam. These tools enable package authors to run
+checks locally, empowering them to catch issues before submitting
+their packages to opam-repository. This approach reduces reliance on
+central infrastructure and allows developers to work more
+efficiently. The CLI tools are also compatible with GitHub Actions,
+allowing contributors to integrate tests into their own workflows. To
+complement these efforts, we enhanced opam-repo-ci, which remains an
+essential safety net for packages entering the repository. Integration
+tests for linting and reverse dependencies were introduced, enabling
+more robust regression detection and improving the reliability of the
+ecosystem.
+To uphold the high standards of the OCaml ecosystem, every package
+submission to opam-repository is reviewed and validated to ensure it
+meets quality criteria. This gatekeeping process minimises errors
+users might encounter when installing community packages, enhancing
+trust in the ecosystem. In 2024, Tarides continued to be actively
+involved
+in maintaining the repository, ensuring its smooth operation. We also
+worked to guide new package authors by updating the contributing
+guide
+and creating a detailed
+wiki with actionable
+instructions for adding and maintaining packages. These resources were
+announced on
+Discuss
+to reach the community and simplify the process for new contributors,
+improving the overall quality of submissions.
+Playing Better with the Larger Ecosystem
+Concurrent & Parallel Programming in OCaml
+
+"Shared-memory multiprocessors have never really 'taken off', at
+least in the general public. For large parallel computations, clusters
+(distributed-memory systems) are the norm. For desktop use,
+monoprocessors are plenty fast."
+
+Twenty+ years after this statement, processors are multicore by
+default, and OCaml has adapted to this reality. Thanks to the combined
+efforts of the OCaml Labs and Tarides team, the OCaml 5.x series
+introduced multicore support after a decade of research and
+experimentation.
+While this was a landmark achievement, the path to making multicore
+OCaml stable, performant, and user-friendly has required significant
+collaboration and continued work. In 2024, Tarides remained focused on
+meeting the needs of the broader community and commercial users.
+OCaml 5.3 (released last week) was an important milestone in this
+journey. With companies such as Routine,
+Hyper, and
+Asemio
+adopting OCaml 5.x, and advanced experimentation ongoing at Jane
+Street, Tezos, Semgrep, and others, OCaml 5.3 is increasingly seen as
+the first “stable” release of the multicore series. While some
+performance issues
+remain in specific parts of the runtime, we are working closely with
+the community to address them in OCaml 5.4. Tarides contributed
+extensively to the
+5.2
+and
+5.3
+releases by directly contributing to nearly two-thirds of the merged
+pull requests. Since Multicore OCaml was incorporated upstream in
+2023, we have been continuously involved in the compiler and language
+evolution in collaboration with Inria and the broader OCaml ecosystem.
+Developing correct concurrent and parallel software is inherently
+challenging, and this applies as much to the runtime as to
+applications built on it. In 2024, we focused on advanced testing
+tools to help identify and address subtle issues in OCaml’s runtime
+and libraries. The property-based test
+suite reached
+maturity this year, uncovering over 40 critical issues, with 28
+resolved by Tarides engineers. Trusted to detect subtle bugs, such as
+issues with orphaned
+ephemerons,
+the suite has become an integral part of OCaml’s development
+workflow. Importantly, it is accessible to contributors without deep
+expertise in multicore programming, ensuring any changes in the
+compiler or the runtime do not introduce subtle concurrency bugs.
+
+Another critical effort was extending ThreadSanitizer (TSAN) support
+to most Tier 1 platforms and applying it extensively to find and fix
+data races in the
+runtime. This
+work has improved the safety and reliability of OCaml’s multicore
+features and is now part of the standard testing process, further
+ensuring the robustness of the runtime.
+Beyond testing, we also worked to enhance library support for
+multicore programming. The release of the Saturn
+library
+introduced lock-free data structures tailored for OCaml 5.x. To
+validate these structures, we developed
+DSCheck,
+a static analyser for verifying lock-free algorithms. These tools,
+along with Saturn itself, provide developers with reliable building
+blocks for scalable multicore applications.
+Another promising development in 2024 was the introduction of the
+Picos
+framework. Picos aims to provide a low-level foundation for
+concurrency, simplifying interoperability between libraries like Eio,
+Moonpool, Miou, Riot, Affect, etc. Picos offers a simple,
+unopinionated, and safe abstraction layer for concurrency. We believe
+it can potentially standardise concurrency patterns in OCaml, but we
+are not there yet. Discussions are underway to integrate parts of
+Picos into higher-level libraries and, eventually, the standard
+library. We still have a long way to go, and getting feedback from
+people who actively tried it in production settings would be very
+helpful!
+Web
+Web development remains one of the most visible and impactful domains
+for programming languages; JavaScript, HTML, and CSS are the most
+popular
+technologies
+in 2024. For OCaml to grow, it must integrate well with this
+ecosystem. Fortunately, the OCaml community has already built a solid
+foundation for web development!
+On the frontend side, in 2024, Tarides focused on strengthening key
+tools like js_of_ocaml
+by expanding its support for WebAssembly
+(Wasm). js_of_ocaml (JSOO) has long been the backbone of OCaml’s web
+ecosystem, enabling developers to compile OCaml bytecode into
+JavaScript. This year, we merged Wasm support back into
+JSOO, unifying the
+toolchain and simplifying adoption for developers. The performance
+gain of Wasm has been very impressive so far: CPU-intensive
+applications in commercial settings have seen 2x to 8x speedups
+using Wasm compared to traditional JSOO. We also worked on better
+support for effect handlers in js_of_ocaml to ensure applications
+built with OCaml 5 can run as fast in the browser as they used to with
+OCaml 4.
+On the backend side, Tarides maintained and contributed to Dream, a
+lightweight and flexible web framework. Dream powers projects like
+our own website and the
+MirageOS website, where we maintain a fork to make
+Dream and MirageOS work well together. Additionally, in 2024, we
+enhanced cohttp, adding proxy
+support to address
+modern HTTP requirements.
+While Tarides focused on JSOO, wasm_of_ocaml, Dream, and Cohttp, the
+broader community made significant strides elsewhere. Tools like
+Melange offer an alternative for compiling OCaml to JavaScript, and
+frameworks like Ocsigen, which integrates backend and frontend
+programming, continue to push the boundaries of what’s possible with
+OCaml on the web. Notably, Tarides will build on this momentum in 2025
+through a grant to
+improve direct-style programming for Ocsigen.
+Windows
+Windows is the most widely used operating system, making first-class
+support for it critical to OCaml’s growth. In 2024, 31% of visitors
+to ocaml.org accessed the site from Windows,
+yet the platform’s support historically lagged behind Linux and
+macOS. This gap created barriers for both newcomers and commercial
+users. We saw these challenges firsthand, with Outreachy interns
+struggling to get started due to tooling issues, and commercial users
+reporting difficulties with workflow reliability and compilation
+speed.
+To address these pain points, Tarides, in collaboration with the OCaml
+community, launched the Windows Working
+Group. A
+key milestone that our team contributed to was the release this year
+of opam 2.2, three years after its predecessor. This release made
+the upstream opam-repository fully compatible with Windows for the
+first time, removing the need for a separate repository and providing
+Windows developers access to the same ecosystem as Linux and macOS
+users. The impact has been clear: feedback on the updated installation
+workflow has been overwhelmingly positive, with developers reporting
+that it "just works." The install page
+for Windows is now significantly shorter and simpler!
+In the OCaml 5.3 release, Tarides restored the MSVC Windows port,
+ensuring native compatibility and improving performance for Windows
+users. To further support the ecosystem, Tarides added Windows
+machines to the opam infrastructure, enabling automated testing for
+Windows compatibility on every new package submitted to opam. This has
+already started to improve package support, with ongoing fixes from
+Tarides and the community. The results are publicly visible at
+windows.check.ci.dev, which we run on
+our infrastructure, providing transparency and a way to track progress
+on the status of our ecosystem. While package support is not yet on
+par with other platforms, we believe that the foundations laid in
+2024—simplified installation, improved tooling, and continuous package
+testing—represent a significant step forward.
+Community Engagement and Outreach
+In 2024, Tarides contributed to building a stronger OCaml community
+through events, internships, and support for foundational
+projects. The creation of FUN OCaml 2024 in
+Berlin was the first dedicated OCaml-only event for a long time
+(similar to how the OCaml Workshop was separated from ICFP in the
+past). Over 75 participants joined for two days of talks, workshops,
+and hacking, and the event has already reached 5k+ views on
+YouTube. Tarides
+also co-chaired the OCaml Workshop at ICFP
+2024 in Milan, bringing together
+contributors from academia, industry, and open-source
+communities. These events brought together two different kinds of
+OCaml developers (with some overlap), bringing an interesting energy
+to our community.
+To expand local community involvement, Tarides organised OCaml hacking
+meetups in
+Manila
+and
+Chennai. To
+make it easier for others to host similar events, we curated a list of
+interesting hacking issues from past Cambridge
+sessions,
+now available on
+GitHub.
+As part of the Outreachy program, Tarides supported two rounds of
+internships in 2024, with results published on
+Discuss and
+watch.ocaml.org. These internships not only
+provided great contributions to our ecosystem but also brought fresh
+insights into the challenges faced by new users. For example, interns
+identified key areas where documentation and tooling could be
+improved, directly informing future updates.
+Tarides also maintained its commitment to funding critical open-source
+projects and maintainers. We continued funding
+Robur for their
+maintenance work on MirageOS (most of those libraries are used by many
+–including us– even in non-MirageOS context) and Daniel
+Bünzli, whose libraries like
+cmdliner are essential for some of our development.
+Finally, Tarides extended sponsorships to non-OCaml-specific events,
+including JFLA,
+BobConf,
+FSTTCS, and Terminal
+Feud (which garnered
+over 100k views). These events expanded OCaml’s visibility to new
+audiences and contexts, introducing the language to a broader
+technical community that –we hope– will discover OCaml and enjoy using
+it as much as we do.
+What’s Next?
+As we begin 2025, Tarides remains committed to making OCaml a
+mainstream language. Our focus this year is to position OCaml as a
+robust choice for mission-critical applications by enhancing developer
+experience, ecosystem integration, and readiness for high-assurance
+use cases.
+We aim to build on the Dune Developer Preview to further improve
+usability across all platforms, with a particular emphasis on Windows,
+to make OCaml more accessible to a broader range of
+developers. Simultaneously, we will ensure OCaml is ready for critical
+applications in industries where reliability, performance, and
+security are essential. Projects like
+SpaceOS
+showcase the potential of memory- and type-safe languages for
+safety-critical systems. Built on MirageOS and OCaml’s
+unique properties, SpaceOS is part of the EU-funded
+Orchide project and aims to set a new
+standard for edge computing in space. Additionally, SpaceOS is being
+launched in the US through our spin-off
+Parsimoni. However, these needs are not
+limited to Space: both the EU Cyber Resilience
+Act
+and the US cybersecurity
+initiatives
+highlight the growing demand for type-safe, high-assurance software to
+address compliance and security challenges in sensitive
+domains. Tarides believes that OCaml has a decisive role to play here
+in 2025!
+I’d like to personally thank our sponsors and customers, especially
+Jane Street, for their unwavering support over the years, and to
+Dennis Dang, our single recurring
+GitHub sponsor. Finally, to every member of Tarides who worked so hard
+in 2024 to make all of this happen: thank you. I’m truly lucky to be
+sailing with you on this journey!
+We are looking for sponsors on
+GitHub, are happy to
+collaborate on innovative projects
+involving OCaml or MirageOS and offer commercial
+services for open-source projects –
+including long-term support, development of new tools, or assistance
+with porting projects to OCaml 5 or Windows.
+
diff --git a/data/planet/tarides/tarides-at-bob-konferenz-2025.md b/data/planet/tarides/tarides-at-bob-konferenz-2025.md
new file mode 100644
index 0000000000..4ca000e4f3
--- /dev/null
+++ b/data/planet/tarides/tarides-at-bob-konferenz-2025.md
@@ -0,0 +1,129 @@
+---
+title: Tarides at BOB Konferenz 2025
+description: We were present at BOBKonf 2025, as speakers and attendees! Here's a
+ brief report on our experiences in Berlin.
+url: https://tarides.com/blog/2025-05-08-tarides-at-bob-konferenz-2025
+date: 2025-05-08T00:00:00-00:00
+preview_image: https://tarides.com/blog/images/heart-berlin-1360w.webp
+authors:
+- Tarides
+source:
+---
+
+BOB Konferenz is a 10-year-old
+conference whose tagline is: "The software development
+conference for everyone dissatisfied with the status quo"! Indeed,
+BOB is a conference that focusses on a variety of subjects that
+strongly converge with the interests of Tarides (and the OCaml world
+in general). It aims to cover topics such as functional programming,
+"fancy types" (dependent types, gradual typing, linear types, ...),
+formal methods for correctness and robustness, abstractions for
+concurrency and parallelism, controlled side effects, next-generation
+IDEs, and much more!
+The convergence has been so strong that, over the years, some big
+names from the OCaml community have shown up — like Anil
+Madhavapeddy, Hannes
+Mehnert, Gabriel
+Scherer, and even Xavier
+Leroy! This is one of the many reasons why
+Tarides decided to sponsor the 2024
+edition and send Sabine
+Schmaltz and Leandro
+Ostera.
+For the 2025 edition, March 14, 2025, in Berlin, Xavier Van de
+Woestyne had the privilege of presenting Tarides’
+work on editor support for OCaml during his talk "Beyond the Basics
+of LSP: Advanced IDE Services for
+OCaml." Accompanied by
+Antonin Decimo, who attended the
+conference, Xavier travelled to BOBKonf 2025 to share their insights and
+experience.
+A Wide Range of Interesting Talks
+The BOB program is wonderfully eclectic, and every talk is an
+opportunity to discover something new! For example, after a keynote on
+Local-First software — which
+included many fascinating use cases with potential applications for
+Irmin. We had the chance to attend talks on
+abstraction, speculative
+reasoning about functions based on their types (for instance, a
+function of type a -> a having only one possible inhabitant), the
+application of separation logic for concurrency in
+Idris, and even collaborations
+between engineers and mathematicians on the specification of formal
+methods.
+We explored the functional programming counterpart to design
+patterns — with a strong
+emphasis on the power of robust module systems, something that
+deeply resonates with us as OCaml developers. That was followed by a
+deep reflection on object-oriented programming from a functional
+programmer’s perspective, a clear
+explanation of how recursive definitions work in
+Lean, and, to wrap it all up,
+a guide to common pitfalls to avoid when building distributed systems
+with microservices.
+All in all, it was an intense and inspiring day — packed with ideas
+that strongly resonated with us. From our perspective, the themes
+explored throughout the conference aligned closely with the
+ideological and technical choices we’ve made at Tarides, particularly
+our commitment to OCaml. But beyond that, many of the talks echoed the
+challenges and directions of the projects we actively maintain!
+About our Presentation
+Although the goal of our
+presentation (you can watch the recording on BOBKonf's website) was to
+discuss OCaml editor support (through
+Merlin,
+Ocaml-lsp-server, and its
+clients, Visual Studio
+Code and
+Emacs), we aimed to present
+an approach and a set of features that wouldn’t limit our audience to
+just OCaml users. Instead, we wanted to spark a conversation with
+other IDE users/maintainers to share ideas and implementation
+perspectives!
+We believe the presentation was well-received, generating some very
+interesting questions along with positive conversations about how
+some of the ideas we presented could be applied to proof assistants
+like Isabelle,
+Idris2, and
+Agda.
+There was a proposal to combine our efforts to improve the Language
+Server
+Protocol,
+making it even more welcoming for certain functional languages that
+leverage interactive features (where the acceptance model is primarily
+based on voting). From our perspective, these were excellent and
+motivating responses!
+Meet and Greet
+Beyond the technical side, one of the great things about conferences
+is the chance to meet people—catch up with familiar faces, make new
+connections, and have meaningful conversations around topics we’re all
+passionate about! From our perspective, even though the schedule is
+quite packed, the talk slots are spaced out just enough to let us
+catch our breath — but more importantly, to connect and chat with
+members of the community. It really helps to foster a friendly, sociable
+atmosphere!
+To Conclude
+Attending conferences is an integral part of our work as engineers—for
+several important reasons:
+
+- Keeping up with the latest in technology and research
+- Sharing our progress and presenting the work we’ve been doing
+- Initiating potential collaborations with people driven by similar
+goals and motivations.
+
+So yes, it's important — but at conferences like BOB, it’s also a real
+pleasure! The talks are truly fascinating (we’re already looking
+forward to the video recordings so we can catch up on what we missed),
+and the interactions are incredibly motivating for our work. If, like
+us, you’re interested in functional programming, fancy types, formal
+methods, and many other exciting topics, don’t hesitate to check out
+BOB’s YouTube channel –
+and maybe even consider attending next time!
+
+Connect with Tarides online on
+Bluesky,
+Mastodon,
+Threads, and
+LinkedIn or sign up for
+our mailing list to stay updated on our latest projects.
+
diff --git a/data/planet/tarides/the-first-wasmofocaml-release-is-out.md b/data/planet/tarides/the-first-wasmofocaml-release-is-out.md
new file mode 100644
index 0000000000..20d48394eb
--- /dev/null
+++ b/data/planet/tarides/the-first-wasmofocaml-release-is-out.md
@@ -0,0 +1,44 @@
+---
+title: The First Wasm_of_ocaml Release is Out!
+description: "Discover what's new with the release of Js_of_ocaml 6.0.1 \u2013 including
+ Wasm support!"
+url: https://tarides.com/blog/2025-02-19-the-first-wasm-of-ocaml-release-is-out
+date: 2025-02-19T00:00:00-00:00
+preview_image: https://tarides.com/blog/images/helloworld-1360w.webp
+authors:
+- Tarides
+source:
+---
+
+The first feature-complete release of Wasm_of_OCaml (also known as WSOO) is out! A low-level virtual machine and portable compilation target, Wasm is popular with many developers thanks to its flexibility and wide compatibility.
+We introduced you to Wasm and the benefits of bringing support for it to OCaml in our blog post on it in 2023. Since then, Wasm_of_ocaml has undergone new developments, so let’s take a look at what’s new and give you an overview of the release.
+What is Wasm_of_ocaml?
+Let’s start with a quick recap. Wasm_of_ocaml is a fork of the popular Js_of_ocaml compiler, translating OCaml bytecode to WebAssembly. It is web-oriented and relies on a JavaScript environment, and is designed to be an alternative to Js_of_ocaml. Since WebAssembly provides a sandboxed environment and enforces memory safety it is well-suited for security-critical applications, such as blockchain applications and programs running in the cloud. We plan to target these environments in the future.
+Wasm_of_ocaml builds on the WebAssembly garbage collection extension (WasmGC), which is available by default on Chrome, Safari, and Firefox. This design means we don’t need to reimplement a garbage collector, and - as an added benefit - gives us good interoperability with JavaScript. Js_of_ocaml translates OCaml bytecode to JavaScript and is a well-liked, industrial-strength compiler for running OCaml on the web. The goal of Wasm_of_ocaml’s development is to retain the strengths of Js_of_ocaml and offer feature parity and inter-compatibility between the two compilers. You can compile your programs with Wasm_of_ocaml instead of Js_of_ocaml (you may have to make a few adjustments) and experience overall better performance.
+Because of its popularity and versatility, creating an OCaml to Wasm translator has been a big priority for the team, and they continue to improve and optimise Wasm_of_ocaml over time.
+What’s New?
+Over the past year, much work has been done to get Wasm_of_ocaml to release readiness. Some of the changes include:
+
+- Putting Wasm_of_ocaml into the same development repo as Js_of_ocaml: This was a natural step due to how much code the two tools share, considering Wasm_of_ocaml is a fork of Js_of_ocaml. However, the two have diverged since the former was forked away from Js_of_ocaml. To put them in the same repo required changes to bring them back in sync. This change was necessary for the first public release of Wasm_of_ocaml, and you can check out the work in the associated PR.
+- Support for Wasm_of_ocaml in Dune: An important milestone on the road to the public release, this change allowed users to compile Wasm in Dune, making it much easier for existing OCaml projects to adopt the new tool. Wasm_of_ocaml support has been released in Dune 3.17.0, which you can upgrade to if you haven’t already.
+- Separate compilation: Support for separate compilation enables much faster compilation when building a program. There are two PRs: the first PR brings the main update, and the second PR makes it more fine-grained and avoids having to load too many modules.
+- Sourcemap support: PR #27 introduces support for source-level debugging of Wasm executables, implementing mapping between source and Wasm locations.
+- Support the JS String Builtins Extension: PR #33 change enables the use of JS string builtins when available for JS engines, which allows for more efficient operations on strings.
+- Minimise the use of the unsafe JS command eval: The JS command
eval is known for being unsafe, and PR #24 creates an alternative workflow that minimises its use. Instead of using eval, strings can be emitted as external JavaScript fragments whenever the value of the string is known at compile time.
+- Store long-lived top-level values into global variables: PR #30 introduces a change where any variable that is used a number of instructions after being defined is stored as a global variable rather than a local variable. This change improves performance and reduces the compilation time of Wasm projects.
+- Updates to make Wasm_of_ocaml compatible with OCaml 5.2 and 5.3: Two PRs, #54 and #59, brought changes that made Wasm_of_ocaml compatible with OCaml: 5.2. For 5.3, PR #136 included updates to make Wasm_of_ocaml compatible with the then latest OCaml update.
+- Bugfixes: Let’s round off with some bug fixes! PR #22 ensured that locals are always explicitly initialised before being used, PR #31 fixed the spec-compliance of some emitted tuple instructions, and PR #46 fixed a stack resizing bug in structural value comparison.
+
+These are just a subset of all the fixes and contributions, and I recommend that you explore the Wasm_of_ocaml repo for a more complete picture.
+Benchmarks and Performance
+The team has run several benchmarks with exciting results. When comparing the performance of Wasm_of_ocaml to Js_of_ocaml and the native code of OCaml’s compiler ocamlopt, the results consistently show that programs compiled with Wasm_of_ocaml are faster than ones compiled with Js_of_ocaml (but two times slower than native code). This holds true not only for microbenchmarks but on macroscopic benchmarks as well. Even more impressively, Jane Street reports that they have observed 2x-8x performance improvements using Wasm_of_ocaml compared to Js_of_ocaml.
+Another aspect of performance lies in casts and bound checks. Wasm_of_ocaml uses a generic representation of values, which means that, at run time, a number of casts might be required to ensure safety. Furthermore, due to the nature of the data representation the team has chosen, a bound check is required whenever a field of value is accessed. The team found that the Wasm_of_ocaml checks take up around 10% of the execution overtime on the V8 engine and 20% on the Bonsai benchmark. The goal is to keep improving performance by reducing the amount of needless casts.
+Regarding file size, Wasm_of_ocaml output code takes up more space than Js_of_ocaml, which is likely due to Wasm being a lower-level language than JavaScript. For example, Wasm_of_ocaml has to generate explicit code to allocate closures and access the environment, both implicit in JavaScript.
+If you’re curious to learn more about Wasm_of_ocaml’s benchmarks and performance, Jérôme Vouillon’s talk from the ML track at ICFP 2024 goes more in-depth.
+Release process, Plus a New Version of js_of_ocaml
+From now on, wasm_of_ocaml and js_of_ocaml will be released jointly. For this reason, this first public release of wasm_of_ocaml is numbered 6.0.1 since it is synchronised with the release of js_of_ocaml 6.0.1.
+A new and important feature of js_of_ocaml 6.0.0 is double translation, a way of making programs that use effect handlers faster. Effect handler support is realised by compiling some functions to Javascript code in continuation passing-style (CPS), which incurs a performance penalty. By passing --effects=double-translation, some functions are compiled in several versions, and the choice of which version of the function to run is made at run time. This improves performance at the cost of slightly larger Javascript bundles. More details are available on the effect handlers page of the js_of_ocaml manual.
+Until Next Time
+If you try Wasm_of_ocaml yourself and have any feedback or questions, please see our post on Discuss. You can also create issues in the Wasm_of_ocaml repo if you spot something that can be improved.
+Connect with Tarides online on Bluesky, X, Mastodon, Threads, and LinkedIn or sign up for our mailing list to stay updated on our latest projects.
+
diff --git a/data/planet/tarides/the-ocaml-52-release-features-and-fixes.md b/data/planet/tarides/the-ocaml-52-release-features-and-fixes.md
index 90a00c754b..9ca1de1283 100644
--- a/data/planet/tarides/the-ocaml-52-release-features-and-fixes.md
+++ b/data/planet/tarides/the-ocaml-52-release-features-and-fixes.md
@@ -51,7 +51,7 @@ source:
Preparing for Upcoming Features
Part of the contributions to each release focus on preparing the way for future features. 5.2 lays the foundation for some long-anticipated additions including:
-- Project-Wide Occurrences: Ulysse Gérard's PR #12508 provides the required steps to support
project-wide-occurences in OCaml projects, a feature that many Merlin users in particular have been waiting for. Work is ongoing to implement this in Merlin to enhance code navigation and refactoring. This change is possible thanks to OCaml's Shapes feature.
+- Project-Wide Occurrences: Ulysse Gérard's PR #12508 provides the required steps to support
project-wide-occurrences in OCaml projects, a feature that many Merlin users in particular have been waiting for. Work is ongoing to implement this in Merlin to enhance code navigation and refactoring. This change is possible thanks to OCaml's Shapes feature.
- Statmemprof: Statmemprof is a well-loved statistical memory profiler that was removed from OCaml before the multicore 5.0 release, due to unanswered questions about how it would perform. Significant efforts have gone into bringing
statmemprof back, and 5.2 prepares the way in two PRs. PR #11911 features much of the initial conversation and collaboration to bring the feature back, and Nick Barnes's #12381 PR changes part of the memory profiler’s API to prepare it for multicore. This feature is expected to be included in 5.3 this autumn.
- MSVC: MSVC is Microsoft's C/C++ compiler and users can compile OCaml 4.14 with MSVC. However, in OCaml 5.0 the runtime uses C features that MSVC doesn't support, making it incompatible with MSVC. Antonin Décimo's PR #12769 unifies MSVC and MinGW-w64 code paths to prepare for full MSVC support for OCaml 5, also expected to be re-introduced in OCaml 5.3 later this autumn.
diff --git a/data/planet/tarides/using-clang-cl-with-ocaml-5.md b/data/planet/tarides/using-clang-cl-with-ocaml-5.md
new file mode 100644
index 0000000000..3596d89be5
--- /dev/null
+++ b/data/planet/tarides/using-clang-cl-with-ocaml-5.md
@@ -0,0 +1,30 @@
+---
+title: Using `clang-cl` With OCaml 5
+description: Antonin shares his findings from building OCaml 5 with Clang-cl during
+ the MSVC port restoration project, part of the effort to improve OCaml on Windows!
+url: https://tarides.com/blog/2025-01-15-using-clang-cl-with-ocaml-5
+date: 2025-01-15T00:00:00-00:00
+preview_image: https://tarides.com/blog/images/clang-cl-1360w.webp
+authors:
+- Tarides
+source:
+---
+
+Bringing new features to OCaml is not a trivial procedure, and any new contribution is subject to rigorous testing and inspection. The introduction of Multicore OCaml added a whole new dimension of complexity to the process, and this post takes you behind-the-scenes of a project that sprang from troubleshooting the restoration of MSVC to OCaml 5.
+Motivation
+OCaml 5.3 is released and comes with a restored MSVC (Microsoft Visual C/C++ compiler) port. It had been removed with the introduction of the multicore runtime in OCaml 5.0. The Restore the MSVC port of OCaml #12954 PR lists all the prerequisite changes, and the final streak of changes, required to restore support.
+The OCaml 5 runtime requires C11 atomic support from the C compiler, which for MSVC was introduced in Visual Studio 2022 version 17.5 Preview 2. This early version had a few bugs during code generation and we were wondering if they would impact the OCaml runtime (fortunately, they did not). While the Microsoft team worked hard to fix the bugs, we learned about clang-cl, a driver program for Clang that attempts to be compatible with MSVC's cl.exe. It is ABI-compatible with MSVC, implements all MSVC compiler extensions, and is also a drop-in command-line replacement for cl.exe.
+I wanted to try building OCaml with clang-cl, mostly because it would get us a second compiler's opinion on our code, as Clang has a different set of warnings and hints than MSVC. Seeing that clang-cl was already used to build Chrome and Firefox (1, 2), I was hoping the needs of the OCaml runtime would have already been covered and bugs fixed, and that we could adopt it seamlessly.
+The OCaml 5 runtime uses the POSIX threads (pthreads) library on Unix-like systems for all its concurrency primitives. For the OCaml 5.3 branch we've chosen to use the winpthreads library, part of the MinGW-w64 project, which implements pthreads on Windows. The OCaml 5 MinGW-w64 port uses it, and we found out we could use it with MSVC too. I then submitted two patch series to winpthreads (Patches and cleanups towards MSVC support, MSVC support without GCC extensions), checking my work with MinGW-w64+GCC, MinGW-w64+clang, MSVC, and clang-cl, foreshadowing their use within the OCaml runtime. What an adventure! And thanks to the MinGW-w64 team for reviewing this work.
+Using clang-cl
+Clang on Unix-like systems masquerades as GCC, supports all GNU C extensions and defines the __GNUC__ macro. On Windows, it masquerades as MSVC and defines the _MSC_VER macro instead, but still supports the GNU C extensions that we can take advantage of!
+The build system only required a few changes. For instance, MSVC currently defaults to C99 and needs two flags to switch to C11 and enable experimental C11 atomic support, whereas clang-cl defaults to C17. We also discussed how to improve the support of MSVC in Autoconf, which led to a few patches. We could then use clang-cl to discover new problems reported by the warnings it raised and fix them. In conjunction with this work, I raised the warning level of MSVC on the OCaml runtime C code from none to -W2.
+Fortunately, most of the warnings were quite minor (see #13081 and #13243), mainly consisting of warnings for deprecated functions or implicit truncations when converting integers or floating points values of different sizes. Switching to newer compilers also allowed us to remove dead code and workarounds for older versions of the compilers.
+I found out that most of the uses of compiler attributes or builtins were guarded by the __GNUC__ macro, and as such were only enabled by GCC or Clang on Unix, even though clang-cl on Windows supports them too. Compiler attributes may enable more checks and warnings from the compiler. For instance, the format attribute tags a function to be printf-like and checks the types of the list of values passed to it against the specifiers inside a format string. Compiler builtins may enable more optimisations, such as __builtin_expect. So, instead of guarding their use, we could discover whether the compiler provides them using newer macros such as __has_attribute or __has_builtin. This improved feature parity between the clang-cl port and an OCaml build using GCC or Clang.
+In particular, we could detect the labels as values (also known as computed gotos) compiler extension to enable threaded code interpretation, which dramatically improves the speed of ocamlc, the OCaml bytecode interpreter. This optimisation isn't supported by MSVC and would require us to use inline assembly on x86 or write part of the bytecode interpreter in assembly on other architectures. If you're often using the bytecode interpreter, there's now a clear advantage of using clang-cl over MSVC. Another interesting optimisation uses software prefetching to speed up the GC when traversing the graph of values. We had worked on restoring it in OCaml 5 from OCaml 4 but forgot to port it to Windows!
+The overall work on restoring the MSVC port of OCaml and also building it with clang-cl led to a few bug reports to Microsoft and to the LLVM project, which I hope will benefit the community. The OCaml project now has an extra set of compiler eyes that scrutinise each and every change on Windows. Windows users may now take advantage of a (compliant) C11 and C23 FOSS compiler, with a wide range of optimisations and checks available.
+I'm grateful to my colleagues at Tarides for helping me with this work, the OCaml core team for reviewing it, and Jane Street for sponsoring this effort.
+Try it Out and Stay in Touch!
+With the release of opam 2.2 (now 2.3) supporting Windows, the restoration of the Cygwin port in OCaml 5.1, the MSVC port in OCaml 5.3, and the option to build OCaml with clang-cl, and the swarm of bug fixes that accompanied them, using OCaml on Windows has never been easier! Give it a whirl!
+Connect with Tarides online on Bluesky, X, Mastodon, Threads, and LinkedIn or sign up for our mailing list to stay updated on our latest projects.
+
diff --git a/data/planet/tarides/were-moving-ocsigen-from-lwt-to-eio.md b/data/planet/tarides/were-moving-ocsigen-from-lwt-to-eio.md
new file mode 100644
index 0000000000..8332b966e1
--- /dev/null
+++ b/data/planet/tarides/were-moving-ocsigen-from-lwt-to-eio.md
@@ -0,0 +1,41 @@
+---
+title: We're Moving Ocsigen from Lwt to Eio!
+description: Announcing a new project transitioning the web development framework
+ Ocsigen from Lwt to Eio and effects, creating new tools and workflows.
+url: https://tarides.com/blog/2025-03-13-we-re-moving-ocsigen-from-lwt-to-eio
+date: 2025-03-13T00:00:00-00:00
+preview_image: https://tarides.com/blog/images/3dwebapps-1360w.webp
+authors:
+- Tarides
+source:
+---
+
+Among the big changes that came with OCaml 5, concurrency via effect handlers was introduced alongside the I/O library Eio, letting users take advantage of effects to write more efficient concurrent programs. In an exciting new project, we are transitioning one of the biggest OCaml open source projects, Ocsigen, from Lwt concurrency to concurrency using effects.
+The most exciting part of this project is that we will develop tools to automate parts of the transition and document how we achieve it, which will be great resources for the wider OCaml community. This work is made possible thanks to a grant from the NLnet Foundation, which funds research and development projects furthering internet technologies and the open internet, and the NGI Zero Core fund of the European commission. This post will give you an overview of the tools, the goals of the project, and some of the methods we will use.
+Why Ocsigen and Why Eio?
+Ocsigen is a web and mobile framework composed of several projects and libraries including Eliom, Js_of_ocaml, Ocsigen Server, and Lwt. Ocsigen lets you build a variety of applications, from simple server-side web sites to complex client-server web and mobile apps. It is built using OCaml and benefits from its strong type system to reduce development time, simplify refactoring, and reduce the likelihood of bugs. It is one of the biggest open source projects in OCaml, and is used commercially to run the BeSport app.
+Choosing a large and established project like Ocsigen will give the community a well-documented proof-of-concept of what the transition between different concurrency models looks like.
+Lwt, a monadic-style concurrent programming library for OCaml, is developed as part of the Ocsigen umbrella. It has served as a way of managing I/O operations using promises for many different OCaml projects, and it is significant that Lwt’s own inventor and biggest user Ocsigen is now making the switch to effects.
+The monadic style has several advantages over more traditional concurrency models (like preemptive threading or interaction loops) including fewer data races and straightforward writing, but also comes with drawbacks in comparison to direct-style effects-based concurrency. Namely, creating an abundance of heap allocations and introducing the function colouring problem to the user’s programs. With direct-style concurrency it is possible to write code in a natural, direct, style as opposed to callback-style with considerations for which code is concurrent and which is not. For more information about concurrency using effect handlers, check out our blog post on Eio.
+Switching to effects is also a first step towards enabling the use of multicore features, which is not fully possible with Lwt.
+Goals of the Project
+The aim of the project is to create a straight-forward path for developers who want to transition projects to OCaml 5 and its new direct-style concurrency libraries. To this end, the team will develop tools for rewriting monadic syntax into direct style, rewrite interfaces, and automate the use of libraries like picos-lwt and eio-lwt. They will also develop heuristics for detecting places in the code where manual intervention is required, simplifying the developer workflow.
+Put simply, we are going to:
+
+- Automate the aspects of transforming monadic style concurrency to direct style concurrency that we can,
+- Make manual intervention as smooth as possible,
+- Document the process so that it is easy to replicate, troubleshoot, and adapt for other projects.
+
+Making effects-based concurrency easier to adopt means that more OCaml developers can potentially take advantage of its benefits. Speaking from experience, Simon Grondin summed up his experiments with Eio in our interview with him from 2024:
+
+Eio helped me reason about my code, and I discovered bugs and problems because of how much Eio had cleaned up the code. I uncovered hidden bugs in every program I converted from Lwt to Eio. Every single one also ended up being faster, not because Eio itself was faster (it was as fast as Lwt), but because of the optimisations I could now afford to make, thanks to the reduced complexity.
+
+This project will enable more people to try Eio and see if their experience matches Simon’s, with the potential to significantly improve their workflow!
+Challenges and Methods
+One of the biggest challenges facing this work is the way that in an effect-based library there is an explicit fork feature to create a new thread, whereas forking is implicit with Lwt which makes it hard to detect. This fact alone is the reason why the team won’t be able to write a fully automated conversion tool.
+Another challenge for the team is to make sure that they stay as neutral as possible in their approach to the effect library's design, in order to be able to make changes later or provide multiple alternatives.
+Finally, they will strive to maintain backward compatibility to the greatest extent, by using Lwt-effect bridges to enable intercompatibility for existing applications without forcing them to switch immediately.
+Until Next Time
+Keep your eye on our blog and OCaml Discuss for more updates on this project and the tools that emerge from it.
+Connect with Tarides online on Bluesky, Mastodon, Threads, and LinkedIn or sign up for our mailing list to stay updated on our latest projects.
+
diff --git a/data/releases/5.1.0.md b/data/releases/5.1.0.md
index 2626572dc8..0a6abaa694 100644
--- a/data/releases/5.1.0.md
+++ b/data/releases/5.1.0.md
@@ -186,7 +186,7 @@ This is the
`Seq.find_mapi`, `Seq.find_index`, `Array.find_mapi`, `Array.find_index`,
`Float.Array.find_opt`, `Float.Array.find_index`, `Float.Array.find_map`,
`Float.Array.find_mapi`.
- (Sima Kinsart, review by Daniel Bünzli and Nicolás Ojeda Bär)
+ (T. Kinsart, review by Daniel Bünzli and Nicolás Ojeda Bär)
- [#11410](https://github.com/ocaml/ocaml/issues/11410): Add Set.to_list, Map.to_list, Map.of_list,
`Map.add_to_list: key -> 'a -> 'a list t -> 'a list t`.
diff --git a/data/releases/5.2.0.md b/data/releases/5.2.0.md
index 78908b324b..2504a6bb1e 100644
--- a/data/releases/5.2.0.md
+++ b/data/releases/5.2.0.md
@@ -14,7 +14,7 @@ highlights: |
- Thread sanitiser support
- New Dynarray module
- New `-H` flag for hidden include directories
- - Project-wide occurence metadata support for developer tools
+ - Project-wide occurrence metadata support for developer tools
- Raw identifiers
- Local open in type expressions
- Around 20 new functions in the standard library
@@ -30,7 +30,7 @@ Some of the highlights in OCaml 5.2.0 are:
- ThreadSanitizer support
- New Dynarray module
- New `-H` flag for hidden include directories
-- Project-wide occurence metadata support for developer tools
+- Project-wide occurrence metadata support for developer tools
- Raw identifiers
- Local open in type expressions
@@ -147,7 +147,7 @@ The user manual for OCaml can be:
of Emacs info files
-## Changes
+## Changes
(Changes that can break existing programs are marked with a "breaking change" warning)
@@ -333,7 +333,7 @@ The user manual for OCaml can be:
- [#12814](https://github.com/ocaml/ocaml/issues/12814): More detailed failure messages from `input_value` and `Marshal.from_*`
(Xavier Leroy, review by Stephen Dolan and Anil Madhavapeddy)
-- [#12815](https://github.com/ocaml/ocaml/issues/12815): Correctly format multiline locations in exception backtraces in the compiler driver's
+- [#12815](https://github.com/ocaml/ocaml/issues/12815): Correctly format multiline locations in exception backtraces in the compiler driver's
style.
(David Allsopp, review by Gabriel Scherer)
@@ -521,7 +521,7 @@ gives more robust semantics to newlines in string literals.
- [#12825](https://github.com/ocaml/ocaml/issues/12825): Disable common subexpression elimination for atomic loads... again.
(Gabriel Scherer, review by KC Sivaramakrishnan, Xavier Leroy
and Vincent Laviron, report by Vesa Karvonen)
-
+
### Other Libraries:
- [#12213](https://github.com/ocaml/ocaml/issues/12213): `Dynlink` library, improve legibility of error messages
@@ -599,7 +599,7 @@ gives more robust semantics to newlines in string literals.
* (*Breaking Change*) [#12942](https://github.com/ocaml/ocaml/issues/12942): Fix line ordering in some module inclusion error messages
(Nick Roberts, review by Florian Angeletti, report by Carl Eastlund)
-
+
### Manual and Documentation:
- [#12338](https://github.com/ocaml/ocaml/issues/12338): Clarification of the documentation of process related function in
diff --git a/data/releases/5.2.1.md b/data/releases/5.2.1.md
index 63b8d7bfdd..c0493e2a5e 100644
--- a/data/releases/5.2.1.md
+++ b/data/releases/5.2.1.md
@@ -2,7 +2,7 @@
kind: compiler
version: 5.2.1
date: 2024-11-18
-is_latest: true
+is_latest: false
intro: |
This page describes OCaml version **5.2.1**, released on
Nov 18, 2024. Go [here](/releases) for a list of all releases.
diff --git a/data/releases/5.3.0.md b/data/releases/5.3.0.md
new file mode 100644
index 0000000000..d498640a70
--- /dev/null
+++ b/data/releases/5.3.0.md
@@ -0,0 +1,839 @@
+---
+kind: compiler
+version: 5.3.0
+date: 2025-01-08
+is_latest: true
+intro: |
+ This page describes OCaml version **5.3.0**, released on
+ 2025-01-08. Go [here](/releases) for a list of all releases.
+
+ This release is available as an [opam](/p/ocaml/5.3.0) package.
+highlights: |
+ - Syntax for deep effect handlers
+ - Restored MSVC port
+ - Re-introduced statistical memory profiling
+ - More space-efficient implementation of Dynarray
+ - utf-8 encoded Unicode source files and modest support of Unicode identifiers
+ - Improved metadata on the pairs of declarations and definitions for merlin.
+ - Around 20 new functions in the standard library
+ - Many fixes and improvements in the runtime
+ - Improved error messages for first-class modules, functors, labeled arguments,
+ type clashes.
+ - Numerous bug fixes
+---
+
+## What's New
+
+Some of the highlights in OCaml 5.3.0 are:
+
+- Syntax for deep effect handlers
+- Restored MSVC port
+- Re-introduced statistical memory profiling
+- More space-efficient implementation of Dynarray
+- utf-8 encoded Unicode source files and modest support of Unicode identifiers
+- Improved metadata on the pairs of declarations and definitions for merlin.
+
+And a lot of incremental changes:
+
+- Around 20 new functions in the standard library
+- Many fixes and improvements in the runtime
+- Improved error messages for first-class modules, functors, labeled arguments,
+type clashes.
+- Numerous bug fixes
+
+For a comprehensive list of changes and details on all new features,
+bug fixes, optimisations, etc., please consult the
+[changelog](#Changes).
+
+---
+
+## Installation Instructions
+
+The base compiler can be installed as an opam switch with the following commands:
+```bash
+opam update
+opam switch create 5.3.0
+```
+
+The source code for OCaml 5.3.0 is also directly available on:
+
+* [GitHub](https://github.com/ocaml/ocaml/archive/5.3.0.tar.gz)
+* [OCaml archives at Inria](https://caml.inria.fr/pub/distrib/ocaml-5.3/ocaml-5.3.0.tar.gz)
+
+
+### Configuration Options
+
+The configuration of the installed [opam](https://opam.ocaml.org/) switch can be tuned with the
+following options:
+
+- `ocaml-option-afl`: Set OCaml to be compiled with `afl-fuzz` instrumentation
+- `ocaml-option-bytecode-only`: Compile OCaml without the native-code compiler
+- `ocaml-option-flambda`: Set OCaml to be compiled with `flambda` activated
+- `ocaml-option-musl`: Set OCaml to be compiled with `musl-gcc`
+- `ocaml-option-no-flat-float-array`: Set OCaml to be compiled with `--disable-flat-float-array`
+- `ocaml-option-static`: Set OCaml to be compiled with `musl-gcc -static`
+- `ocaml-option-tsan` : Set OCaml to be compiled with thread sanitiser support
+- `ocaml-option-address-sanitizer`: Set OCaml to be compiled with address sanitiser
+- `ocaml-option-leak-sanitizer`: Set OCaml to be compiled with leak sanitiser
+- `ocaml-option-fp`: Set OCaml to be compiled with frame pointers
+
+For instance, one can install a switch with both `flambda` and the `--disable-flat-float-array` option with
+
+
+```
+opam switch create 5.3.0+flambda+nffa ocaml-variants.5.3.0+options ocaml-option-flambda ocaml-option-no-flat-float-array
+```
+
+
+Source Distribution
+-------------------
+
+- [Source
+ tarball](https://github.com/ocaml/ocaml/archive/5.3.0.tar.gz)
+ (`.tar.gz`) for compilation under Unix (including Linux and macOS X)
+ and Microsoft Windows (including Cygwin)
+- Also available in
+ [`.zip`](https://github.com/ocaml/ocaml/archive/5.3.0.zip)
+ format
+- [Opam](https://opam.ocaml.org/) is a source-based distribution of
+ OCaml and many companion libraries and tools. Compilation and
+ installation are automated by powerful package managers.
+- The official development repo is hosted on
+ [GitHub](https://github.com/ocaml/ocaml).
+
+The
+[INSTALL](https://ocaml.org/releases/5.3/notes/INSTALL.adoc) file
+of the distribution provides detailed compilation and installation
+instructions. See also the [Windows release
+notes](https://ocaml.org/releases/5.3/notes/README.win32.adoc) for
+instructions on how to build under Windows.
+
+Alternative Compilers
+---------------------
+
+Additionally, the following projects allow you to compile OCaml code to
+targets traditionally associated with other languages:
+
+* [Js_of_ocaml](http://ocsigen.org/js_of_ocaml/) is a stable OCaml
+ to JavaScript compiler.
+
+User Manual
+-------------
+
+The user manual for OCaml can be:
+
+- [Browsed online](https://ocaml.org/releases/5.3/manual/index.html)
+- Downloaded as a single
+ [PDF](https://ocaml.org/releases/5.3/ocaml-5.3-refman.pdf)
+ or [plain
+ text](https://ocaml.org/releases/5.3/ocaml-5.3-refman.txt)
+ document
+- Downloaded as a single
+ [TAR](https://ocaml.org/releases/5.3/ocaml-5.3-refman-html.tar.gz)
+ or
+ [ZIP](https://ocaml.org/releases/5.3/ocaml-5.3-refman-html.zip)
+ archive of HTML files
+- Downloaded as a single
+ [tarball](https://ocaml.org/releases/5.3/ocaml-5.3-refman.info.tar.gz)
+ of Emacs info files
+
+
+## Changes
+
+(Changes that can break existing programs are marked with a "*")
+
+### Restored backend:
+
+- [#12954](https://github.com/ocaml/ocaml/issues/12954): Restore the MSVC port
+ (David Allsopp, Antonin Décimo, Samuel Hym, and Miod Vallat, review by Nicolás
+ Ojeda Bär)
+
+- [#13093](https://github.com/ocaml/ocaml/issues/13093): Allow building the MSVC port with clang-cl.
+ (Antonin Décimo, review by Nicolás Ojeda Bär, Samuel Hym,
+ David Allsopp and Sébastien Hinderer)
+
+### Language features:
+
+- [#12309](https://github.com/ocaml/ocaml/issues/12309), [#13158](https://github.com/ocaml/ocaml/issues/13158): Add syntax support for deep effect handlers
+ (Leo White, Tom Kelly, Anil Madhavapeddy, KC Sivaramakrishnan, Xavier Leroy
+ and Florian Angeletti, review by the same, Hugo Heuzard, and Ulysse Gérard)
+
+- [#11736](https://github.com/ocaml/ocaml/issues/11736), [#12664](https://github.com/ocaml/ocaml/issues/12664), [#13628](https://github.com/ocaml/ocaml/issues/13628): Support utf-8 encoded source files and latin-9
+ compatible identifiers.
+ (Xavier Leroy and Florian Angeletti, review by Daniel Bünzli and
+ Jules Aguillon)
+
+
+- [#12828](https://github.com/ocaml/ocaml/issues/12828), [#13283](https://github.com/ocaml/ocaml/issues/13283): Add short syntax for dependent functor types `(X:A) -> ...`
+ (Jeremy Yallop, review by Nicolás Ojeda Bär and Gabriel Scherer)
+
+### Type system
+
+- [#11891](https://github.com/ocaml/ocaml/issues/11891), [#12507](https://github.com/ocaml/ocaml/issues/12507): Allow to name new locally abstract types in constructor type
+ annotations.
+ (Jacques Garrigue, report and review by Gabriel Scherer and Florian Angeletti)
+
+### Runtime system:
+
+- [#11911](https://github.com/ocaml/ocaml/issues/11911), [#12923](https://github.com/ocaml/ocaml/issues/12923): Multicore statistical memory profiling.
+ This restores a notable OCaml 4 feature that was missing
+ in OCaml 5.
+ (Nick Barnes, review by Stephen Dolan, Jacques-Henri Jourdan
+ and Guillaume Munch-Maccagnoni).
+
+
+- [#13419](https://github.com/ocaml/ocaml/issues/13419): Fix memory bugs in runtime events system.
+ (B. Szilvasy and Nick Barnes, review by Miod Vallat, Nick Barnes,
+ Tim McGilchrist, and Gabriel Scherer)
+
+- [#13364](https://github.com/ocaml/ocaml/issues/13364): Emit major slice counters in the runtime events.
+ (KC Sivaramakrishnan and Sadiq Jaffer, review by Gabriel Scherer)
+
+- [#13382](https://github.com/ocaml/ocaml/issues/13382): Add more documentation for Runtime_events types
+ (Sadiq Jaffer, review by Tim McGilchrist, Miod Vallat and KC Sivaramakrishnan)
+
+- [#13370](https://github.com/ocaml/ocaml/issues/13370): Fix a low-probability crash when calling Gc.counters.
+ (Demi Marie Obenour, review by Gabriel Scherer)
+
+- [#13272](https://github.com/ocaml/ocaml/issues/13272): Allow maximum number of domains to be specified as a OCAMLRUNPARAM
+ parameter.
+ (KC Sivaramakrishnan, review by Guillaume Munch-Maccagnoni, Miod Vallat,
+ Gabriel Scherer, David Allsopp, request by Zachary Yedidia).
+
+- [#12579](https://github.com/ocaml/ocaml/issues/12579): OS-based Synchronisation for Stop-the-World Sections
+ (B. Szilvasy, review by Miod Vallat, Nick Barnes, Olivier Nicole,
+ Gabriel Scherer and Damien Doligez)
+
+- [#12789](https://github.com/ocaml/ocaml/issues/12789): Restore caml_unregister_frametable from OCaml 4
+ (Frédéric Recoules, review by Gabriel Scherer)
+
+- [#13003](https://github.com/ocaml/ocaml/issues/13003): new, more consistent names for array-creation C functions
+ (Gabriel Scherer, review by Olivier Nicole)
+
+- [#13013](https://github.com/ocaml/ocaml/issues/13013): introduce a `caml_result` type to supersede the
+ use of 'encoded exception values' in the FFI.
+ (Gabriel Scherer, review by Damien Doligez,
+ Guillaume Munch-Maccagnoni and Xavier Leroy,
+ suggested by Guillaume Munch-Maccagnoni)
+
+- [#12407](https://github.com/ocaml/ocaml/issues/12407), [#13226](https://github.com/ocaml/ocaml/issues/13226): Resource-handling improvements: add
+ exception-returning variants for all exception-raising functions in
+ `caml/fail.h`, for the purpose of cleaning-up state and resources
+ before raising.
+ (Guillaume Munch-Maccagnoni, review by Damien Doligez, Xavier Leroy
+ and Gabriel Scherer)
+
+- [#13086](https://github.com/ocaml/ocaml/issues/13086): Avoid spurious major GC slices.
+ (Damien Doligez, report by Stephen Dolan, review by Gabriel Scherer
+ and Stephen Dolan)
+
+- [#11779](https://github.com/ocaml/ocaml/issues/11779), [#13117](https://github.com/ocaml/ocaml/issues/13117): Improve logic for fiber stack alignment.
+ (Miod Vallat, report by Damien Doligez, review by Gabriel Scherer)
+
+- [#12839](https://github.com/ocaml/ocaml/issues/12839): Remove ATOMIC_UINTNAT_INIT from camlatomic.h (as part of a larger
+ cleanup of camlatomic.h)
+ (David Allsopp, review by Antonin Décimo, Sébastien Hinderer, Samuel Hym,
+ Guillaume Munch-Maccagnoni and Miod Vallat)
+
+- [#13163](https://github.com/ocaml/ocaml/issues/13163): Enable frame pointers on macOS x86_64
+ (Tim McGilchrist, review by Sébastien Hinderer and Fabrice Buoro)
+
+- [#12951](https://github.com/ocaml/ocaml/issues/12951): Constify constructors and flags tables in C code (take 2).
+ Now these tables will go in the readonly segment, where they belong.
+ (Antonin Décimo, review by David Allsopp)
+
+- [#10696](https://github.com/ocaml/ocaml/issues/10696): Introduce __has_attribute and __has_c_attributes in
+ to test the support of specific attributes in C
+ code. Introduce fallthrough as a wrapper around the fallthrough
+ attribute.
+ (Antonin Décimo, review by Nicolás Ojeda Bär, Xavier Leroy, and
+ Gabriel Scherer)
+
+- [#13083](https://github.com/ocaml/ocaml/issues/13083): Use macros from limits.h to avoid signed-integer wrap-around.
+ Introduce CAML_{U,}INTNAT_{MIN,MAX} macros to expose {u,}intnat limits.
+ (Antonin Décimo, review by Nick Barnes, Xavier Leroy, Gabriel Scherer,
+ and Miod Vallat)
+
+- [#13239](https://github.com/ocaml/ocaml/issues/13239): Check whether the compiler supports the labels as values
+ extension to enable threaded code interpretation.
+ (Antonin Décimo, review by Miod Vallat)
+
+- [#13238](https://github.com/ocaml/ocaml/issues/13238): Enable software prefetching on x86 and x86_64 when building
+ with MSVC or clang-cl.
+ (Antonin Décimo, review by Miod Vallat)
+
+- [#13241](https://github.com/ocaml/ocaml/issues/13241), [#13261](https://github.com/ocaml/ocaml/issues/13261), [#13271](https://github.com/ocaml/ocaml/issues/13271): Add CFI_SIGNAL_FRAME to ARM64 and RiscV runtimes,
+ for the purpose of displaying backtraces correctly in GDB.
+ (Tim McGilchrist, review by Miod Vallat, Gabriel Scherer and
+ KC Sivaramakrishnan)
+
+- [#13139](https://github.com/ocaml/ocaml/issues/13139): Simplify CAMLalign to always use C23/C++11 alignas or C11
+ _Alignas. Ensures that stat data is always aligned to the best
+ boundary.
+ (Antonin Décimo, review by Miod Vallat and Xavier Leroy)
+
+- [#13280](https://github.com/ocaml/ocaml/issues/13280): Check for support of compiler attributes. Allows using
+ compiler attributes with clang-cl.
+ (Antonin Décimo, review by Miod Vallat)
+
+- [#13243](https://github.com/ocaml/ocaml/issues/13243): Enable C compiler warnings internally when building with
+ clang-cl or MSVC. Provide fixes too.
+ (Antonin Décimo, review by Miod Vallat and Xavier Leroy)
+
+- [#13242](https://github.com/ocaml/ocaml/issues/13242): Define and use unreachable and trap annotation, and clean-up some
+ runtime assertions.
+ (Antonin Décimo, review by Miod Vallat, Gabriel Scherer, and David Allsopp)
+
+- [#13402](https://github.com/ocaml/ocaml/issues/13402), [#13512](https://github.com/ocaml/ocaml/issues/13512), [#13549](https://github.com/ocaml/ocaml/issues/13549), [#13553](https://github.com/ocaml/ocaml/issues/13553): Revise bytecode implementation of callbacks
+ so that it no longer produces dangling registered bytecode fragments.
+ (Xavier Leroy, report by Jan Midtgaard, analysis by Stephen Dolan,
+ review by Miod Vallat)
+
+- [#13407](https://github.com/ocaml/ocaml/issues/13407): Add Runtime_events.EV_EMPTY_MINOR
+ (Thomas Leonard)
+
+- [#13522](https://github.com/ocaml/ocaml/issues/13522): Confirm runtime events ring is still active after callback.
+ (KC Sivaramakrishnan, review by Sadiq Jaffer and Miod Vallat)
+
+- [#13529](https://github.com/ocaml/ocaml/issues/13529): Do not write to event ring after going out of stw participant set.
+ (KC Sivaramakrishnan, review by Sadiq Jaffer)
+
+### Code generation and optimizations:
+
+- [#13014](https://github.com/ocaml/ocaml/issues/13014): Enable compile-time option -function-sections on all previously
+ unsupported native backends (POWER, riscv64 and s390x)
+ (Miod Vallat, review by Nicolás Ojeda Bär)
+
+- [#7241](https://github.com/ocaml/ocaml/issues/7241), [#12555](https://github.com/ocaml/ocaml/issues/12555), [#13076](https://github.com/ocaml/ocaml/issues/13076), [#13138](https://github.com/ocaml/ocaml/issues/13138), [#13338](https://github.com/ocaml/ocaml/issues/13338), [#13152](https://github.com/ocaml/ocaml/issues/13152), [#13153](https://github.com/ocaml/ocaml/issues/13153), [#13154](https://github.com/ocaml/ocaml/issues/13154):
+ fix a soundness bug in the pattern-matching compiler
+ when side-effects mutate the scrutinee during matching.
+ (Gabriel Scherer, review by Nick Roberts)
+
+- [#13341](https://github.com/ocaml/ocaml/issues/13341): a warning when the pattern-matching compiler pessimizes code
+ because side-effects may mutate the scrutinee during
+ matching. (This warning is disabled by default, as this rarely
+ happens and its performance impact is typically not noticeable.)
+ (Gabriel Scherer, review by Nick Roberts, Florian Angeletti
+ and David Allsopp)
+
+- [#13179](https://github.com/ocaml/ocaml/issues/13179): Fix evaluation of toplevel lets in classes containing
+ local opens
+ (Vincent Laviron, review by Hugo Heuzard, Nathanaëlle Courant
+ and Gabriel Scherer)
+
+- [#13543](https://github.com/ocaml/ocaml/issues/13543): Remove some String-Bytes conversion from the stdlib to behave better
+ with js_of_ocaml
+ (Hugo Heuzard, review by Gabriel Scherer)
+
+### Standard library:
+
+- [#12885](https://github.com/ocaml/ocaml/issues/12885): move Dynarray to an unboxed representation
+ (Gabriel Scherer, suggestions by Vincent Laviron,
+ review by Olivier Nicole and Simon Cruanes, Yann Leray, Alain Frisch)
+
+
+- [#12884](https://github.com/ocaml/ocaml/issues/12884): Add `Queue.drop`
+ (Léo Andrès, review by Nicolás Ojeda Bär and Gabriel Scherer)
+
+- [#13168](https://github.com/ocaml/ocaml/issues/13168): In Array.shuffle, clarify the code that validates the
+ result of the user-supplied function `rand`, and improve the
+ error message that is produced when this result is invalid.
+ (François Pottier, review by Florian Angeletti, Daniel Bünzli
+ and Gabriel Scherer)
+
+- [#12133](https://github.com/ocaml/ocaml/issues/12133): Expose support for printing substrings in Format
+ (Florian Angeletti, review by Daniel Bünzli, Gabriel Scherer
+ and Nicolás Ojeda Bär)
+
+- [#12869](https://github.com/ocaml/ocaml/issues/12869): Add List.take, List.drop, List.take_while and List.drop_while
+ (Kate Deplaix and Oscar Butler-Aldridge, review by Nicolás Ojeda Bär,
+ Craig Ferguson and Gabriel Scherer)
+
+- [#13047](https://github.com/ocaml/ocaml/issues/13047): Add Sys.poll_actions to (only) run pending runtime actions.
+ (Nick Barnes, review by Gabriel Scherer, Guillaume Munch-Maccagnoni, and
+ Vincent Laviron)
+
+- [#13144](https://github.com/ocaml/ocaml/issues/13144): Dynarray.{equal, compare}
+ (Gabriel Scherer,
+ review by Jeremy Yallop, Daniel Bünzli and Olivier Nicole,
+ request by Olivier Nicole)
+
+- [#13171](https://github.com/ocaml/ocaml/issues/13171): expose `Domain.self_index : unit -> int` (a somewhat-dense
+ indexing of currently-running domains) for advanced use-cases of
+ domain-indexed concurrent data structures.
+ (Gabriel Scherer,
+ review by KC Sivaramakrishnan, Miod Vallat and Nicolás Ojeda Bär,
+ report by Vesa Karvonen)
+
+- [#13197](https://github.com/ocaml/ocaml/issues/13197): Dynarray.blit, which allows to extend the destination
+ dynarray (0 <= dst_pos <= dst_length).
+ (Gabriel Scherer, report by Hazem Elmasry,
+ review by Olivier Nicole, Hazem Elmasry and Nicolás Ojeda Bär)
+
+* (*breaking change*) [#13240](https://github.com/ocaml/ocaml/issues/13240): Add Uchar.seeded_hash, Change Uchar.hash implementation.
+ Previously, Uchar.hash was aliased to Uchar.to_int. If you need that behavior,
+ change your module instantiation from eg `module HT = Hashtbl.Make(Uchar)` to
+ ```
+ module HT = Hashtbl.Make(struct
+ ...
+ let hash = Uchar.to_int
+ end)
+ ```
+ If the current implementation is desired, and you have a hashtable module `HT`
+ (produced with the `Make` functor) in persistent storage, use `HT.rebuild` to
+ ensure it doesn't break when reading from or writing to buckets.
+ (Hazem ElMasry, review by Gabriel Scherer and Nicolás Ojeda Bär)
+
+- [#13318](https://github.com/ocaml/ocaml/issues/13318): Fix regression in GC alarms, and fix them for flambda.
+ (Guillaume Munch-Maccagnoni, report by Benjamin Monate, review by
+ Vincent Laviron and Gabriel Scherer)
+
+- [#13296](https://github.com/ocaml/ocaml/issues/13296): Add mem, memq, find_opt, find_index, find_map and find_mapi
+ to Dynarray.
+ (Jake H, review by Gabriel Scherer and Florian Angeletti)
+
+### Other libraries:
+
+- [#11996](https://github.com/ocaml/ocaml/issues/11996): release the dependency of dynlink on compilerlibs.
+ (Sébastien Hinderer and Stephen Dolan, review by Damien Doligez and
+ Hugo Heuzard)
+
+- [#13326](https://github.com/ocaml/ocaml/issues/13326): Implement Unix.O_APPEND on windows.
+ (Romain Beauxis, review by Miod Vallat, Gabriel Scherer and Antonin Décimo)
+
+### Tools:
+
+- [#11716](https://github.com/ocaml/ocaml/issues/11716): ocamllex: mismatched parentheses and curly brackets are now caught
+ by ocamllex, instead of causing invalid OCaml code to be generated.
+ (Demi Marie Obenour, review by Damien Doligez and Xavier Leroy)
+
+- [#12904](https://github.com/ocaml/ocaml/issues/12904): Run the testsuite with ThreadSanitizer on a PR when label
+ `run-thread-sanitizer` is added
+ (Olivier Nicole, suggested by Sébastien Hinderer and David Allsopp, review by
+ Gabriel Scherer)
+
+* (*breaking change*) [#13114](https://github.com/ocaml/ocaml/issues/13114): Support ocamldebug remote debugging over IPv6 on all
+ platforms, and over Unix domain sockets on Windows.
+ (Antonin Décimo, review by Gabriel Scherer and Miod Vallat)
+
+- [#13136](https://github.com/ocaml/ocaml/issues/13136): Rewrite GDB extensions and macros in debugger-agnostic Python, and add
+ LLDB support for them.
+ (Nick Barnes, review by Tim McGilchrist and Gabriel Scherer)
+
+### Toplevel:
+
+- [#12891](https://github.com/ocaml/ocaml/issues/12891): Improved styling for initial prompt
+ (Florian Angeletti, review by Gabriel Scherer)
+
+- [#13053](https://github.com/ocaml/ocaml/issues/13053): Improved display of builtin types such as `_ list` when aliased.
+ (Samuel Vivien, review by Florian Angeletti)
+
+### Manual and documentation:
+
+- [#13370](https://github.com/ocaml/ocaml/issues/13370): Document that that temporary variables holding GCd pointers must
+ not be live across a GC.
+ (Demi Marie Obenour)
+
+- [#12298](https://github.com/ocaml/ocaml/issues/12298): Manual: emphasize that Bigarray.int refers to an OCaml integer,
+ which does not match the C int type.
+ (Edwin Török, review by Florian Angeletti)
+
+- [#12868](https://github.com/ocaml/ocaml/issues/12868): Manual: simplify style colours of the post-processed manual and API
+ HTML pages, and fix the search button icon
+ (Yawar Amin, review by Simon Grondin, Gabriel Scherer, and Florian Angeletti)
+
+- [#12949](https://github.com/ocaml/ocaml/issues/12949): document OCaml release cycles and version strings in
+ `release-info/introduction.md`.
+ (Florian Angeletti, review by Fabrice Buoro, Kate Deplaix, Damien Doligez, and
+ Gabriel Scherer)
+
+- [#12976](https://github.com/ocaml/ocaml/issues/12976): Manual: use webman//*.html and
+ webman//api/ for OCaml.org HTML manual generation
+ (Shakthi Kannan, review by Hannes Mehnert, and Florian Angeletti)
+
+- [#13045](https://github.com/ocaml/ocaml/issues/13045): Emphasize caution about behaviour of custom block finalizers.
+ (Nick Barnes)
+
+- [#13216](https://github.com/ocaml/ocaml/issues/13216): document the new `caml_result` type in the FFI chapter of the manual.
+ (Gabriel Scherer, review by Miod Vallat, Daniel Bünzli, Nick Barnes,
+ Guillaume Munch-Maccagnoni and Antonin Décimo)
+
+- [#13287](https://github.com/ocaml/ocaml/issues/13287): stdlib/sys.mli: Update documentation on Sys.opaque_identity
+ following [#9412](https://github.com/ocaml/ocaml/issues/9412).
+ (Matt Walker, review by Guillaume Munch-Maccagnoni and Vincent Laviron)
+
+- [#13295](https://github.com/ocaml/ocaml/issues/13295): Use syntax for deep effect handlers in the effect handlers manual
+ page.
+ (KC Sivaramakrishnan, review by Anil Madhavapeddy, Florian Angeletti and Miod
+ Vallat)
+
+- [#13424](https://github.com/ocaml/ocaml/issues/13424): Fix `Gc.quick_stat` documentation to clarify that returned fields
+ `live_words`, `live_blocks`, `free_words`, and `fragments` are not zero.
+ (Jan Midtgaard, review by Damien Doligez and KC Sivaramakrishnan)
+
+- [#13440](https://github.com/ocaml/ocaml/issues/13440): Update documentation of `Gc.{control,get,set}` to reflect fields
+ not currently supported on OCaml 5.
+ (Jan Midtgaard, review by Gabriel Scherer)
+
+- [#13469](https://github.com/ocaml/ocaml/issues/13469), [#13474](https://github.com/ocaml/ocaml/issues/13474), [#13535](https://github.com/ocaml/ocaml/issues/13535): Document that [Hashtbl.create n] creates a hash table
+ with a default minimal size, even if [n] is very small or negative.
+ (Antonin Décimo, Nick Bares, report by Nikolaus Huber and Jan Midtgaard,
+ review by Florian Angeletti, Anil Madhavapeddy, Gabriel Scherer,
+ and Miod Vallat)
+
+- [#13666](https://github.com/ocaml/ocaml/issues/13666): Rewrite parts of the example code around nested lists in Chapter 6
+ (Polymorphism and its limitations -> Polymorphic recursion) giving the
+ "depth" function [in the non-polymorphically-recursive part of the example]
+ a much more sensible behavior; also fix a typo and some formatting.
+ (Frank Steffahn, review by Florian Angeletti)
+
+- [#13668](https://github.com/ocaml/ocaml/issues/13668): Document the basic support for unicode identifiers and the switch to
+ UTF-8 encoded Unicode text for OCaml source file
+ (Florian Angeletti, review by Nicolás Ojeda Bär and Daniel Bünzli)
+
+### Compiler user-interface and warnings:
+
+* (*breaking change*) [#12084](https://github.com/ocaml/ocaml/issues/12084), [#13669](https://github.com/ocaml/ocaml/issues/13669), [#13673](https://github.com/ocaml/ocaml/issues/13673): Check link order when creating archive and when using
+ ocamlopt.
+ (Hugo Heuzard, review by Stefan Muenzel and Sébastien Hinderer)
+
+- [#12980](https://github.com/ocaml/ocaml/issues/12980): Explain type mismatch involving first-class modules by including
+ the module level error message
+ (Florian Angeletti, review by Vincent Laviron)
+
+- [#12985](https://github.com/ocaml/ocaml/issues/12985), [#12988](https://github.com/ocaml/ocaml/issues/12988): Better error messages for partially applied functors.
+ (Florian Angeletti, report by Arthur Wendling, review by Gabriel Scherer)
+
+- [#13034](https://github.com/ocaml/ocaml/issues/13034), [#13260](https://github.com/ocaml/ocaml/issues/13260): Better error messages for mismatched function labels
+ (Florian Angeletti, report by Daniel Bünzli, review by Gabriel Scherer and
+ Samuel Vivien)
+
+- [#13051](https://github.com/ocaml/ocaml/issues/13051): Add a "Syntax error" to error messages for invalid package signatures.
+ (Samuel Vivien, review by Gabriel Scherer)
+
+- [#13099](https://github.com/ocaml/ocaml/issues/13099): Fix erroneous loading of cmis for some module type errors.
+ (Nick Roberts, review by Florian Angeletti)
+
+- [#13151](https://github.com/ocaml/ocaml/issues/13151), name conflicts explanation as a footnote
+ (Florian Angeletti, review by Gabriel Scherer)
+
+- [#13228](https://github.com/ocaml/ocaml/issues/13228): Re-export Cmt2annot.{iterator,binary_part} which had become hidden
+ since [#11288](https://github.com/ocaml/ocaml/issues/11288) and broke ocamlbrowser.
+ (David Allsopp, report by Jacques Garrigue, review by Sébastien Hinderer)
+
+- [#13251](https://github.com/ocaml/ocaml/issues/13251): Register printer for errors in Emitaux
+ (Vincent Laviron, review by Miod Vallat and Florian Angeletti)
+
+- [#13255](https://github.com/ocaml/ocaml/issues/13255): Re-enable warning 34 for unused locally abstract types
+ (Nick Roberts, review by Chris Casinghino and Florian Angeletti)
+
+- [#12182](https://github.com/ocaml/ocaml/issues/12182): Improve the type clash error message.
+ For example, this message:
+ This expression has type ...
+ is changed into:
+ The constant "42" has type ...
+ (Jules Aguillon, review by Gabriel Scherer and Florian Angeletti)
+
+- [#13471](https://github.com/ocaml/ocaml/issues/13471): add `-keywords ` flag to define the list of keywords
+ recognized by the lexer, for instance `-keywords 5.2` disable the `effect`
+ keyword.
+ (Florian Angeletti, review by Gabriel Scherer)
+
+### Internal/compiler-libs changes:
+
+- [#13286](https://github.com/ocaml/ocaml/issues/13286): Distinguish unique identifiers `Shape.Uid.t` according to their
+ provenance: either an implementation or an interface.
+ (Ulysse Gérard, review by Florian Angeletti and Leo White)
+
+- [#13308](https://github.com/ocaml/ocaml/issues/13308): keep track of relations between declaration in the cmt files. This is
+ useful information for external tools for navigation and analysis purposis.
+ (Ulysse Gérard, Florian Angeletti, review by Florian Angeletti and Gabriel
+ Scherer)
+
+
+- [#11129](https://github.com/ocaml/ocaml/issues/11129), [#11148](https://github.com/ocaml/ocaml/issues/11148): enforce that ppxs do not produce `parsetree`s with
+ an empty list of universally quantified type variables
+ (`. int -> int` instead of `'a . int -> int'`)
+ (Florian Angeletti, report by Simmo Saan, review by Gabriel Scherer)
+
+- [#12534](https://github.com/ocaml/ocaml/issues/12534): document and refactor Matching.mk_failaction_pos
+ (Gabriel Scherer, review by Vincent Laviron and Nick Roberts)
+
+- [#13076](https://github.com/ocaml/ocaml/issues/13076): change the handling of Match_failure exits in the pattern-matching
+ compiler, to prepare for a complete fix for [#7241](https://github.com/ocaml/ocaml/issues/7241)
+ (Gabriel Scherer, review by Thomas Refis and Nick Roberts)
+
+- [#12896](https://github.com/ocaml/ocaml/issues/12896): Simplify the compilation of custom bytecode runtimes by explicitly
+ compiling the primitives file before calling the linker. Tidy-up both the
+ generating code and the output itself for C code being generated by the
+ bytecode linker in `-custom` and `-output-*` modes.
+ (David Allsopp, Antonin Décimo and Samuel Hym, review by Vincent Laviron)
+
+- [#12932](https://github.com/ocaml/ocaml/issues/12932): Remove useless code in Typecore.type_label_exp (was a fix for [#4862](https://github.com/ocaml/ocaml/issues/4862))
+ (Jacques Garrigue, review by Gabriel Scherer)
+
+- [#12943](https://github.com/ocaml/ocaml/issues/12943): Make transient_expr.scope a bitfield, and use it to store marks.
+ Marks are automatically allocated, and removed when leaving their scope.
+ Falls back to using TransientTypeSet when marks are exhausted.
+ (Jacques Garrigue and Takafumi Saikawa, review by Basile Clément)
+
+- [#12946](https://github.com/ocaml/ocaml/issues/12946): Make generalization automatic when leaving scope.
+ As a result, the `Ctype.generalize*` and `Ctype.correct_levels` functions
+ were removed. The latter is now called `Ctype.duplicate_type`.
+ (Jacques Garrigue and Takafumi Saikawa, review by Richard Eisenberg)
+
+- [#12968](https://github.com/ocaml/ocaml/issues/12968): Attach location to constants in the parsetree
+ (Jules Aguillon, review by Gabriel Scherer)
+
+- [#12959](https://github.com/ocaml/ocaml/issues/12959), [#13055](https://github.com/ocaml/ocaml/issues/13055): Avoid an internal error on recursive module type inconsistency
+ (Florian Angeletti, review by Jacques Garrigue and Gabriel Scherer)
+
+- [#13049](https://github.com/ocaml/ocaml/issues/13049): graphical debugging printer for types
+ (Florian Angeletti, review by Gabriel Scherer)
+
+- [#13074](https://github.com/ocaml/ocaml/issues/13074), [#13082](https://github.com/ocaml/ocaml/issues/13082), [#13084](https://github.com/ocaml/ocaml/issues/13084): refactoring in the pattern-matching compiler
+ (Gabriel Scherer, review by Thomas Refis, Vincent Laviron and Nick Roberts)
+
+- [#13067](https://github.com/ocaml/ocaml/issues/13067): rework volatile memory access rules under TSan to consider properly
+ aligned smaller-than-register read operations as atomic, which gets rid of
+ false positives on s390x
+ (Miod Vallat, review by Fabien Buoro)
+
+- [#13162](https://github.com/ocaml/ocaml/issues/13162): Use quoted strings to clarify code being generated.
+ (Antonin Décimo, review by Miod Vallat and Gabriel Scherer)
+
+- [#13015](https://github.com/ocaml/ocaml/issues/13015): Emit floating-point literals in .rodata section on ELF arm64
+ platforms (Linux, *BSD).
+ (Miod Vallat, review by Nicolás Ojeda Bär)
+
+- [#13169](https://github.com/ocaml/ocaml/issues/13169), [#13311](https://github.com/ocaml/ocaml/issues/13311): Introduce a document data type for compiler messages
+ rather than relying on `Format.formatter -> unit` closures.
+ (Florian Angeletti, review by Gabriel Scherer)
+
+- [#13193](https://github.com/ocaml/ocaml/issues/13193): Remove the unused env_init field from class blocks
+ (Vincent Laviron, review by Jacques Garrigue)
+
+- [#13257](https://github.com/ocaml/ocaml/issues/13257): integrate MetaOCaml in the Menhir grammar to ease MetaOCaml
+ maintenance. This is a purely internal change: there is no support
+ in the lexer, so no change to the surface OCaml grammar.
+ (Oleg Kiselyov, Gabriel Scherer and Florian Angeletti,
+ review by Jeremy Yallop)
+
+- [#13289](https://github.com/ocaml/ocaml/issues/13289): Use C99 for loop to reduce the scope of the for loop iterator.
+ (Antonin Décimo, review by Miod Vallat and Gabriel Scherer)
+
+- [#13336](https://github.com/ocaml/ocaml/issues/13336): compiler-libs, split the `Printtyp` in three to only keep
+ "user-friendly" functions in the `Printtyp` module.
+ (Florian Angeletti, review by Gabriel Scherer)
+
+- [#13361](https://github.com/ocaml/ocaml/issues/13361): split runtime/array.c functions to consistently expose
+ uniform_array and floatarray versions, use floatarray versions
+ in Float.Array.
+ (Gabriel Scherer, review by Nicolás Ojeda Bär)
+
+- [#13507](https://github.com/ocaml/ocaml/issues/13507): A small refactoring to [free_vars] to make it a bit faster
+ by not allocating a list when the list is not necessary.
+ (Richard Eisenberg, review by Jacques Garrigue)
+
+### Build system:
+
+- [#12909](https://github.com/ocaml/ocaml/issues/12909): Reorganise how MKEXE_VIA_CC is built to make it correct for MSVC by
+ grouping all the linker flags at the end of the C compiler commandline
+ (David Allsopp and Samuel Hym, review by Nicolás Ojeda Bär)
+
+- [#12992](https://github.com/ocaml/ocaml/issues/12992), [#13009](https://github.com/ocaml/ocaml/issues/13009): Check that flexlink can be executed only when building in a
+ native windows environment.
+ (Romain Beauxis, review by David Allsopp and Sébastien Hinderer)
+
+- [#12996](https://github.com/ocaml/ocaml/issues/12996): Only link with -lgcc_eh when available.
+ (Romain Beauxis, review by David Allsopp and Miod Vallat)
+
+* (*breaking change*) [#13200](https://github.com/ocaml/ocaml/issues/13200): Do not use CFLAGS for linking.
+ (Sébastien Hinderer, review by Gabriel Scherer, Antonin Décimo,
+ Miod Vallat and Samuel Hym)
+
+- [#13201](https://github.com/ocaml/ocaml/issues/13201), [#13244](https://github.com/ocaml/ocaml/issues/13244): Fix and speedup builds with TSan.
+ (Sébastien Hinderer, review by Miod Vallat, Gabriel Scherer and
+ Olivier Nicole)
+
+* (*breaking change*) [#12578](https://github.com/ocaml/ocaml/issues/12578), [#12589](https://github.com/ocaml/ocaml/issues/12589), [#13322](https://github.com/ocaml/ocaml/issues/13322), [#13519](https://github.com/ocaml/ocaml/issues/13519): Use configured CFLAGS and CPPFLAGS *only*
+ during the build of the compiler itself. Do not use them when
+ compiling third-party C sources through the compiler. Flags for
+ compiling third-party C sources can still be specified at configure
+ time in the COMPILER_{BYTECODE,NATIVE}_{CFLAGS,CPPFLAGS}
+ configuration variables.
+ (Sébastien Hinderer, report by William Hu, review by David Allsopp)
+
+- [#13285](https://github.com/ocaml/ocaml/issues/13285): continue the merge of the sub-makefiles into the root
+ Makefile started with [#11243](https://github.com/ocaml/ocaml/issues/11243), [#11248](https://github.com/ocaml/ocaml/issues/11248), [#11268](https://github.com/ocaml/ocaml/issues/11268), [#11420](https://github.com/ocaml/ocaml/issues/11420), [#11675](https://github.com/ocaml/ocaml/issues/11675),
+ [#12198](https://github.com/ocaml/ocaml/issues/12198), [#12321](https://github.com/ocaml/ocaml/issues/12321), [#12586](https://github.com/ocaml/ocaml/issues/12586), [#12616](https://github.com/ocaml/ocaml/issues/12616), [#12706](https://github.com/ocaml/ocaml/issues/12706) and [#13048](https://github.com/ocaml/ocaml/issues/13048).
+ (Sébastien Hinderer, review by David Allsopp and Florian Angeletti)
+
+### Bug fixes:
+
+- [#12854](https://github.com/ocaml/ocaml/issues/12854): Add a test in the regression suite that flags the bug [#12825](https://github.com/ocaml/ocaml/issues/12825).
+ (Luc Maranget)
+
+- [#12888](https://github.com/ocaml/ocaml/issues/12888): fix printing of uncaught exceptions in `.cmo` files passed on the
+ command-line of the toplevel.
+ (Nicolás Ojeda Bär, review by Florian Angeletti, report by Daniel Bünzli)
+
+- [#12910](https://github.com/ocaml/ocaml/issues/12910), [#12920](https://github.com/ocaml/ocaml/issues/12920): Fix an unsound interaction between first-class modules
+ and polymorphic records by saving and restoring univar_pairs.
+ (Stephen Dolan, review by Gabriel Scherer, report by Jeremy Yallop)
+
+- [#12994](https://github.com/ocaml/ocaml/issues/12994): Remove un-used and unsafe caml_drop_continuation
+ (Tim McGilchrist, reviewed by Gabriel Scherer and Miod Vallat)
+
+- [#12963](https://github.com/ocaml/ocaml/issues/12963): Restore caml_runtime_parameters implementation. This primitive allows
+ programs to query the runtime parameters supplied to an OCaml program.
+ Implementation missing since OCaml 5.0.
+ (Tim McGilchrist, reviewed by David Allsopp and Miod Vallat)
+
+- [#13012](https://github.com/ocaml/ocaml/issues/13012): parsing: Fix dropped attributes after a '-' or '+'
+ The syntax '-(1 [@foo])' was incorrectly parsed as '-1'.
+ (Jules Aguillon, reviewed by Gabriel Scherer, report by Gabriel Scherer)
+
+* (*breaking change*) [#13070](https://github.com/ocaml/ocaml/issues/13070): On Windows, when configured with bootstrapped flexdll, don't add
+ +flexdll to the search path when -nostdlib is specified (which then means
+ -L no longer gets passed to the system linker).
+ (David Allsopp, review by Florian Angeletti)
+
+- [#13089](https://github.com/ocaml/ocaml/issues/13089): Fix bug in `runtime_events` library which could result in garbled
+ output under Windows.
+ (B. Szilvasy, review by Nicolás Ojeda Bär and Miod Vallat)
+
+- [#13088](https://github.com/ocaml/ocaml/issues/13088): A few type-checker behaviors look at a type to see if there are
+ any labeled arguments in it. This sometimes required expansion, which
+ could, in obscure scenarios, result in superfluous type errors.
+ (Richard Eisenberg, review by Gabriel Scherer and Jacques Garrigue)
+
+- [#13103](https://github.com/ocaml/ocaml/issues/13103): FreeBSD/amd64: properly annotate .o files with non-executable stack
+ notes (Konstantin Belousov, review by Nicolás Ojeda Bär)
+
+- [#13150](https://github.com/ocaml/ocaml/issues/13150): improve a transitive-closure computation algorithm in the flambda
+ middle-end to avoid a compilation time blowup on Menhir-generated code
+ (Florian Weimer, review by Gabriel Scherer and Pierre Chambart,
+ report by Richard Jones)
+
+- [#13166](https://github.com/ocaml/ocaml/issues/13166): Fix a MinGW/MSVC Sys.rename regression on renaming a parent directory
+ to an empty child directory.
+ (Jan Midtgaard, review by Antonin Décimo, Sébastien Hinderer, and
+ David Allsopp)
+
+- [#13185](https://github.com/ocaml/ocaml/issues/13185), [#13192](https://github.com/ocaml/ocaml/issues/13192): Reject type-level module aliases on functor parameter
+ inside signatures.
+ (Jacques Garrigue, report by Richard Eisenberg, review by Florian Angeletti)
+
+- [#13170](https://github.com/ocaml/ocaml/issues/13170): Fix a bug that would result in some floating alerts `[@@@alert ...]`
+ incorrectly triggering Warning 53.
+ (Nicolás Ojeda Bär, review by Chris Casinghino and Florian Angeletti)
+
+- [#13203](https://github.com/ocaml/ocaml/issues/13203): Do not issue warning 53 if the compiler is stopping before attributes
+ have been accurately marked.
+ (Chris Casinghino, review by Florian Angeletti)
+
+- [#13207](https://github.com/ocaml/ocaml/issues/13207): Be sure to reload the register caching the exception handler in
+ caml_c_call and caml_c_call_stack_args, as its value may have been changed
+ if the OCaml stack is expanded during a callback.
+ (Miod Vallat, report by Vesa Karvonen, review by Gabriel Scherer and
+ Xavier Leroy)
+
+- [#13209](https://github.com/ocaml/ocaml/issues/13209): Fix configure test that checks whether `ar` supports `@FILE`
+ arguments.
+ (Nicolás Ojeda Bär, report by Boris D.)
+
+- [#13221](https://github.com/ocaml/ocaml/issues/13221): Compute more accurate instruction sizes for branch relocation on
+ POWER.
+ (Miod Vallat, review by Gabriel Scherer)
+
+- [#13252](https://github.com/ocaml/ocaml/issues/13252): Rework register assignment in the interpreter code on m68k on Linux,
+ due to the %a5 register being used by Glibc.
+ (Miod Vallat, report by Stéphane Glondu, review by Gabriel Scherer and
+ Xavier Leroy)
+
+- [#13247](https://github.com/ocaml/ocaml/issues/13247): Disable lib_unix/kill test for MacOS AMD64 with TSan, linking
+ to llvm bug report causing infinite signal loops.
+ (Tim McGilchrist, review by Olivier Nicole, Miod Vallat, Sébastien Hinderer
+ and Gabriel Scherer)
+
+- [#13234](https://github.com/ocaml/ocaml/issues/13234), [#13267](https://github.com/ocaml/ocaml/issues/13267): Open runtime events file in read-write mode on armel
+ (armv5) systems due to atomic operations limitations on that
+ platform.
+ (Stéphane Glondu, review by Miod Vallat and Vincent Laviron)
+
+- [#13273](https://github.com/ocaml/ocaml/issues/13273): Fix a call to test in configure.ac that was causing errors when
+ LDFLAGS contains several words.
+ (Stéphane Glondu, review by Miod Vallat)
+
+- [#13290](https://github.com/ocaml/ocaml/issues/13290): Fix uninitialized and out of bounds reads in runtime_events_consumer.c
+ (Edwin Török, review by Miod Vallat and Antonin Décimo)
+
+- [#13306](https://github.com/ocaml/ocaml/issues/13306): An algorithm in the type-checker that checks two types for equality
+ could sometimes, in theory, return the wrong answer. This patch fixes the
+ oversight. No known program triggers the bug.
+ (Richard Eisenberg, review by Florian Angeletti)
+
+- [#13400](https://github.com/ocaml/ocaml/issues/13400): Initialize th->signal_stack to avoid free of uninitialized data
+ if the user calls caml_c_thread_unregister on the main thread.
+ (Richard W.M. Jones, review by Guillaume Munch-Maccagnoni and
+ Gabriel Scherer)
+
+- [#13140](https://github.com/ocaml/ocaml/issues/13140): POWER back-end: fix issue with call to `caml_call_realloc_stack`
+ from a DLL
+ (Xavier Leroy, review by Miod Vallat)
+
+- [#13263](https://github.com/ocaml/ocaml/issues/13263), [#13560](https://github.com/ocaml/ocaml/issues/13560): fix printing true and false in toplevel and error
+ messages (no more unexpected \#true)
+ (Florian Angeletti, report by Samuel Vivien, review by Gabriel Scherer)
+
+- [#13388](https://github.com/ocaml/ocaml/issues/13388), [#13540](https://github.com/ocaml/ocaml/issues/13540): raises an error message (and not an internal compiler error)
+ when two local substitutions are incompatible (for instance `module type
+ S:=sig end type t:=(module S)`)
+ (Florian Angeletti, report by Nailen Matschke, review by Gabriel Scherer, and
+ Leo White)
+
+- [#13408](https://github.com/ocaml/ocaml/issues/13408): Fix misplaced debug runtime assertion triggerable by a race
+ between domain exit and backup thread
+ (Miod Vallat and Gabriel Scherer, report by Jan Midtgaard)
+
+- [#13417](https://github.com/ocaml/ocaml/issues/13417): `Filename.quote_command`: fix handling of forward slashes in program
+ path under Win32.
+ (Nicolás Ojeda Bär, review by David Allsopp and Damien Doligez)
+
+- [#13501](https://github.com/ocaml/ocaml/issues/13501): Regression on mutually recursive types caused by [#12180](https://github.com/ocaml/ocaml/issues/12180).
+ Resuscitate Typedecl.update_type.
+ (Jacques Garrigue and Takafumi Saikawa, review by Florian Angeletti, Richard
+ Eisenberg and Gabriel Scherer)
+
+- [#13502](https://github.com/ocaml/ocaml/issues/13502): Fix misindexing related to `Gc.finalise_last` that could prevent
+ finalisers from being run.
+ (Nick Roberts, review by Mark Shinwell)
+
+- [#13495](https://github.com/ocaml/ocaml/issues/13495), [#13514](https://github.com/ocaml/ocaml/issues/13514): Fix typechecker crash while typing objects
+ (Jacques Garrigue, report by Nicolás Ojeda Bär, review by
+ Nicolas Ojeda Bär, Gabriel Scherer, Stephen Dolan, Florian Angeletti)
+
+- [#13391](https://github.com/ocaml/ocaml/issues/13391), [#13551](https://github.com/ocaml/ocaml/issues/13551): fix a printing bug with `-dsource` when using
+ raw literal inside a locally abstract type constraint
+ (i.e. `let f: type \#for. ... `)
+ (Florian Angeletti, report by Nick Roberts, review by Richard Eisenberg)
+
+- [#13520](https://github.com/ocaml/ocaml/issues/13520): Fix compilation of native-code version of systhreads. Bytecode fields
+ were being included in the thread descriptors.
+ (David Allsopp, review by Sébastien Hinderer and Miod Vallat)
+
+- [#13541](https://github.com/ocaml/ocaml/issues/13541), [#13591](https://github.com/ocaml/ocaml/issues/13591): Fix headers for C++ inclusion.
+ (Antonin Décimo, review by Nick Barnes, report by Kate Deplaix)
+
+- [#13579](https://github.com/ocaml/ocaml/issues/13579), [#13583](https://github.com/ocaml/ocaml/issues/13583): Unsoundness involving non-injective types + gadts
+ (Jacques Garrigue, report by @v-gb,
+ review by Richard Eisenberg and Florian Angeletti)
+
+- [#13598](https://github.com/ocaml/ocaml/issues/13598): Falsely triggered warning 56 [unreachable-case]
+ This was caused by unproper protection of the retyping function.
+ (Jacques Garrigue, report by Tõivo Leedjärv, review by Florian Angeletti)
+
+- [#13603](https://github.com/ocaml/ocaml/issues/13603), [#13604](https://github.com/ocaml/ocaml/issues/13604): fix source printing in the presence of the escaped raw
+ identifier `\#mod`.
+ (Florian Angeletti, report by Chris Casinghino, review by Gabriel Scherer)
diff --git a/data/tutorials/getting-started/1_00_install_OCaml.md b/data/tutorials/getting-started/1_00_install_OCaml.md
index 43ad02acdb..e7b0c55b31 100644
--- a/data/tutorials/getting-started/1_00_install_OCaml.md
+++ b/data/tutorials/getting-started/1_00_install_OCaml.md
@@ -17,7 +17,7 @@ On this page, you'll find installation instructions for Linux, macOS, Windows, a
OCaml has an official package manager, [opam](https://opam.ocaml.org/), which allows users to download and install OCaml tools and libraries. Opam also makes it practical to deal with different projects which require different versions of OCaml.
-Opam also installs the OCaml compiler. Alternatives exist, but opam is the best way to install OCaml. Although OCaml is available as a package in most Linux distributions, it is often outdated.
+Opam also installs the OCaml compiler. Alternatives exist, but opam is the best way to install OCaml. Although OCaml is available as a package in most Linux distributions, it is often outdated.
To install opam, you can [use your system package manager](https://opam.ocaml.org/doc/Install.html#Using-your-distribution-39-s-package-system) or download the [binary distribution](https://opam.ocaml.org/doc/Install.html#Binary-distribution). The details are available in these links, but for convenience, we use package distributions:
@@ -26,13 +26,13 @@ To install opam, you can [use your system package manager](https://opam.ocaml.or
If you're installing with [Homebrew](https://brew.sh/):
```shell
-$ brew install opam
+brew install opam
```
Or if you're using [MacPorts](https://www.macports.org/):
```shell
-$ port install opam
+port install opam
```
**Note**: While it's rather straightforward to install opam using macOS, it's possible you'll run into problems later with Homebrew because it has changed the way it installs. The executable files cannot be found in ARM64, the M1 processor used in newer Macs. Addressing this can be a rather complicated procedure, so we've made [a short ARM64 Fix doc](/docs/arm64-fix) explaining this so as not to derail this installation guide.
@@ -42,16 +42,19 @@ $ port install opam
It's preferable to install opam with your system's package manager on Linux, as superuser. On the opam site, find [details of all installation methods](https://opam.ocaml.org/doc/Install.html). A version of opam above 2.0 is packaged in all supported Linux distributions. If you are using an unsupported Linux distribution, please either download a precompiled binary or build opam from sources.
If you are installing in Debian or Ubuntu:
+
```shell
-$ sudo apt-get install opam
+sudo apt-get install opam
```
If you are installing in Arch Linux:
+
```shell
-$ sudo pacman -S opam
+sudo pacman -S opam
```
**Note**: The Debian package for opam, which is also used in Ubuntu, has the OCaml compiler as a recommended dependency. By default, such dependencies are installed. If you want to only install opam without OCaml, you need to run something like this:
+
```shell
sudo apt-get install --no-install-recommends opam
```
@@ -69,8 +72,9 @@ PS C:\> winget install Git.Git OCaml.opam
If you want the latest release of opam, install it through the binary distribution. On Unix and macOS, you'll need to install the following system packages first: `gcc`, `build-essential`, `curl`, `bubblewrap`, and `unzip`. Note that they might have different names depending on your operating system or distribution. Also, note this script internally calls `sudo`.
The following command will install the latest version of opam that applies to your system:
+
```shell
-$ bash -c "sh <(curl -fsSL https://opam.ocaml.org/install.sh)"
+bash -c "sh <(curl -fsSL https://opam.ocaml.org/install.sh)"
```
On Windows, the winget package is maintained by opam's developers and uses the binaries released [on GitHub](https://github.com/ocaml/opam/releases), however you can also install using an equivalent PowerShell script:
@@ -86,14 +90,15 @@ Invoke-Expression "& { $(Invoke-RestMethod https://opam.ocaml.org/install.ps1) }
After you install opam, you'll need to initialise it. To do so, run the following command, as a normal user. This might take a few minutes to complete.
```shell
-$ opam init -y
+opam init -y
```
**Note**: In case you are running `opam init` inside a Docker container, you will need to disable sandboxing, which is done by running `opam init --disable-sandboxing -y`. This is necessary, unless you run a privileged Docker container.
Make sure you follow the instructions provided at the end of the output of `opam init` to complete the initialisation. Typically, this is:
+
```
-$ eval $(opam env)
+eval $(opam env)
```
on Unix, and from the Windows Command Prompt:
@@ -112,7 +117,7 @@ Opam initialisation may take several minutes. While waiting for its installation
**Note**: opam can manage something called _switches_. This is key when switching between several OCaml projects. However, in this “getting started” series of tutorials, switches are not needed. If interested, you can read an introduction to [opam switches here](/docs/opam-switch-introduction).
-**Any problems installing?** Be sure to read the [latest release notes](https://opam.ocaml.org/blog/opam-2-2-0/). You can file an issue at https://github.com/ocaml/opam/issues or https://github.com/ocaml-windows/papercuts/issues.
+**Any problems installing?** Be sure to read the [latest release notes](https://opam.ocaml.org/blog/opam-2-2-0/). You can file an issue at or .
## Install Platform Tools
@@ -125,8 +130,9 @@ Now that we've successfully installed the OCaml compiler and the opam package ma
- [OCamlFormat](https://opam.ocaml.org/packages/ocamlformat/) to automatically format OCaml code
All these tools can be installed using a single command:
+
```shell
-$ opam install ocaml-lsp-server odoc ocamlformat utop
+opam install ocaml-lsp-server odoc ocamlformat utop
```
You're now all set and ready to start hacking.
@@ -134,6 +140,7 @@ You're now all set and ready to start hacking.
## Check Installation
To check that everything is working properly, you can start the UTop toplevel:
+
```shell
$ utop
────────┬─────────────────────────────────────────────────────────────┬─────────
@@ -147,6 +154,7 @@ utop #
```
You're now in an OCaml toplevel, and you can start typing OCaml expressions. For instance, try typing `21 * 2;;` at the `#` prompt, then hit `Enter`. You'll see the following:
+
```ocaml
# 21 * 2;;
- : int = 42
@@ -154,6 +162,8 @@ You're now in an OCaml toplevel, and you can start typing OCaml expressions. For
**Congratulations**! You've installed OCaml! 🎉
+Exit UTop by typing `#quit;;` or pressing `Ctrl+D`.
+
## Join the Community
Make sure you [join the OCaml community](/community). You'll find many community members on [Discuss](https://discuss.ocaml.org/) or [Discord](https://discord.com/invite/cCYQbqN). These are great places to ask for help if you have any issues.
diff --git a/data/tutorials/getting-started/1_01_a_tour_of_ocaml.md b/data/tutorials/getting-started/1_01_a_tour_of_ocaml.md
index 3fd4fb48da..9b0f2fd704 100644
--- a/data/tutorials/getting-started/1_01_a_tour_of_ocaml.md
+++ b/data/tutorials/getting-started/1_01_a_tour_of_ocaml.md
@@ -49,6 +49,7 @@ The goal of this tutorial is to provide the following capabilities:
## Expressions and Definitions
Let's start with a simple expression:
+
```ocaml
# 50 * 50;;
- : int = 2500
@@ -59,6 +60,7 @@ In OCaml, everything has a value, and every value has a type. The above example
The double semicolon `;;` at the end tells the toplevel to evaluate and print the result of the given phrase.
Here are examples of other primitive values and types:
+
```ocaml
# 6.28;;
- : float = 6.28
@@ -74,6 +76,7 @@ Here are examples of other primitive values and types:
```
OCaml has _type inference_. It automatically determines the type of an expression without much guidance from the programmer. _Lists_ have a [dedicated tutorial](/docs/lists). For the time being, the following two expressions are both lists. The former contains integers, and the latter, strings.
+
```ocaml
# let u = [1; 2; 3; 4];;
val u : int list = [1; 2; 3; 4]
@@ -83,12 +86,14 @@ val u : int list = [1; 2; 3; 4]
```
The lists' types, `int list` and `string list`, have been inferred from the type of their elements. Lists can be empty `[]` (pronounced “nil”). Note that the first list has been given a name using the `let … = …` construction, which is detailed below. The most primitive operation on lists is to add a new element at the front of an existing list. This is done using the “cons” operator, written with the double colon operator `::`.
+
```ocaml
# 9 :: u;;
- : int list = [9; 1; 2; 3; 4]
```
In OCaml, `if … then … else …` is not a statement; it is an expression.
+
```ocaml
# 2 * if "hello" = "world" then 3 else 5;;
- : int = 10
@@ -97,6 +102,7 @@ In OCaml, `if … then … else …` is not a statement; it is an expression.
The source beginning at `if` and ending at `5` is parsed as a single integer expression that is multiplied by 2. OCaml has no need for two different test constructions. The [ternary conditional operator](https://en.wikipedia.org/wiki/Ternary_conditional_operator) and the `if … then … else …` are the same. Also note parentheses are not needed here, which is often the case in OCaml.
Values can be given names using the `let` keyword. This is called _binding_ a value to a name. For example:
+
```ocaml
# let x = 50;;
val x : int = 50
@@ -124,6 +130,7 @@ val feets : int = 5280
This is discussed further in [`odoc` for Authors: Special Comments](https://ocaml.github.io/odoc/odoc_for_authors.html#special_comments).
Names can be defined locally, within an expression, using the `let … = … in …` syntax:
+
```ocaml
# let y = 50 in y * y;;
- : int = 2500
@@ -135,6 +142,7 @@ Error: Unbound value y
This example defines the name `y` and binds it to the value `50`. It is then used in the expression `y * y`, resulting in the value `2500`. Note that `y` is only defined in the expression following the `in` keyword.
Since `let … = … in …` is an expression, it can be used within another expression in order to have several values with their own names:
+
```ocaml
# let a = 1 in
let b = 2 in
@@ -145,6 +153,7 @@ Since `let … = … in …` is an expression, it can be used within another exp
This defines two names: `a` with value `1` and `b` with value `2`. Then the example uses them in the expression `a + b`, resulting in the value of `3`.
In OCaml, the equality symbol has two meanings. It is used in definitions and equality tests.
+
```ocaml
# let dummy = "hi" = "hello";;
val dummy : bool = false
@@ -155,6 +164,7 @@ This is interpreted as: “define `dummy` as the result of the structural equali
## Functions
In OCaml, since everything is a value, functions are values too. Functions are defined using the `let` keyword:
+
```ocaml
# let square x = x * x;;
val square : int -> int =
@@ -177,17 +187,19 @@ The REPL indicates that the type of `square` is `int -> int`. This means it is a
- : bool = true
```
-Some functions, such as `String.ends_with` have labelled parameters. Labels are useful when a function has several parameters of the same type; naming arguments allows to guess their purpose. Above, `~suffix:"less"` indicates `"less"` is passed as labelled argument `suffix`. Labelled arguments are detailed in the [Labelled Arguments](/docs/labels) tutorial.
+Some functions, such as `String.ends_with` have labelled parameters. Labels are useful when a function has several parameters of the same type; naming arguments allows to guess their purpose. Above, `~suffix:"less"` indicates `"less"` is passed as labelled argument `suffix`. Labelled arguments are detailed in the [Labelled Arguments](/docs/labels) tutorial.
### Anonymous Functions
_Anonymous_ functions do not have a name, and they are defined with the `fun` keyword:
+
```ocaml
# fun x -> x * x;;
- : int -> int =
```
We can write anonymous functions and immediately apply them to a value:
+
```ocaml
# (fun x -> x * x) 50;;
- : int = 2500
@@ -196,18 +208,21 @@ We can write anonymous functions and immediately apply them to a value:
### Functions with Multiple Parameters and Partial Application
A function may have several parameters, separated by spaces.
+
```ocaml
# let cat a b = a ^ " " ^ b;;
val cat : string -> string -> string =
```
The function `cat` has two `string` parameters, `a` and `b`, and returns a value of type `string`.
+
```ocaml
# cat "ha" "ha";;
- : string = "ha ha"
```
Functions don't have to be called with all the arguments they expect. It is possible to only pass `a` to `cat` without passing `b`.
+
```ocaml
# let cat_hi = cat "hi";;
val cat_hi : string -> string =
@@ -216,6 +231,7 @@ val cat_hi : string -> string =
This returns a function that expects a single string, here the `b` from the definition of `cat`. This is called a _partial application_. In the above, `cat` was partially applied to `"hi"`.
The function `cat_hi`, which resulted from the partial application of `cat`, behaves as follows:
+
```ocaml
# cat_hi "friend";;
- : string = "hi friend"
@@ -224,6 +240,7 @@ The function `cat_hi`, which resulted from the partial application of `cat`, beh
### Type Parameters and Higher-Order Functions
A function may expect a function as a parameter, which is called a _higher-order_ function. A well-known example of higher-order function is `List.map`. Here is how it can be used:
+
```ocaml
# List.map;;
- : ('a -> 'b) -> 'a list -> 'b list =
@@ -245,6 +262,7 @@ The function `List.map` can be applied on any kind of list. Here it is given a l
### Side-Effects and the `unit` Type
Performing operating system level input-output operations is done using functions. Here is an example of each:
+
```ocaml
# read_line;;
- : unit -> string =
@@ -272,6 +290,7 @@ Input-output is an example of something taking place when executing a function b
A recursive function calls itself in its own body. Such functions must be declared using `let rec … = …` instead of just `let`. Recursion is not the only means to perform iterative computation on OCaml. Loops such as `for` and `while` are available, but they are meant to be used when writing imperative OCaml in conjunction with mutable data. Otherwise, recursive functions should be preferred.
Here is an example of a function which creates a list of consecutive integers between two bounds.
+
```ocaml
# let rec range lo hi =
if lo > hi then
@@ -303,6 +322,7 @@ Each `=>` sign corresponds to the computation of a recursive step, except the la
### Type Conversion and Type-Inference
OCaml has floating-point values of type `float`. To add floats, one must use `+.` instead of `+`:
+
```ocaml
# 2.0 +. 2.0;;
- : float = 4.
@@ -313,6 +333,7 @@ In OCaml, `+.` is the addition between floats, while `+` is the addition between
In many programming languages, values can be automatically converted from one type into another. This includes _implicit type conversion_ and _promotion_. For example, in such a language, if you write `1 + 2.5`, the first argument (an integer) is promoted to a floating point number, making the result a floating point number, too.
OCaml never implicitly converts values from one type to another. It is not possible to perform the addition of a float and integer. Both examples below throw an error:
+
```ocaml
# 1 + 2.5;;
Error: This expression has type float but an expression was expected of type
@@ -323,9 +344,11 @@ Error: This expression has type int but an expression was expected of type
float
Hint: Did you mean `1.'?
```
+
In the first example, `+` is intended to be used with integers, so it can't be used with the `2.5` float. In the second example, `+.` is intended to be used with floats, so it can't be used with the `1` integer.
In OCaml you need to explicitly convert the integer to a floating point number using the `float_of_int` function:
+
```ocaml
# float_of_int 1 +. 2.5;;
- : float = 3.5
@@ -336,6 +359,7 @@ There are several reasons why OCaml requires explicit conversions. Most importan
### Lists
Lists may be the most common data type in OCaml. They are ordered collections of values having the same type. Here are a few examples.
+
```ocaml
# [];;
- : 'a list = []
@@ -351,18 +375,21 @@ Lists may be the most common data type in OCaml. They are ordered collections of
```
The examples above read the following way:
+
1. The empty list, nil
1. A list containing the numbers 1, 2, and 3
1. A list containing the Booleans `false`, `false`, and `true`. Repetitions are allowed.
1. A list of lists
Lists are defined as being either empty, written `[]`, or being an element `x` added at the front of another list `u`, which is written `x :: u` (the double colon operator is pronounced “cons”).
+
```ocaml
# 1 :: [2; 3; 4];;
- : int list = [1; 2; 3; 4]
```
In OCaml, _pattern matching_ provides a means to inspect data of any kind, except functions. In this section, it is introduced on lists, and it will be generalised to other data types in the next section. Here is how pattern matching can be used to define a recursive function that computes the sum of a list of integers:
+
```ocaml
# let rec sum u =
match u with
@@ -373,10 +400,12 @@ val sum : int list -> int =
# sum [1; 4; 3; 2; 5];;
- : int = 15
```
+Note that the `x :: v` pattern in the second matching expression is used to destructure the list into its head `x` and tail `v`, where _head_ is the first element of the list and _tail_ is the rest of the list.
#### Polymorphic Functions on Lists
Here is how to write a recursive function that computes the length of a list:
+
```ocaml
# let rec length u =
match u with
@@ -399,6 +428,7 @@ This function operates not just on lists of integers but on any kind of list. It
#### Defining a Higher-Order Function
It is possible to pass a function as argument to another function. Functions having other functions as parameters are called _higher-order_ functions. This was illustrated earlier using function `List.map`. Here is how `map` can be written using pattern matching on lists.
+
```ocaml
# let square x = x * x;;
val square : int -> int
@@ -416,6 +446,7 @@ val map : ('a -> 'b) -> 'a list -> 'b list =
### Pattern Matching, Cont'd
Pattern matching isn't limited to lists. Any kind of data can be inspected using it, except functions. Patterns are expressions that are compared to an inspected value. It could be performed using `if … then … else …`, but pattern matching is more convenient. Here is an example using the `option` data type that will be detailed in the [Modules and the Standard Library](#modules-and-the-standard-library) section.
+
```ocaml
# #show option;;
type 'a option = None | Some of 'a
@@ -432,6 +463,7 @@ The inspected value is `opt` of type `option`. It is compared against the patter
Pattern matching is detailed in the [Basic Datatypes](/docs/basic-data-types) tutorial as well as in per data type tutorials.
In this other example, the same comparison is made, using `if … then … else …` and pattern matching.
+
```ocaml
# let g x =
if x = "foo" then 1
@@ -453,6 +485,7 @@ val g' : string -> int =
The underscore symbol is a catch-all pattern; it matches with anything.
Note that OCaml throws a warning when pattern matching does not catch all cases:
+
```ocaml
# fun i -> match i with 0 -> 1;;
Line 1, characters 9-28:
@@ -465,6 +498,7 @@ Here is an example of a case that is not matched:
### Pairs and Tuples
Tuples are fixed-length collections of elements of any type. Pairs are tuples that have two elements. Here is a 3-tuple and a pair:
+
```ocaml
# (1, "one", 'K');;
- : int * string * char = (1, "one", 'K')
@@ -474,6 +508,7 @@ Tuples are fixed-length collections of elements of any type. Pairs are tuples th
```
Access to the components of tuples is done using pattern matching. For instance, the predefined function `snd` returns the second component of a pair:
+
```ocaml
# let snd p =
match p with
@@ -493,6 +528,7 @@ The type of tuples is written using `*` between the components' types.
Like pattern matching generalises `switch` statements, variant types generalise enumerated and union types.
Here is the definition of a variant type acting as an enumerated data type:
+
```ocaml
# type primary_colour = Red | Green | Blue;;
type primary_colour = Red | Green | Blue
@@ -502,6 +538,7 @@ type primary_colour = Red | Green | Blue
```
Here is the definition of a variant type acting as a union type:
+
```ocaml
# type http_response =
| Data of string
@@ -528,6 +565,7 @@ Data
```
Here is something sitting in between:
+
```ocaml
# type page_range =
| All
@@ -539,6 +577,7 @@ type page_range = All | Current | Range of int * int
In the previous definitions, the capitalised identifiers are called _constructors_. They allow the creation of variant values. This is unrelated to object-oriented programming.
As suggested in the first sentence of this section, variants go along with pattern matching. Here are some examples:
+
```ocaml
# let colour_to_rgb colour =
match colour with
@@ -562,6 +601,7 @@ val is_printable : int -> int -> page_range -> bool =
```
Like a function, a variant can be recursive if it refers to itself in its own definition. The predefined type `list` provides an example of such a variant:
+
```ocaml
# #show list;;
type 'a list = [] | (::) of 'a * 'a list
@@ -572,6 +612,7 @@ As previously shown, `sum`, `length`, and `map` functions provide examples of pa
### Records
Like tuples, records also pack elements of several types together. However, each element is given a name. Like variant types, records types must be defined before being used. Here are examples of a record type, a value, access to a component, and pattern matching on the same record.
+
```ocaml
# type person = {
first_name : string;
@@ -589,6 +630,7 @@ val gerard : person = {first_name = "Gérard"; surname = "Huet"; age = 76}
```
When defining `gerard`, no type needs to be declared. The type checker will search for a record which has exactly three fields with matching names and types. Note that there are no typing relationships between records. It is not possible to declare a record type that extends another by adding fields. Record type search will succeed if it finds an exact match and fails in any other case.
+
```ocaml
# let s = gerard.surname;;
val s : string = "Huet"
@@ -609,12 +651,14 @@ Here, the pattern `{ age = x; _ }` is typed with the most recently declared reco
### Exceptions
When a computation is interrupted, an exception is thrown. For instance:
+
```ocaml
# 10 / 0;;
Exception: Division_by_zero.
```
Exceptions are raised using the `raise` function.
+
```ocaml
# let id_42 n = if n <> 42 then raise (Failure "Sorry") else n;;
val id_42 : int -> int =
@@ -629,6 +673,7 @@ Exception: Failure "Sorry".
Note that exceptions do not appear in function types.
Exceptions are caught using the `try … with …` construction:
+
```ocaml
# try id_42 0 with Failure _ -> 0;;
- : int = 0
@@ -640,12 +685,14 @@ The standard library provides several predefined exceptions. It is possible to d
Another way to deal with errors in OCaml is by returning value of type `result`,
which can represent either the correct result or an error. Here is how it is defined:
+
```ocaml
# #show result;;
type ('a, 'b) result = Ok of 'a | Error of 'b
```
So one may write:
+
```ocaml
# let id_42_res n = if n <> 42 then Error "Sorry" else Ok n;;
val id_42_res : int -> (int, string) result =
@@ -665,6 +712,7 @@ val id_42_res : int -> (int, string) result =
## Working with Mutable State
OCaml supports imperative programming. Usually, the `let … = …` syntax does not define variables, it defines constants. However, mutable variables exist in OCaml. They are called _references_. Here's how we create a reference to an integer:
+
```ocaml
# let r = ref 0;;
val r : int ref = {contents = 0}
@@ -673,6 +721,7 @@ val r : int ref = {contents = 0}
It is syntactically impossible to create an uninitialised or null reference. The `r`
reference is initialised with the integer zero. Accessing a reference's content
is done using the `!` de-reference operator.
+
```ocaml
# !r;;
- : int = 0
@@ -684,18 +733,21 @@ it is not possible to update an integer or multiply a reference.
Let's update the content of `r`. Here `:=` is the assignment operator; it is
pronounced “receives”.
+
```ocaml
# r := 42;;
- : unit = ()
```
This returns `()` because changing the content of a reference is a side-effect.
+
```ocaml
# !r;;
- : int = 42
```
Execute an expression after another with the `;` operator. Writing `a; b` means: execute `a`. Once done, execute `b`, only returns the value of `b`.
+
```ocaml
# let text = ref "hello ";;
val text : string ref = {contents = "hello "}
@@ -706,6 +758,7 @@ hello world!
```
Here are the side effects that occur in the second line:
+
1. Display the contents of the reference `text` on standard output
1. Update the contents of the reference `text`
1. Display the contents of the reference `text` on standard output
@@ -715,6 +768,7 @@ This behaviour is the same as in an imperative language. However, although `;` i
## Modules and the Standard Library
Organising source code in OCaml is done using something called _modules_. A module is a group of definitions. The _standard library_ is a set of modules available to all OCaml programs. Here are how the definitions contained in the `Option` module of the standard library can be listed:
+
```ocaml
# #show Option;;
module Option :
@@ -740,6 +794,7 @@ module Option :
```
Definitions provided by modules are referred to by adding the module name as a prefix to their name.
+
```ocaml
# Option.map;;
- : ('a -> 'b) -> 'a option -> 'b option =
@@ -755,6 +810,7 @@ Definitions provided by modules are referred to by adding the module name as a p
```
Here, usage of the function `Option.map` is illustrated in several steps.
+
1. Display its type. It has two parameters: a function of type `'a -> 'b` and an `'a option`.
1. Using partial application, only pass `fun x -> x * x`. Check the type of the resulting function.
1. Apply with `None`.
@@ -763,6 +819,7 @@ Here, usage of the function `Option.map` is illustrated in several steps.
When the option value provided contains an actual value (i.e., it is `Some` something), it applies the provided function and returns its result wrapped in an option. When the option value provided doesn't contain anything (i.e., it is `None`), the result doesn't contain anything as well (i.e., it is `None` too).
The `List.map` function which was used earlier in this section is also part of a module, the `List` module.
+
```ocaml
# List.map;;
- : ('a -> 'b) -> 'a list -> 'b list =
diff --git a/data/tutorials/getting-started/1_02_your_first_ocaml_program.md b/data/tutorials/getting-started/1_02_your_first_ocaml_program.md
index 96e9577c87..3173098a70 100644
--- a/data/tutorials/getting-started/1_02_your_first_ocaml_program.md
+++ b/data/tutorials/getting-started/1_02_your_first_ocaml_program.md
@@ -28,7 +28,6 @@ Once you've completed this tutorial, you should be able to:
- Make a definition private
- Download, install, and use a package from the open source repository
-
How to work on several OCaml projects simultaneously is out of the scope of this tutorial. Currently (Summer 2023), this requires using opam local [_switches_](https://opam.ocaml.org/doc/man/opam-switch.html). This allows handling different sets of dependencies per project. Check the Best Practices document on [Dependencies](https://ocaml.org/docs/managing-dependencies) addressing that matter for detailed instructions. This document was written and tested using a global switch, which is created by default when installing opam and can be ignored in the beginning.
-->
@@ -57,15 +56,10 @@ Success: initialized project component named hello
**Note 2**: Throughout this tutorial, outputs generated by Dune might vary slightly because of the Dune version installed. This tutorial shows the output for Dune 3.12. If you'd like to get the most recent version of Dune, run `opam update; opam upgrade dune` in a terminal.
-The project is stored in a directory named `hello`. The `tree` command lists the files and directories created. It might be necessary to install `tree` if you don't see the following. Through Homebrew, for example, run `brew install tree`.
-
-**Note 3**: If you get an error in Homebrew from this in an Apple silicon macOS, it's likely an issue with the architecture switch from Intel to ARM. Please refer to the [ARM64 Fix](/docs/arm64-fix) to remedy the ARM64 error.
+The project is stored in a directory named `hello` with the following contents:
-**Note 4**: Unlike in Unix where they contain compiled binaries, directories `lib` and `bin` contain source code files, for libraries and programs, respectively. This is the a convention used in many OCaml projects, and the canned project created by Dune. All the built artifacts, and a copy of the sources, are stored in the `_build` directory. You shall not edit anything in the `_build` directory.
```shell
-$ cd hello
-$ tree
-.
+hello
├── bin
│ ├── dune
│ └── main.ml
@@ -78,11 +72,12 @@ $ tree
└── test
├── dune
└── hello.ml
-
-4 directories, 8 files
```
+Unlike in Unix where they contain compiled binaries, directories `lib` and `bin` contain source code files, for libraries and programs, respectively. This is the convention used in many OCaml projects, including those created by Dune. All the built artifacts, and a copy of the sources, are stored in the `_build` directory. Do not edit anything in the `_build` directory, since any manual edits will be overwritten during subsequent builds.
+
OCaml source files have the `.ml` extension, which stands for “Meta Language.” Meta Language (ML) is an ancestor of OCaml. This is also what the “ml” stands for in “OCaml.” Here is the content of the `bin/main.ml` file:
+
```ocaml
let () = print_endline "Hello, World!"
```
@@ -92,11 +87,13 @@ The project-wide metadata is available in the `dune-project` file. It contains i
Each directory containing source files that need to be built must contain a `dune` file describing how.
This builds the project:
+
```shell
-$ opam exec -- dune build
+opam exec -- dune build
```
This launches the executable it creates:
+
```shell
$ opam exec -- dune exec hello
Hello, World!
@@ -113,8 +110,8 @@ In the rest of this tutorial, we will make more changes to this project in order
Before we dive in, note that you will typically want to use Dune's watch mode to continually compile and optionally restart your program. This ensures that the language server has the freshest possible data about your project, so your editor support will be top-notch. To use watch mode, just add the `-w` flag:
```shell
-$ opam exec -- dune build -w
-$ opam exec -- dune exec hello -w
+opam exec -- dune build -w
+opam exec -- dune exec hello -w
```
## Why Isn't There a Main Function?
@@ -128,11 +125,13 @@ However, it is common practice to single out a value that triggers all the side
## Modules and the Standard Library, Cont'd
Let's summarise what was said about modules in the [Tour of OCaml](/docs/tour-of-ocaml):
+
- A module is a collection of named values.
- Identical names from distinct modules don't clash.
-- The standard library is collection of several modules.
+- The standard library is a collection of several modules.
Modules aid in organising projects; concerns can be separated into isolated modules. This is outlined in the next section. Before creating a module ourselves, we'll demonstrate using a definition from a module of the standard library. Change the content of the file `bin/main.ml` to this:
+
```ocaml
let () = Printf.printf "%s\n" "Hello, World!"
```
@@ -144,16 +143,19 @@ This replaces the function `print_endline` with the function `printf` from the `
Each OCaml file defines a module, once compiled. This is how separate compilation works in OCaml. Each sufficiently standalone concern should be isolated into a module. References to external modules create dependencies. Circular dependencies between modules are not allowed.
To create a module, let's create a new file named `lib/en.ml` containing this:
+
```ocaml
let v = "Hello, world!"
```
Here is a new version of the `bin/main.ml` file:
+
```ocaml
let () = Printf.printf "%s\n" Hello.En.v
```
Now execute the resulting project:
+
```shell
$ opam exec -- dune exec hello
Hello, world!
@@ -161,13 +163,14 @@ Hello, world!
The file `lib/en.ml` creates the module named `En`, which in turn defines a string value named `v`. Dune wraps `En` into another module called `Hello`; this name is defined by the stanza `name hello` in the file `lib/dune`. The string definition is `Hello.En.v` from the `bin/main.ml` file.
-
Dune can launch UTop to access the modules exposed by a project interactively. Here's how:
+
```shell
-$ opam exec -- dune utop
+opam exec -- dune utop
```
Then, inside the `utop` toplevel, it is possible to inspect our `Hello.En` module:
+
```ocaml
# #show Hello.En;;
module Hello : sig val v : string end
@@ -176,6 +179,7 @@ module Hello : sig val v : string end
Now exit `utop` with `Ctrl-D` or enter `#quit;;` before going to the next section.
**Note**: If you add a file named `hello.ml` in the `lib` directory, Dune will consider this the whole `Hello` module and it will make `En` unreachable. If you want your module `En` to be visible, you need to add this in your `hello.ml` file:
+
```ocaml
module En = En
```
@@ -184,6 +188,7 @@ module En = En
## Defining Module Interfaces
UTop's `#show` command displays an [API](https://en.wikipedia.org/wiki/API#Libraries_and_frameworks) (in the software library sense): the list of definitions provided by a module. In OCaml, this is called a _module interface_. An `.ml` file defines a module. In a similar way, an `.mli` file defines a module interface. The module interface file corresponding to a module file must have the same base name, e.g., `en.mli` is the module interface for module `en.ml`. Create a `lib/en.mli` file with this content:
+
```ocaml
val v : string
```
@@ -200,11 +205,13 @@ let v = hello ^ ", world!"
```
Also edit the `bin/main.ml` file like this:
+
```ocaml
let () = Printf.printf "%s\n" Hello.En.hello
```
Trying to compile this fails.
+
```shell
$ opam exec -- dune build
File "hello/bin/main.ml", line 1, characters 30-43:
@@ -217,6 +224,7 @@ This is because we haven't changed `lib/en.mli`. Since it does not list
`hello`, it is therefore private.
## Defining Multiple Modules in a Library
+
Multiple modules can be defined in a single library. To demonstrate this,
create a new file named `lib/es.ml` with the following content:
@@ -225,6 +233,7 @@ let v = "¡Hola, mundo!"
```
And use the new module in `bin/main.ml`:
+
```ocaml
let () = Printf.printf "%s\n" Hello.Es.v
let () = Printf.printf "%s\n" Hello.En.v
@@ -246,8 +255,9 @@ A more detailed introduction to modules can be found at [Modules](/docs/modules)
OCaml has an active community of open-source contributors. Most projects are available using the opam package manager, which you installed in the [Install OCaml](/docs/up-and-ready) tutorial. The following section shows you how to install and use a package from opam's open-source repository.
To illustrate this, let's update our `hello` project to parse a string containing an [S-expression](https://en.wikipedia.org/wiki/S-expression) and print back to a string, both using [Sexplib](https://github.com/janestreet/sexplib). First, update the package list for opam by running `opam update`. Then, install the `Sexplib` package with this command:
+
```shell
-$ opam install sexplib
+opam install sexplib
```
Next, define a string containing a valid S-expression in `bin/main.ml`. Parse
@@ -263,8 +273,9 @@ let exp1 = Sexplib.Sexp.of_string "(This (is an) (s expression))"
(* Convert back to a string to print *)
let () = Printf.printf "%s\n" (Sexplib.Sexp.to_string exp1)
```
+
The string you entered representing a valid S-expression is parsed into
-an S-expression type, which is defined as either an `Atom` (string) or a `List`
+an S-expression type, which is defined as either an `Atom` (string) or a `List`
of S-expressions (it's a recursive type). Refer to the [Sexplib documentation](https://github.com/janestreet/sexplib) for more information.
Before the example will build and run, you need to tell Dune that it needs `Sexplib` to compile the project. Do this by adding `Sexplib` to the `library` stanza of the `bin/dune` file. The full `bin/dune` file should then match the following.
@@ -279,6 +290,7 @@ Before the example will build and run, you need to tell Dune that it needs `Sexp
**Fun fact**: Dune configuration files are S-expressions.
Finally, execute as before:
+
```shell
$ opam exec -- dune exec hello
(This(is an)(s expression))
@@ -290,11 +302,13 @@ $ opam exec -- dune exec hello
**Note**: This example was successfully tested on Windows using DkML 2.1.0. Run `dkml version` to see the version.
Let's assume we'd like `hello` to display its output as if it was a list of strings in UTop: `["hello"; "using"; "an"; "opam"; "library"]`. To do that, we need a function turning a `string list` into a `string`, adding brackets, spaces, and commas. Instead of defining it ourselves, let's generate it automatically with a package. We'll use [`ppx_deriving`](https://github.com/ocaml-ppx/ppx_deriving). Here is how to install it:
+
```shell
-$ opam install ppx_deriving
+opam install ppx_deriving
```
Dune needs to be told how to use it, which is done in the `lib/dune` file. Note that this is different from the `bin/dune` file that you edited earlier! Open up the `lib/dune` file, and edit it to look like this:
+
```lisp
(library
(name hello)
@@ -306,12 +320,14 @@ The line `(preprocess (pps ppx_deriving.show))` means that before compilation th
The files `lib/en.ml` and `lib/en.mli` need to be edited, too:
**`lib/en.mli`**
+
```ocaml
val string_of_string_list : string list -> string
val v : string list
```
**`lib/en.ml`**
+
```ocaml
let string_of_string_list = [%show: string list]
@@ -319,15 +335,18 @@ let v = String.split_on_char ' ' "Hello using an opam library"
```
Let's read this from the bottom up:
+
- `v` has the type `string list`. We're using `String.split_on_char` to turn a `string` into a `string list` by splitting the string on space characters.
- `string_of_string_list` has type `string list -> string`. This converts a list of strings into a string, applying the expected formatting.
Finally, you'll also need to edit `bin/main.ml`
+
```ocaml
let () = print_endline Hello.En.(string_of_string_list v)
```
Here is the result:
+
```shell
$ opam exec -- dune exec hello
["Hello"; "using"; "an"; "opam"; "library"]
@@ -338,11 +357,13 @@ $ opam exec -- dune exec hello
This section explains the purpose of the files and directories created by `dune init proj` which haven't been mentioned earlier.
Along the history of OCaml, several build systems have been used. As of writing this tutorial (Summer 2023), Dune is the mainstream one, which is why it is used in the tutorial. Dune automatically extracts the dependencies between the modules from the files and compiles them in a compatible order. It only needs one `dune` file per directory where there is something to build. The three directories created by `dune init proj` have the following purposes:
+
- `bin`: executable programs
- `lib`: libraries
- `test`: tests
There will be a tutorial dedicated to Dune. This tutorial will present the many features of Dune, a few of which are listed here:
+
- Running tests
- Generating documentation
- Producing packaging metadata (here in `hello.opam`)
@@ -353,30 +374,35 @@ The `_build` directory is where Dune stores all the files it generates. It can b
## Minimum Setup
In this last section, let's create a bare minimum project, highlighting what's really needed for Dune to work. We begin by creating a fresh project directory:
+
```shell
-$ cd ..
-$ mkdir minimo
-$ cd minimo
+cd ..
+mkdir minimo
+cd minimo
```
At the very least, Dune only needs two files: `dune-project` and one `dune` file. Here is how to write them with as little text as possible:
`dune-project`
+
```lisp
(lang dune 3.6)
```
`dune`
+
```lisp
(executable (name minimo))
```
`minimo.ml`
+
```ocaml
let () = print_endline "My name is Minimo"
```
That's all! This is sufficient for Dune to build and execute the `minimo.ml` file.
+
```shell
$ opam exec -- dune exec ./minimo.exe
My name is Minimo
diff --git a/data/tutorials/getting-started/2_00_editor_setup.md b/data/tutorials/getting-started/2_00_editor_setup.md
index 53f8b076e7..73c106ab2a 100644
--- a/data/tutorials/getting-started/2_00_editor_setup.md
+++ b/data/tutorials/getting-started/2_00_editor_setup.md
@@ -16,26 +16,28 @@ OCaml has plugins for many editors, but the most actively maintained are for Vis
For VSCode, install the [OCaml Platform Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=ocamllabs.ocaml-platform) from the Visual Studio Marketplace. The extension depends on OCaml LSP and OCamlFormat. To install them in your switch, you can run:
```shell
-$ opam install ocaml-lsp-server ocamlformat
+opam install ocaml-lsp-server ocamlformat
```
-Upon first loading an OCaml source file, you may be prompted to select the toolchain in use. Pick the version of OCaml you are using, e.g., `5.1.0` from the list.
+Upon first loading an OCaml source file, you may be prompted to select the toolchain in use. Pick the version of OCaml you are using, e.g., `5.1.0` from the list.
### Editor Features at Your Disposal
+
If your editor is setup correctly, here are some important features you can begin using to your advantage:
-#### 1) Hovering for Type Information:
+
+#### 1) Hovering for Type Information

This is a great feature that let's you see type information of any OCaml variable or function. All you have to do is place your cursor over the code and it will be displayed in the tooltip.
-#### 2) Jump to Definitions With `Ctrl + Click`:
+#### 2) Jump to Definitions With `Ctrl + Click`

If you hold down the Ctrl key while hovering, the code appears as a clickable link which if clicked takes you to the file where the implementation is. This can be great if you want to understand how a piece of code works under the hood. In this example, hovering and `Ctrl + Clicking` over the `peek` method of the `Queue` module will take you to the definiton of the `peek` method itself and how it is implemented.
-#### 3) OCaml Commands With `Ctrl + Shift + P`:
+#### 3) OCaml Commands With `Ctrl + Shift + P`

@@ -47,27 +49,95 @@ If you used the DkML distribution, you will need to:
1. Go to `File` > `Preferences` > `Settings` view (or press `Ctrl ,`)
2. Select `User` > `Extensions` > `OCaml Platform`
3. Uncheck `OCaml: Use OCaml Env`. That's it!
+
+## Emacs
+
+Using Emacs to work with OCaml requires at least two modes:
+
+- A major mode, which, among other things, supports syntax highlighting and the structuring of indentation levels
+- A minor mode, which will interact with a language server (such as `ocaml-lsp-server` or `merlin`). In this tutorial, we will focus on using the new `ocaml-eglot` mode and `ocaml-lsp-server` as a server.
+
+### Choosing a major mode
+
+There are several major modes dedicated to OCaml, of which the 3 main ones are:
+
+- [Tuareg](https://github.com/ocaml/tuareg): an old-fashioned (but still updated), very complete mode, usually the recommended one
+- [Caml](https://github.com/ocaml/caml-mode): a mode even older than `tuareg` (but still updated), lighter than `tuareg`
+- [Neocaml](https://github.com/bbatsov/neocaml): a brand new mode, based on modern tools (like [tree-sitter](https://tree-sitter.github.io/tree-sitter/)). Still experimental at the time of writing.
+
+For the purposes of this tutorial, we are going to focus on the use of `tuareg` as the major mode, but you should feel free to experiment and choose your favourite one! To use `tuareg`, you can add these lines to your Emacs configuration:
+
+```elisp
+(use-package tuareg
+ :ensure t
+ :mode (("\\.ocamlinit\\'" . tuareg-mode)))
+```
+
+
+#### Melpa and `use-package`
+
+If your version of Emacs does not support the `use-package` macro (or is not set up to take MELPA packages into account), please update it and follow these instructions to install [`use-package`](https://github.com/jwiegley/use-package) and [MELPA](https://melpa.org/#/getting-started).
+
+### LSP setup for OCaml
+
+Since version `29.1`, Emacs has had a built-in mode for interacting with LSP servers, [Eglot](https://www.gnu.org/software/emacs/manual/html_mono/eglot.html). If you are using an earlier version of Emacs, you will need to install it this way:
+
+```elisp
+(use-package eglot
+ :ensure t)
+```
+
+Next, we need to bridge the gap between our major mode (in this case, `tuareg`) and `eglot`. This is done using the [`ocaml-eglot`](https://github.com/tarides/ocaml-eglot) package:
+
+```elisp
+(use-package ocaml-eglot
+ :ensure t
+ :after tuareg
+ :hook
+ (tuareg-mode . ocaml-eglot)
+ (ocaml-eglot . eglot-ensure))
+```
-## Vim and Emacs
+And that's all there is to it! Now all you need to do is install `ocaml-lsp-server` and `ocamlformat` in our [switch](/docs/opam-switch-introduction):
-**For Vim and Emacs**, we won't use the LSP server but rather directly talk to Merlin.
+```shell
+opam install ocaml-lsp-server ocamlformat
+```
+
+You are now ready to edit OCaml code _productively_ with Emacs!
+
+#### Finer configuration
+
+OCaml-eglot can be finely configured, the project [README](https://github.com/tarides/ocaml-eglot/blob/main/README.md) gives several configuration paths to adapt perfectly to your workflow. You will also find there an exhaustive presentation of the different functions offered by the mode.
+
+
+#### Getting Type Information
+
+Opening an OCaml file should launch an `ocaml-lsp` server, and you can convince yourself that it's working by using, for example, the `ocaml-eglot-type-enclosing` command (or using the `C-c C-t` binding) on an expression of your choice:
+
+
+
+OCaml-eglot [README](https://github.com/tarides/ocaml-eglot/blob/main/README.md) provides a comprehensive overview of all the functions available in this mode!
+
+
+## Vim
+
+For Vim, we won't use the LSP server but rather directly talk to Merlin.
```shell
-$ opam install merlin
+opam install merlin
```
After installing Merlin above, instructions will be printed on how to link Merlin with your editor. If you do not have them visible, just run this command:
```shell
-$ opam user-setup install
+opam user-setup install
```
### Talking to Merlin
#### Getting Type Information
-**Vim**
-

- In the Vim editor, press the Esc to enter command mode.
@@ -75,12 +145,3 @@ $ opam user-setup install
- Type `:MerlinTypeOf` and press Enter.
- The type information will be displayed in the command bar.
Other Merlin commands for Vim are available and you can checkout their usage on the [Merlin official documentation for Vim](https://ocaml.github.io/merlin/editor/vim/).
-
-**Emacs**
-
-
-
-- In the Emacs editor, place you cursor over the variable.
-- Use the keyboard shortcut Alt + x followed by `merlin-type-enclosing`
-- The type information will be displayed in the mini-buffer.
-Other Merlin commands for Emacs are available and you can checkout their usage on the [Merlin Official documentation for Emacs](https://ocaml.github.io/merlin/editor/emacs/).
diff --git a/data/tutorials/getting-started/2_01_toplevel.md b/data/tutorials/getting-started/2_01_toplevel.md
index 59b618446e..ac10339ca9 100644
--- a/data/tutorials/getting-started/2_01_toplevel.md
+++ b/data/tutorials/getting-started/2_01_toplevel.md
@@ -9,6 +9,7 @@ category: "Tooling"
An OCaml toplevel is a chat between the user and OCaml. The user writes OCaml code, and UTop evaluates it. This is why it is also called a Read-Eval-Print-Loop (REPL). Several OCaml toplevels exist, like `ocaml` and `utop`. We recommend using UTop, which is part of the [OCaml Platform](/docs/platform) toolchain.
To run UTop, we use the `utop` command, which looks like this:
+
```shell
$ utop
────────┬─────────────────────────────────────────────────────────────┬─────────
@@ -30,6 +31,7 @@ Lines ending with double semicolons trigger the parsing, type checking, and eval
Code samples beginning with `#` are intended to be copied/pasted into UTop.
For instance, consider the following code snippet:
+
```ocaml
# 2 + 2;;
- : int = 4
@@ -43,7 +45,7 @@ Commands beginning with a hash character `#`, such as `#quit` or `#help`, are no
You're now ready to hack with UTop! If you hit any issue with the toplevel, don't hesitate to [ask on Discuss](https://discuss.ocaml.org/).
-> Note: The double semicolon `;;` is also a valid token in the OCaml syntax outside the toplevel. In OCaml source code, it is a [no-op](https://en.wikipedia.org/wiki/NOP_(code)), i.e., it does not trigger any behaviour, so it is ignored by the compiler. If your intention is to compile or interpret files as scripts, double semicolons can and should be avoided when writing in OCaml. Leaving them does not raise errors, but they are useless. The compiler tolerates them to allow copy-paste from UTop to a file without having to remove them.
+> Note: The double semicolon `;;` is also a valid token in the OCaml syntax outside the toplevel. In OCaml source code, it is a [no-op](https://en.wikipedia.org/wiki/NOP_(code)), i.e., it does not trigger any behaviour, so it is ignored by the compiler. If your intention is to compile or interpret files as scripts, double semicolons can and should be avoided when writing in OCaml. Leaving them does not raise errors, but they are useless. The compiler tolerates them to allow copy-paste from UTop to a file without having to remove them.
## Using Packages in UTop
@@ -66,6 +68,7 @@ Error: Unbound module Str
# Str.quote {|"hello"|};;
- : string = "\"hello\""
```
+
**Tip**: UTop knows about the available libraries and completion works. Outside `utop` you can use `ocamlfind list` to display the complete list of libraries. Note that opam package may bundle several libraries and libraries may bundle several modules.
### Using a Pre-Processor Extension (PPX) in UTop
diff --git a/data/tutorials/getting-started/2_02_opam_switch.md b/data/tutorials/getting-started/2_02_opam_switch.md
index 592e6200c1..f95e683d52 100644
--- a/data/tutorials/getting-started/2_02_opam_switch.md
+++ b/data/tutorials/getting-started/2_02_opam_switch.md
@@ -6,13 +6,14 @@ description: |
category: "Tooling"
---
-OCaml's package manager, opam, introduces the concept of a _switch_, which is an isolated OCaml environment. These switches often cause confusion amongst OCaml newcomers, so this document aims to provide a better understanding of opam switches and their usage for managing dependencies and project-specific configurations.
+OCaml's package manager, opam, introduces the concept of a _switch_, which is an isolated OCaml environment. These switches often cause confusion amongst OCaml newcomers, so this document aims to provide a better understanding of opam switches and their usage for managing dependencies and project-specific configurations.
Opam is designed to manage multiple concurrent installation prefixes called "switches." Similar to Python's `virtualenv`, an opam switch is a tool that creates isolated environments. They are independent of each other and have their own set of installed packages, repositories, and configuration options. Switches also have their own OCaml compiler, libraries, and binaries. This enables you to have multiple compiler versions available at once.
## Listing Switches
The command below will display the opam switches that are configured on your system. After completing installation of OCaml, such as outlined in [Installing OCaml](/docs/installing-ocaml), a single switch called `default` will have been created. At that point, listing the switches will only show that switch.
+
```shell
$ opam switch list
# switch compiler description
@@ -33,7 +34,7 @@ Next, **activate** your new switch. This will set it as the currently selected s
```
opam switch my_project
-```
+```
Replace `my_project` with the name of your new switch.
@@ -42,13 +43,14 @@ Replace `my_project` with the name of your new switch.
```
opam switch
```
+
If the output is the name of your new switch, you've successfully activated it! Now you can use it for your OCaml projects and install OCaml packages, libraries, and dependencies specific to this switch without affecting other switches or the system-wide OCaml environment.
## Types of Switches
### Global Switches
-Global switches are often used for system-wide OCaml installations and are not tied to a particular project or directory. A switch is created and configured at the system level and is typically used to manage OCaml and its ecosystem on a global scale.
+Global switches are often used for system-wide OCaml installations and are not tied to a particular project or directory. A switch is created and configured at the system level and is typically used to manage OCaml and its ecosystem on a global scale.
When creating an opam switch, it's global by default unless otherwise configured. You can also explicitly select a global switch by using the opam switch command with the `--global` flag.
@@ -78,6 +80,4 @@ Most package-related commands in opam operate within the context of a selected s
---
-> Learn more details and uses of opam switches in the [opam manual's File Hierarchies page](https://opam.ocaml.org/doc/Manual.html) and its [page dedicated to switches](https://opam.ocaml.org/doc/man/opam-switch.html).
-
-
+> Learn more details and uses of opam switches in the [opam manual's File Hierarchies page](https://opam.ocaml.org/doc/Manual.html) and its [page dedicated to switches](https://opam.ocaml.org/doc/man/opam-switch.html).
diff --git a/data/tutorials/getting-started/3_01_ocaml_on_windows.md b/data/tutorials/getting-started/3_01_ocaml_on_windows.md
index 659237255c..9823a3582d 100644
--- a/data/tutorials/getting-started/3_01_ocaml_on_windows.md
+++ b/data/tutorials/getting-started/3_01_ocaml_on_windows.md
@@ -51,10 +51,10 @@ $ opam --version
Once opam is installed, run the `opam init` command to set up your opam environment.
You will notice that the repository information fetching stage takes a while to
-complete. This is normal (for the moment), so we advise our users to get
+complete. This is normal (for the moment), so we advise our users to get
themselves their favourite hot beverage while it runs.
-opam requires a Unix-like environment to function. By default,
+opam requires a Unix-like environment to function. By default,
opam relies on Cygwin and is also compatible with MSYS2.
At *init-time*, opam scans your machine for available Unix environments and
@@ -64,10 +64,9 @@ by opam. This cuts down possible interferences from other tools
that interact with such environments. Think of it as a
sandboxed environment.
-
Opam's default behavior when initialising is to install a fresh `switch` as
well as an OCaml compiler of version `> 4.05`. By default, opam chooses `mingw` as
-a C compiler when creating switches, but know that you can choose to install an
+a C compiler when creating switches, but know that you can choose to install an
alternative instead, like `msvc`, with the following command:
```
@@ -77,13 +76,17 @@ opam install system-msvc
After `opam init` completes, run the following command to update your environment:
On CMD:
+
```
> for /f "tokens=*" %i in ('opam env --switch=default') do @%i
```
+
On PowerShell:
+
```
> (& opam env --switch=default) -split '\\r?\\n' | ForEach-Object { Invoke-Expression $_ }
```
+
Opam will display the shell update command each time it is needed.
You can verify your installation with
@@ -108,7 +111,7 @@ You should now have a functioning OCaml environment ready for development. If yo
### WSL2
-If you only need to _run_ OCaml programs on a Windows machine, the simplest solution is to use the Windows Subsystem for Linux 2 (WSL2). WSL2 is a feature that allows Linux programs to run directly on Windows. WSL2 is substantially easier and faster to use than WSL1. Microsoft has comprehensive installation steps for [setting up WSL2](https://docs.microsoft.com/en-us/windows/wsl/install-win10).
+If you only need to *run* OCaml programs on a Windows machine, the simplest solution is to use the Windows Subsystem for Linux 2 (WSL2). WSL2 is a feature that allows Linux programs to run directly on Windows. WSL2 is substantially easier and faster to use than WSL1. Microsoft has comprehensive installation steps for [setting up WSL2](https://docs.microsoft.com/en-us/windows/wsl/install-win10).
After you have installed WSL2 and chosen one Linux distribution (we suggest [Ubuntu LTS](https://apps.microsoft.com/store/detail/ubuntu/9PDXGNCFSCZV?hl=en-us&gl=US)), you can follow the
[Installing OCaml: Installation for Linux and macOS](/docs/installing-ocaml) steps.
@@ -124,6 +127,7 @@ easier way to get a working Windows environment on your machine.
Diskuv OCaml ("DKML") is an OCaml distribution that supports software development in pure OCaml.
The distribution is unique because of its:
+
* full compatibility with OCaml standards like opam, Dune, and OCamlFind.
* focus on "native" development (desktop software, mobile apps, and embedded software) through support for the standard native compilers,
like Visual Studio and Xcode.
@@ -194,7 +198,7 @@ that covers getting WSL2 and Visual Studio Code connected.
system using opam:
```console
-$ opam install merlin
+opam install merlin
```
The installation procedure will print instructions on how to link Merlin with
diff --git a/data/tutorials/getting-started/3_02_arm_fix.md b/data/tutorials/getting-started/3_02_arm_fix.md
index 15d29bf76c..42b0755407 100644
--- a/data/tutorials/getting-started/3_02_arm_fix.md
+++ b/data/tutorials/getting-started/3_02_arm_fix.md
@@ -11,17 +11,18 @@ Since [Homebrew has changed](https://github.com/Homebrew/brew/issues/9177) the w
Before we get started, let's check where Homebrew is installed. We can do this by running this in the CLI:
```shell
-$ where brew
+where brew
```
-If the response is `/usr/local/bin/brew`, we'll need to make the changes. It needs to be `/opt/homebrew/bin/brew`.
+
+If the response is `/usr/local/bin/brew`, we'll need to make the changes. It needs to be `/opt/homebrew/bin/brew`.
### Install CLT
-First, ensure the Command Line Tools (CLT) are installed by running
+First, ensure the Command Line Tools (CLT) are installed by running
```shell
$ ls /Library/Developer/CommandLineTools
-Library SDKs usr
+Library SDKs usr
```
If they're not installed, let's install them now. You don't have to install all of XCode; you can install just the CLT by [downloading them directly from Apple's Developer](https://developer.apple.com/download/all/). Look for a non-beta version for stability, like "Command Line Tools for XCode 14.3.1"
@@ -33,14 +34,14 @@ Next, it's necessary to disable Rosetta if you have it installed. This [Apple Su
1. Uninstall Homebrew by running the following:
```shell
-$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall.sh)"
-$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/uninstall.sh)"
+/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall.sh)"
+/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/uninstall.sh)"
```
2. Reinstall Homebrew:
```Shell
-$ brew install /Users/tarides/Library/Caches/Homebrew/downloads/9e6d2a225119ad88cde6474d39696e66e4f87dc4a4d101243b91986843df691e--libev--4.33.arm64_monterey.bottle.tar.gz
+brew install /Users/tarides/Library/Caches/Homebrew/downloads/9e6d2a225119ad88cde6474d39696e66e4f87dc4a4d101243b91986843df691e--libev--4.33.arm64_monterey.bottle.tar.gz
```
3. Check to see if Homebrew is in the correct location now. It should return what's shown below:
diff --git a/data/tutorials/getting-started/3_03_ocaml_playground.md b/data/tutorials/getting-started/3_03_ocaml_playground.md
index 7f52cc9728..eaeaddb461 100644
--- a/data/tutorials/getting-started/3_03_ocaml_playground.md
+++ b/data/tutorials/getting-started/3_03_ocaml_playground.md
@@ -7,7 +7,7 @@ description: |
category: "Resources"
---
-Welcome to OCaml's in-browser playground!
+Welcome to OCaml's in-browser playground!
The [OCaml Playground](https://ocaml.org/play) is made to make it easier for users, especially beginners, to get started with OCaml without worrying about installing anything. Everything is ready to use once you open it.
@@ -38,6 +38,7 @@ Let's start with something simple. Type the following on your editor panel and c
```
2+3
```
+
You should see the following output.
`- : int = 5`
@@ -47,6 +48,7 @@ Now, clear the output and also delete the things on the editor panel. Let's try
```
"OCaml is amazing"
```
+
You should see the following output.
`- : string = "OCaml is amazing"`
@@ -72,6 +74,7 @@ let () =
let res = fib_par n num_domains in
Printf.printf "fib(%d) = %d\n" n res
```
+
The output will be the following.
```
@@ -82,6 +85,7 @@ val n : int = 20
val fib : int -> int =
val fib_par : int -> int -> int =
```
+
## Autocomplete
The playground also supports code completion. It helps users by suggesting and completing their input based on the context.
@@ -106,4 +110,4 @@ In contrast, when you separate these expressions with a `;;`, [like this](/play#
## Bottom Line
-Congratulations! You have made it to the end. Hopefully, by now, you have a better idea how to use the [OCaml Playground](/play). Use this to practice the OCaml code and have fun. Happy Hacking!
\ No newline at end of file
+Congratulations! You have made it to the end. Hopefully, by now, you have a better idea how to use the [OCaml Playground](/play). Use this to practice the OCaml code and have fun. Happy Hacking!
diff --git a/data/tutorials/guides/0tt_00_formatting_text.md b/data/tutorials/guides/0tt_00_formatting_text.md
index ccf6f6404b..083563283d 100644
--- a/data/tutorials/guides/0tt_00_formatting_text.md
+++ b/data/tutorials/guides/0tt_00_formatting_text.md
@@ -15,6 +15,7 @@ intended to break lines in a nice way (let's say “automatically when it
is necessary”).
## Principles
+
Line breaking is based on three concepts:
* **boxes**: a box is a logical pretty-printing unit, which defines a
@@ -29,19 +30,20 @@ Line breaking is based on three concepts:
* **Indentation rules**: When a line break occurs, the pretty-printing
engines fixes the indentation (or amount of leading spaces) of the
new line using indentation rules, as follows:
- * A box can state the extra indentation of every new line opened
+ * A box can state the extra indentation of every new line opened
in its scope. This extra indentation is named **box breaking
indentation**.
- * A break hint can also set the additional indentation of the new
+ * A break hint can also set the additional indentation of the new
line it may fire. This extra indentation is named **hint
breaking indentation**.
- * If break hint `bh` fires a new line within box `b`, then the
+ * If break hint `bh` fires a new line within box `b`, then the
indentation of the new line is simply the sum of: the current
indentation of box `b` `+` the additional box breaking
indentation, as defined by box `b` `+` the additional hint
breaking indentation, as defined by break hint `bh`.
## Boxes
+
There are 4 types of boxes. (The most often used is the “hov” box type,
so skip the rest at first reading).
@@ -90,6 +92,7 @@ the value of the break that is explained below):
```text
--b--b--
```
+
But "---b---b---" that cannot fit on the line is written
```text
@@ -105,12 +108,14 @@ the value of the break that is explained below):
```text
--b--b--
```
+
But if "---b---b---" cannot fit on the line, it is written as
```text
---b---b
---
```
+
The first break hint does not lead to a new line, since there is
enough room on the line. The second one leads to a new line since
there is no more room to print the material following it. If the
@@ -123,8 +128,8 @@ the value of the break that is explained below):
---
```
-
## Printing Spaces
+
Break hints are also used to output spaces (if the line is not split
when the break is encountered, otherwise the new line indicates properly
the separation between printing items). You output a break hint using
@@ -153,6 +158,7 @@ For instance, if b is `break 1 0` in the output "--b--b--", we get
```text
-- -- --
```
+
or, according to the remaining room on the line:
```text
@@ -169,8 +175,8 @@ instead. (For instance `print_space ()` that is a convenient
abbreviation for `print_break 1 0` and outputs a single space or break
the line.)
-
## Indentation of New Lines
+
The user gets 2 ways to fix the indentation of new lines:
* **when defining the box**: when you open a box, you can fix the
@@ -183,12 +189,14 @@ The user gets 2 ways to fix the indentation of new lines:
---[--b--b
--b--
```
+
with `open_hovbox 2`, we get
```text
---[--b--b
--b--
```
+
Note: the `[` sign in the display is not visible on the screen, it
is just there to materialise the aperture of the pretty-printing
box. Last “screen” stands for:
@@ -214,8 +222,8 @@ The user gets 2 ways to fix the indentation of new lines:
--
```
-
## Refinement on “hov” Boxes
+
### Packing and Structural “hov” Boxes
The “hov” box type is refined into two categories.
@@ -231,6 +239,7 @@ The “hov” box type is refined into two categories.
to new lines even if there is enough room on the current line.
### Differences Between a Packing and a Structural “hov” Box
+
The difference between a packing and a structural “hov” box is shown by
a routine that closes boxes and parentheses at the end of printing: with
packing boxes, the closure of boxes and parentheses do not lead to new
@@ -245,6 +254,7 @@ box (open_hovbox), `[(---[(----[(---b)]b)]b)]` is printed as follows:
(----
(---)))
```
+
If we replace the packing boxes by structural boxes (open_box), each
break hint that precedes a closing parenthesis can show the boxes
structure, if it leads to a new line; hence `[(---[(----[(---b)]b)]b)]`
@@ -277,7 +287,7 @@ When writing a pretty-printing routine, follow these simple rules:
definition. You will probably treat the first three spaces as
“unbreakable spaces” and write them directly in the string constants
for keywords, and print `"let rec "` before the identifier, and
- similarly write ` =` to get an unbreakable space after the
+ similarly write `=` to get an unbreakable space after the
identifier; in contrast, the space after the `=` sign is certainly a
break hint, since breaking the line after `=` is a usual (and
elegant) way to indent the expression part of a definition. In
@@ -301,6 +311,7 @@ When writing a pretty-printing routine, follow these simple rules:
input.)
## Printing to `stdout`: Using `printf`
+
The `format` module provides a general printing facility “à la”
`printf`. In addition to the usual conversion facility provided by
`printf`, you can write pretty-printing indications directly inside the
@@ -350,6 +361,7 @@ type lambda =
| Var of string
| Apply of lambda * lambda
```
+
I use the format library to print the lambda-terms:
```ocaml
@@ -373,6 +385,7 @@ and print_lambda = function
close_box()
| e -> print_app e
```
+
In Caml Light, replace the first line by:
diff --git a/data/tutorials/guides/0tt_02_file_manipulation.md b/data/tutorials/guides/0tt_02_file_manipulation.md
index 78605a317c..e667a2e7f0 100644
--- a/data/tutorials/guides/0tt_02_file_manipulation.md
+++ b/data/tutorials/guides/0tt_02_file_manipulation.md
@@ -14,6 +14,7 @@ Official documentation for the modules of interest:
the core library including the initially opened module Stdlib and Printf.
## Buffered Channels
+
The normal way of opening a file in OCaml returns a **channel**. There
are two kinds of channels:
@@ -21,6 +22,7 @@ are two kinds of channels:
* channels that read from a file: type `in_channel`
### Writing
+
For writing into a file, you would do this:
1. Open the file to obtain an `out_channel`
@@ -36,6 +38,7 @@ Commonly used functions: `open_out`, `open_out_bin`, `flush`,
Standard `out_channel`s: `stdout`, `stderr`
### Reading
+
For reading data from a file you would do this:
1. Open the file to obtain an `in_channel`
@@ -52,6 +55,7 @@ Commonly used functions: `open_in`, `open_in_bin`, `close_in`,
Standard `in_channel`: `stdin`
### Seeking
+
Whenever you write or read something to or from a channel, the current
position changes to the next character after what you just wrote or
read. Occasionally, you may want to skip to a particular position in the
@@ -59,6 +63,7 @@ file, or restart reading from the beginning. This is possible for
channels that point to regular files, use `seek_in` or `seek_out`.
### Gotchas
+
* Don't forget to flush your `out_channel`s if you want to actually
write something. This is particularly important if you are writing
to non-files such as the standard output (`stdout`) or a socket.
diff --git a/data/tutorials/guides/0tt_03_calling_c_libraries.md b/data/tutorials/guides/0tt_03_calling_c_libraries.md
index 531e9b0f7b..e63629400a 100644
--- a/data/tutorials/guides/0tt_03_calling_c_libraries.md
+++ b/data/tutorials/guides/0tt_03_calling_c_libraries.md
@@ -7,6 +7,7 @@ category: "Tutorials"
---
## MiniGtk
+
While the structure of lablgtk outlined in [Introduction to
Gtk](https://v2.ocaml.org/learn/tutorials/introduction_to_gtk.html) seems perhaps
over-complex, it's worth considering exactly why the author chose two
@@ -43,6 +44,7 @@ win#add lbl;;
let () =
Gtk.main ()
```
+
I defined a single abstract type to cover all `GtkObject`s (and
"subclasses" of this C structure). In the `Gtk` module you'll find this
type definition:
@@ -82,6 +84,7 @@ gtk_label_new_c (value str)
gtk_label_new (String_val (str)))));
}
```
+
Before explaining this function further, I'm going to take a step back
and look at the hierarchy of our Gtk classes. I've chosen to reflect the
actual Gtk widget hierarchy as closely as possible. All Gtk widgets are
@@ -141,6 +144,7 @@ class virtual widget ?show obj =
initializer if show <> Some false then self#show
end
```
+
This class is considerably more complex. Let's look at the
initialization code first:
@@ -266,6 +270,7 @@ class label ~text
may (gtk_label_set_line_wrap obj) line_wrap
end
```
+
Although this class is bigger than the ones we've looked at up til now,
it's really more of the same idea, *except* that this class isn't
virtual. You can create instances of this class which means it finally
@@ -280,12 +285,14 @@ class label ~text ... () =
inherit misc ... obj
end
```
+
(Pop quiz: what happens if we need to define a class which is both a
base class from which other classes can be derived, and is also a
non-virtual class of which the user should be allowed to create
instances?)
#### Wrapping Calls to C Libraries
+
Now we'll look in more detail at actually wrapping up calls to C library
functions. Here's a simple example:
@@ -303,6 +310,7 @@ gtk_label_set_text_c (value obj, value str)
CAMLreturn (Val_unit);
}
```
+
Comparing the OCaml prototype for the external function call (in the
comment) with the definition of the function we can see two things:
@@ -374,6 +382,7 @@ caml__roots_obj.ntables = 2;
caml__roots_obj.tables [0] = &obj;
caml__roots_obj.tables [1] = &str;
```
+
And for `CAMLreturn (foo)`:
```C
diff --git a/data/tutorials/guides/0tt_04_calling_fortran_libraries.md b/data/tutorials/guides/0tt_04_calling_fortran_libraries.md
index 25173a1be2..39f2e11cf3 100644
--- a/data/tutorials/guides/0tt_04_calling_fortran_libraries.md
+++ b/data/tutorials/guides/0tt_04_calling_fortran_libraries.md
@@ -31,6 +31,7 @@ these have been tested with the GNU fortran 90 compiler (gfort) and will
not be until it has proven itself through some time.
### Step 1: Compile the Fortran Routine
+
Where C/C++ have only one category of subroutine (the function), Fortran
has two: the function and the subroutine. The function is the equivalent
to a non-void C function in that it takes parameters and always returns
@@ -65,7 +66,7 @@ Reals are equivalent to C doubles and integers are equivalent to C
longs. In addition, Fortran passes everything by reference so the
corresponding C prototype for our gtd6 function is
-` int gtd6_(integer *iyd, real* sec, real* alt, real* glat, real* glong, real* dens, real* temp);`
+`int gtd6_(integer *iyd, real* sec, real* alt, real* glat, real* glong, real* dens, real* temp);`
Note that its up to the caller to know that `dens` and `temp` are
actually arrays. Failure to pass an array will cause a segmentation
@@ -73,6 +74,7 @@ violation since the gtd6_ function is using them as arrays (yet another
reason OCaml shines).
### Step 2: Create the C Wrapper
+
Because OCaml's foreign function interface is C based, it is necessary
to create a C wrapper. To avoid difficulties in passing back arrays of
values, we are going to simply create a function that will return the
@@ -93,6 +95,7 @@ CAMLprim value gtd6_t (value iydV, value secVal, value altVal, value latVal, val
CAMLreturn( caml_copy_double( t[1] ) );
}
```
+
A few points of interest
1. The file must include the OCaml header files `alloc.h`, `memory.h`,
@@ -107,14 +110,15 @@ A few points of interest
macro to create a new value containing the return value and to
return it respectively.
-### Step 3: Compile the Shared Library.
+### Step 3: Compile the Shared Library
+
Now having the two source files funcs.f and wrapper.c we need to create
a shared library that can be loaded by OCaml. Its easier to do this as a
multistep process, so here are the commands:
`prompt> g77 -c funcs.f`
-`prompt> cc -I -c wrapper.c `
+`prompt> cc -I -c wrapper.c`
`prompt> cc -shared -o wrapper.so wrapper.o funcs.o -lg2c`
@@ -124,6 +128,7 @@ required to provide the implementations of the built in fortran
functions that are used.
### Step 4: Now to OCaml
+
Now in an OCaml file (gtd6.ml) we have to define the external reference
to the function and a function to call it.
@@ -135,6 +140,7 @@ let () =
print_double (temp 1 2.0 3.0 4.0 5.0);
print_newline ()
```
+
This tells OCaml that the temp function takes 5 parameters and returns a
single floating point and calls the C function gtd6_t.
@@ -142,7 +148,8 @@ At this point, the steps that are given are to compile this into
bytecode. I don't yet have much experience compiling to native so I'll
let some else help out (or wait until I learn how to do it).
-```
-prompt> ocamlc -c gtd6.ml prompt> ocamlc -o test gtd6.cmo wrapper.so
-```
+`prompt> ocamlc -c gtd6.ml`
+
+`prompt> ocamlc -o test gtd6.cmo wrapper.so`
+
And voila, we've called the fortran function from OCaml.
diff --git a/data/tutorials/guides/1wf_00_using_the_ocaml_compiler_toolchain.md b/data/tutorials/guides/1wf_00_using_the_ocaml_compiler_toolchain.md
index 1ecc55c889..6b544822fb 100644
--- a/data/tutorials/guides/1wf_00_using_the_ocaml_compiler_toolchain.md
+++ b/data/tutorials/guides/1wf_00_using_the_ocaml_compiler_toolchain.md
@@ -41,9 +41,10 @@ But if you want to try `ocamlc`, you can go ahead and try the following.
Create a new directory named `hello` and navigate to the directory:
```shell
-$ mkdir hello
-$ cd hello
+mkdir hello
+cd hello
```
+
Next, create a file named `hello.ml` and add the following code with your favorite text editor:
```shell
@@ -53,12 +54,12 @@ let () = print_endline "Hello OCaml!"
Now, we are ready to run the code. Save the file and return to the command line. Let's compile the code:
```dune
-$ ocamlc -o hello hello.ml
+ocamlc -o hello hello.ml
```
-The `-o hello` option tells the compiler to name the output executable as `hello`. The executable `hello` contains compiled OCaml bytecode. In addition, two other files are produced, `hello.cmi` and `hello.cmo`.
+The `-o hello` option tells the compiler to name the output executable as `hello`. The executable `hello` contains compiled OCaml bytecode. In addition, two other files are produced, `hello.cmi` and `hello.cmo`.
-`hello.cmi` contains compiled interface information for OCaml modules. An interface file includes type information and module signatures but doesn't contain the actual code.
+`hello.cmi` contains compiled interface information for OCaml modules. An interface file includes type information and module signatures but doesn't contain the actual code.
`hello.cmo` contains the compiled bytecode for OCaml modules. Bytecode is an intermediate representation of the code that is executed by the OCaml interpreter or runtime system.
@@ -70,6 +71,7 @@ Now let's run the executable and see what happens:
$ ./hello
Hello OCaml!
```
+
Voilà! It says, `Hello OCaml!`.
We can change the string or add more content, save the file, recompile, and rerun.
@@ -215,13 +217,13 @@ structure, build, and run dune projects.
### Bytecode Using Dune
-Dune is a build system for OCaml projects, and it allows you to configure different modes for building your executables. We will use `(modes byte exe)` stanza in the `dune` file, which will produce both bytecode (interpreted) and native executable versions of the OCaml program.
+Dune is a build system for OCaml projects, and it allows you to configure different modes for building your executables. We will use `(modes byte exe)` stanza in the `dune` file, which will produce both bytecode (interpreted) and native executable versions of the OCaml program.
Let's create an example project named `myproject`.
```dune
-$ mkdir myproject
-$ cd myproject
+mkdir myproject
+cd myproject
```
Create a `dune-project` file, add the following content.
@@ -253,7 +255,7 @@ let () = print_endline "Hello Dune!"
Finally, we compile and execute it.
```shell
-$ dune build main.bc
+dune build main.bc
```
The `.bc` stands for generic bytecode file, and it can be an executable or library.
@@ -262,16 +264,18 @@ The `.bc` stands for generic bytecode file, and it can be an executable or libra
$ dune exec ./main.bc
Hello Dune!
```
+
We can also do that with `.exe`.
```shell
-$ dune build main.exe
+dune build main.exe
```
```shell
$ dune exec ./main.exe
Hello Dune!
```
+
## Other Build Systems
- [OMake](https://github.com/ocaml-omake/omake) Another OCaml build system.
diff --git a/data/tutorials/guides/1wf_01_debugging.md b/data/tutorials/guides/1wf_01_debugging.md
index 29de098f72..87ba99cd55 100644
--- a/data/tutorials/guides/1wf_01_debugging.md
+++ b/data/tutorials/guides/1wf_01_debugging.md
@@ -17,7 +17,6 @@ This tutorial presents four techniques for debugging OCaml programs:
OCaml program
* [Using Thread Sanitizer to detect a data race](#detecting-a-data-race-with-thread-sanitizer) in an OCaml 5 program
-
## Tracing Functions Calls in the Toplevel
The simplest way to debug programs in the toplevel is to follow the function
@@ -161,13 +160,38 @@ purpose formats are more suited to get the relevant information, than what can
be output automatically by the generic pretty-printer used by the trace
mechanism.
+Compiler builtins help display useful debugging messages. They indicate a location in the program's source.
+For example,
+
+```ocaml
+match Message.unpack response with
+| Some y -> y
+| None -> (Printf.eprintf "Invalid message at %s" __LOC__; raise Invalid_argument)
+```
+
+At compile time, the `__LOC__` builtin is substituted with its location in the program, described as a string `"File %S, line %d, characters %d-%d"`. File name, line number, start character and end character are also available through the `__POS__` builtin:
+
+```ocaml
+match Message.unpack response with
+| Some y -> y
+| None ->
+ let fname, lnum, _cstart, _cend = __POS__ in
+ Printf.printf "At line %d in file %s, an incorrect response was passed to Message.unpack"
+ lnum fname;
+ flush stdout; raise Invalid_argument
+```
+
+Compiler builtins are described in the
+[standard library](https://ocaml.org/manual/5.2/api/Stdlib.html#1_Debugging) documentation.
+
## The OCaml Debugger
We now give a quick tutorial for the OCaml debugger (`ocamldebug`). Before
starting, please note that
-- `ocamldebug` runs on `ocamlc` bytecode programs (it does not work on
+
+* `ocamldebug` runs on `ocamlc` bytecode programs (it does not work on
native code executables), and
-- it does not work under native Windows ports of OCaml (but it runs
+* it does not work under native Windows ports of OCaml (but it runs
under the Cygwin port).
### Launching the Debugger
@@ -185,6 +209,7 @@ let () =
add_address "IRIA" "Rocquencourt";;
print_string (find_address "INRIA"); print_newline ();;
```
+
```mdx-error
val l : (string * string) list ref = {contents = [("IRIA", "Rocquencourt")]}
val find_address : string -> string =
@@ -266,7 +291,6 @@ let rec assoc x = function
The function that calls it is in module `Uncaught`, file `uncaught.ml`
line 8, char 38:
-
```ocaml
print_string (find_address "INRIA"); print_newline ();;
@@ -372,7 +396,6 @@ From this back trace it should be clear that we receive a `Not_found`
exception in `List.assoc` from the `Stdlib` when calling
`find_address` on line 8.
-
The environment variable `OCAMLRUNPARAM` also works when working on a
program built with `dune`:
@@ -383,6 +406,7 @@ program built with `dune`:
(modules uncaught)
)
```
+
```
OCAMLRUNPARAM=b dune exec ./uncaught.exe
Fatal error: exception Not_found
@@ -391,7 +415,6 @@ Called from Dune__exe__Uncaught.find_address in file "uncaught.ml" (inlined), li
Called from Dune__exe__Uncaught in file "uncaught.ml", line 8, characters 15-37
```
-
## Detecting a Data Race with Thread Sanitizer
With the introduction of Multicore parallelism in OCaml 5, comes the
@@ -403,6 +426,7 @@ report these.
To install the TSan mode, create a dedicated TSan switch by running the
following command (here we create a 5.2.0 switch):
+
```
opam switch create 5.2.0+tsan ocaml-variants.5.2.0+options ocaml-option-tsan
```
@@ -414,11 +438,12 @@ Note: TSan is supported on all architectures with a native code
compiler since OCaml 5.2.0.
Troubleshooting:
-- If the above fails during installation of `conf-unwind` with `No
+
+* If the above fails during installation of `conf-unwind` with `No
package 'libunwind' found`, try setting the environment variable
`PKG_CONFIG_PATH` to point to the location of `libunwind.pc`, for
example, `PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig`
-- If the above fails with an error along the lines of
+* If the above fails with an error along the lines of
`FATAL: ThreadSanitizer: unexpected memory mapping 0x61a1a94b2000-0x61a1a94ca000`,
this is [a known issue with older versions of TSan](https://github.com/google/sanitizers/issues/1716)
and can be addressed by reducing ASLR entropy by running
@@ -447,6 +472,7 @@ Next, it spawns two parallel `Domain`s `t1` and `t2` that both update
the field `v.x`.
Here is a corresponding `dune` file:
+
```dune
(executable
(name race)
@@ -456,6 +482,7 @@ Here is a corresponding `dune` file:
If we compile and run the program using `dune` under a regular `5.2.0` switch the
program appears to work:
+
```
$ opam exec -- dune build ./race.exe
$ opam exec -- dune exec ./race.exe
@@ -464,6 +491,7 @@ v.x is 11
However, if we compile and run the program with Dune from the new
`5.2.0+tsan` switch TSan warns us of a data race:
+
```
$ opam switch 5.2.0+tsan
$ opam exec -- dune build ./race.exe
@@ -500,9 +528,10 @@ this required no change to our `dune` file.
The TSan report warns of a data race between two uncoordinated writes
happening in parallel and prints a back trace for both:
-- The first back trace reports a write at `race.ml` in line
+
+* The first back trace reports a write at `race.ml` in line
6 of `thread T4` and
-- the second back trace reports a previous write at
+* the second back trace reports a previous write at
`race.ml` in line 5 of `thread T1`
Looking again at our program, we realize that these two writes are in
@@ -524,6 +553,7 @@ let () =
If we recompile and run our program with this change, it now completes
without TSan warnings:
+
```
$ opam exec -- dune build ./race.exe
$ opam exec -- dune exec ./race.exe
@@ -534,6 +564,7 @@ The TSan instrumentation benefits from compiling programs with debug
information, which happens by default under `dune`. To manually invoke
the `ocamlopt` compiler under our `5.2.0+tsan` switch it is thus
suffient to pass it the `-g` flag:
+
```
-$ ocamlopt -g -o race.exe -I +unix unix.cmxa race.ml
+ocamlopt -g -o race.exe -I +unix unix.cmxa race.ml
```
diff --git a/data/tutorials/guides/1wf_02_error_handling.md b/data/tutorials/guides/1wf_02_error_handling.md
index 1b031824a7..3b6ee3e833 100644
--- a/data/tutorials/guides/1wf_02_error_handling.md
+++ b/data/tutorials/guides/1wf_02_error_handling.md
@@ -34,6 +34,7 @@ errors aren't ignored. This has been the cause of many bugs, some with dire
consequences. This is not the proper way to deal with errors in OCaml.
There are three major ways to make it impossible to ignore errors in OCaml:
+
1. Exceptions
1. **`option`** values
1. **`result`** values
@@ -101,6 +102,7 @@ Stack overflow during evaluation (looping recursion?).
```
Although the last one doesn't look as an exception, it actually is.
+
```ocaml
# try loop 42 with Stack_overflow -> [];;
- : int list = []
@@ -117,6 +119,7 @@ are intended to be raised by user-written functions:
Functions are provided by the standard library to raise `Invalid_argument` and
`Failure` using a string parameter:
+
```ocaml
val invalid_arg : string -> 'a
(** @raise Invalid_argument *)
@@ -126,6 +129,7 @@ val failwith : string -> 'a
When implementing a software component which exposes functions raising
exceptions, a design decision must be made:
+
* Use the preexisting exceptions
* Raise custom exceptions
@@ -140,9 +144,11 @@ desirable for errors that must be handled at the client level.
Because handling exceptions interrupts normal control flow, using them can
complicate some tasks requiring strictly ordered and coupled processes. For
these scenarios, the `Fun` module of the standard library provides:
+
```ocaml
val protect : finally:(unit -> unit) -> (unit -> 'a) -> 'a
```
+
This function is meant to be used when something _always_ needs to be done
_after_ a computation is complete, whether it succeeds or fails. The unlabeled
function argument is called first, then the function passed as the
@@ -179,6 +185,7 @@ val head_channel : in_channel -> int -> string list =
Fun.protect ~finally work;;
val head_file : string -> int -> string list =
```
+
When `head_file` is called, it opens a file descriptor, defines `finally` and
`work`, then `Fun.protect ~finally work` performs two computations in order:
`work ()` and then `finally ()`, and has the same result as `work ()`, either
@@ -188,7 +195,7 @@ closed after use.
### Asynchronous Exceptions
Some exceptions don't arise because something attempted by the program failed,
-but rather because an external factor is impeding its execution. Those
+but rather because an external factor is impeding its execution. Those
exceptions are called asynchronous. These include:
* `Out_of_memory`
@@ -301,6 +308,7 @@ Segmentation fault (core dumped)
### Language Bugs
When a crash isn't coming from:
+
* A limitation of the native code compiler
* An inherently unsafe function such as are found in modules `Marshal` and `Obj`
@@ -312,12 +320,13 @@ it may be a language bug. It happens. Here is what to do when this is suspected:
1. File an issue in the [OCaml Bug Tracker in
GitHub](https://github.com/ocaml/ocaml/issues)
-Here is an example of such a bug: https://github.com/ocaml/ocaml/issues/7241
+Here is an example of such a bug:
### Safe vs. Unsafe Functions
Uncaught exceptions cause runtime crashes. Therefore, there is a tendency to use
the following terminology:
+
* Function raising exceptions: Unsafe
* Function handling errors in data: Safe
@@ -335,6 +344,7 @@ is of type `int option` - or the absence of data due to any error as `None`.
Using **`option`** it is possible to write functions that return `None` instead of
throwing an exception. Here are two examples of such functions:
+
```ocaml
# let div_opt m n =
try Some (m / n) with
@@ -346,6 +356,7 @@ val div_opt : int -> int -> int option =
Not_found -> None;;
val find_opt : ('a -> bool) -> 'a list -> 'a option =
```
+
We can try those functions:
```ocaml
@@ -375,17 +386,20 @@ behavior where one may raise an exception and the other returns an option. In
the above examples, the convention of the standard library is used: adding an
`_opt` suffix to the name of the version of the function that returns an option
instead of raising an exception.
+
```ocaml
val find: ('a -> bool) -> 'a list -> 'a
(** @raise Not_found *)
val find_opt: ('a -> bool) -> 'a list -> 'a option
```
+
This is extracted from the `List` module of the standard library.
However, some projects tend to avoid or reduce the usage of exceptions. In such
a context, the opposite convention is relatively common: the version of the
function that raises exceptions is suffixed with `_exn`. Using the same
functions, that would give the specification:
+
```ocaml
val find_exn: ('a -> bool) -> 'a list -> 'a
(** @raise Not_found *)
@@ -410,17 +424,20 @@ Error: This expression has type 'a option
In order to combine option values with other values, conversion functions are
needed. Here are the functions provided by the **`option`** module to extract the
data contained in an option:
+
```ocaml
val get : 'a t -> 'a
val value : 'a t -> default:'a -> 'a
val fold : none:'a -> some:('b -> 'a) -> 'b t -> 'a
```
+
`get` returns the contents, or raises `Invalid_argument` if applied to `None`.
`value` returns the contents, or its `default` argument if applied to `None`.
`fold` returns its `some` argument applied to the contents of the option, or its
`none` argument if applied to `None`.
As a remark, observe that `value` can be implemented using `fold`:
+
```ocaml
# let value ~default = Option.fold ~none:default ~some:Fun.id;;
val value : default:'a -> 'a option -> 'a =
@@ -431,11 +448,13 @@ val value : default:'a -> 'a option -> 'a =
```
It is also possible to perform pattern matching on option values:
+
```ocaml
match opt with
| None -> ... (* Something *)
| Some x -> ... (* Something else *)
```
+
However, sequencing such expressions leads to deep nesting which is often
considered bad:
@@ -458,6 +477,7 @@ provided as a string. For instance, given the string
example).
Here is a questionable but straightforward implementation using exceptions:
+
```ocaml
# let host email =
let fqdn_pos = String.index email '@' + 1 in
@@ -470,6 +490,7 @@ Here is a questionable but straightforward implementation using exceptions:
if fqdn <> "" then fqdn else raise Not_found;;
val host : string -> string =
```
+
This may fail by raising `Not_found` if the first the call to `String.index`
does, which could happen if there is no `@` character in the input string,
signifying that it's not an email address. However, if the second call to
@@ -525,6 +546,7 @@ If there isn't anything to supply to `f`, `None` is forwarded.
If we don't take arguments order into account, `Option.bind` is almost exactly
the same, except we assume `f` returns an option. Therefore, there is no need to
rewrap its result, since it's already an option value:
+
```ocaml
let bind opt f = match opt with
| Some x -> f x
@@ -534,12 +556,14 @@ let bind opt f = match opt with
`bind` having flipped parameter order with respect to `map` allows its use as a
[binding operator](/manual/bindingops.html), which is a popular extension of
OCaml providing means to create “custom `let`”. Here is how it goes:
+
```ocaml
# let ( let* ) = Option.bind;;
val ( let* ) : 'a option -> ('a -> 'b option) -> 'b option =
```
Using these mechanisms, here is a possible way to rewrite `host_opt`:
+
```ocaml
# let host_opt email =
let* fqdn_pos = Option.map (( + ) 1) (String.index_opt email '@') in
@@ -554,15 +578,16 @@ val host_opt : string -> string option =
This version was picked to illustrate how to use and combine operations on
options allowing users to achieve some balance between understandability and
robustness. A couple of observations:
+
* As in the original `host` function (with exceptions):
- - The calls to `String` functions (`index_opt`, `length`, and `sub`) are the
+ * The calls to `String` functions (`index_opt`, `length`, and `sub`) are the
same and in the same order
- - The same local names are used with the same types
+ * The same local names are used with the same types
* There isn't any remaining indentation or pattern-matching
* Line 1:
- - right-hand side of `=` : `Option.map` allows adding 1 to the result of
+ * right-hand side of `=` : `Option.map` allows adding 1 to the result of
`String.index_opt`, if it didn't fail
- - left-hand side of `=` : the `let*` syntax turns all the rest of the code
+ * left-hand side of `=` : the `let*` syntax turns all the rest of the code
(from line 2 to the end) into the body of an anonymous function which takes
`fqdn_pos` as parameter, and the function `( let* )` is called with `fqdn_pos`
and that anonymous function.
@@ -612,10 +637,13 @@ constructor. Nevertheless, `Result.map` is implemented similarly: on `Error`, it
also behaves like identity.
Here is its type:
+
```ocaml
val map : ('a -> 'b) -> ('a, 'c) result -> ('b, 'c) result
```
+
And here is how it is written:
+
```ocaml
let map f = function
| Ok x -> Ok (f x)
@@ -626,19 +654,23 @@ The **`result`** module has two map functions: the one we've just seen and anoth
one, with the same logic, applied to `Error`
Here is its type:
+
```ocaml
val map_error : ('c -> 'd) -> ('a, 'c) result -> ('a, 'd) result
```
+
And here is how it is written:
+
```ocaml
let map_error f = function
| Ok x -> Ok x
-| Error e -> f e
+| Error e -> Error (f e)
```
The same reasoning applies to `Result.bind`, except there's no `bind_error`.
Using those functions, here is an hypothetical example of code using [Anil
Madhavapeddy's OCaml YAML library](https://github.com/avsm/ocaml-yaml):
+
```ocaml
let file_opt = File.read_opt path in
let file_res = Option.to_result ~none:(`Msg "File not found") file_opt in begin
@@ -650,6 +682,7 @@ end |> Result.map_error (Printf.sprintf "%s, error: %s: " path)
```
Here are the types of the involved functions:
+
```ocaml
val File.read_opt : string -> string option
val Yaml.of_string : string -> (Yaml.value, [`Msg of string]) result
@@ -657,13 +690,13 @@ val Yaml.Util.find : string -> Yaml.value -> (Yaml.value option, [`Msg of string
val Option.to_result : none:'e -> 'a option -> ('a, 'e) result
```
-- `File.read_opt` is supposed to open a file, read its contents and return it as
+* `File.read_opt` is supposed to open a file, read its contents and return it as
a string wrapped in an option, if anything goes wrong `None` is returned.
-- `Yaml.of_string` parses a string and turns it into an ad hoc OCaml type
-- `Yaml.find` recursively searches for a key in a Yaml tree. If found, it returns
+* `Yaml.of_string` parses a string and turns it into an ad hoc OCaml type
+* `Yaml.find` recursively searches for a key in a Yaml tree. If found, it returns
the corresponding data, wrapped in an option
-- `Option.to_result` performs conversion of an **`option`** into a **`result`**.
-- Finally, `let*` stands for `Result.bind`.
+* `Option.to_result` performs conversion of an **`option`** into a **`result`**.
+* Finally, `let*` stands for `Result.bind`.
Since both functions from the `Yaml` module return **`result`** data, it is
easier to write a pipe which processes that type all along. That's why
@@ -677,14 +710,17 @@ into an `Error` and `Result.map_error` will never turn an `Error` into an `Ok`.
On the other hand, functions passed to `Result.bind` are allowed to fail. As
stated before there isn't a `Result.bind_error`. One way to make sense out of
that absence is to consider its type, it would have to be:
+
```ocaml
val Result.bind_error : ('a, 'e) result -> ('e -> ('a, 'f) result) -> ('a, 'f) result
```
+
We would have:
+
* `Result.map_error f (Ok x) = Ok x`
* And either:
- - `Result.map_error f (Error e) = Ok y`
- - `Result.map_error f (Error e) = Error e'`
+ * `Result.map_error f (Error e) = Ok y`
+ * `Result.map_error f (Error e) = Error e'`
This means an error would be turned back into valid data or changed into
another error. This is almost like recovering from an error. However, when
@@ -698,11 +734,12 @@ val recover : ('e -> 'a option) -> ('a, 'e) result -> ('a, 'e) result =
Although any kind of data can be wrapped as a **`result`** `Error`, it is
recommended to use that constructor to carry actual errors, for instance:
-- `exn`, in which case the result type just makes exceptions explicit
-- `string`, where the error case is a message that indicates what failed
-- `string Lazy.t`, a more elaborate form of error message that is only evaluated
+
+* `exn`, in which case the result type just makes exceptions explicit
+* `string`, where the error case is a message that indicates what failed
+* `string Lazy.t`, a more elaborate form of error message that is only evaluated
if printing is required
-- some polymorphic variant, with one case per possible error. This is very
+* some polymorphic variant, with one case per possible error. This is very
accurate (each error can be dealt with explicitly and occurs in the type), but
the use of polymorphic variants sometimes make the code harder to read.
@@ -737,9 +774,11 @@ source code are functionally identical:
```ocaml
bind a (fun x -> b)
```
+
```ocaml
let* x = a in b
```
+
```ocaml
a >>= fun x -> b
```
@@ -750,14 +789,17 @@ calls to `bind` are chained. The following three are also equivalent:
```ocaml
bind a (fun x -> bind b (fun y -> c))
```
+
```ocaml
let* x = a in
let* y = b in
c
```
+
```ocaml
a >>= fun x -> b >>= fun y -> c
```
+
Variables `x` and `y` may appear in `c` in the three cases. The first form isn't
very convenient, as it uses a lot of parentheses. The second one is often
preferred due to its resemblance with regular local definitions. The third
@@ -765,15 +807,19 @@ one is harder to read, as `>>=` associates to the right in order to avoid
parentheses in that precise case, but it's easy to get lost. Nevertheless, it
has some appeal when named functions are used. It looks a bit like good old Unix
pipes:
+
```ocaml
a >>= f >>= g
```
+
looks better than:
+
```ocaml
let* x = a in
let* y = f x in
g y
```
+
Writing `x >>= f` is very close to what is found in functionally tainted
programming languages which have methods and receivers such as Kotlin, Scala,
Go, Rust, Swift, or even modern Java, where it would look like:
@@ -781,6 +827,7 @@ Go, Rust, Swift, or even modern Java, where it would look like:
Here is the same code as presented at the end of the previous section, rewritten
using `Result.bind` as a binary opeator:
+
```ocaml
File.read_opt path
|> Option.to_result ~none:(`Msg "File not found")
@@ -806,13 +853,15 @@ Guidelines](/docs/guidelines) for more details on those matters.
This is done by using the following functions:
-- From **`option`** to `Invalid_argument` exception, use function `Option.get`:
+* From **`option`** to `Invalid_argument` exception, use function `Option.get`:
+
```ocaml
val get : 'a option -> 'a
```
-- From **`result`** to `Invalid_argument` exception, use functions
+* From **`result`** to `Invalid_argument` exception, use functions
`Result.get_ok` and `Result.get_error`:
+
```ocaml
val get_ok : ('a, 'e) result -> 'a
val get_error : ('a, 'e) result -> 'e
@@ -824,11 +873,14 @@ To raise other exceptions, pattern matching and `raise` must be used.
This is done by using the following functions:
-- From **`option`** to **`result`**, use function `Option.to_result`:
+* From **`option`** to **`result`**, use function `Option.to_result`:
+
```ocaml
val to_result : none:'e -> 'a option -> ('a, 'e) result
```
-- From **`result`** to **`option`**, use function `Result.to_option`:
+
+* From **`result`** to **`option`**, use function `Result.to_option`:
+
```ocaml
val to_option : ('a, 'e) result -> 'a option
```
@@ -848,6 +900,7 @@ It would be same for **`result`**, except some data must be provided to the
`Error` constructor.
Some may like to turn this into a higher-order generic function:
+
```ocaml
# let catch f x = try Some (f x) with _ -> None;;
val catch : ('a -> 'b) -> 'a -> 'b option =
@@ -893,9 +946,11 @@ match Sys.os_type with
It is right to use `failwith`, other operating systems aren't supported, but
they are possible. Here is the dual example:
+
```ocaml
function x when true -> () | _ -> assert false
```
+
Here, it wouldn't be correct to use `failwith` because it requires a corrupted
system or the compiler to be bugged for the second code path to be executed.
Breakage of the language semantics qualifies as extraordinary circumstances. It
@@ -917,12 +972,12 @@ fine, so it's a balance.
# External Resources
-- [“Exceptions”](/manual/coreexamples.html#s%3Aexceptions) in ”The OCaml Manual, The Core Language”, chapter 1, section 6, December 2022
-- [Module **`option`**](/manual/api/Option.html) in OCaml Library
-- [Module **`result`**](/manual/api/Result.html) in Ocaml Library
-- [“Error Handling”](https://dev.realworldocaml.org/error-handling.html) in “Real World OCaml”, part 7, Yaron Minsky and Anil Madhavapeddy, 2ⁿᵈ edition, Cambridge University Press, October 2022
-- “Add "finally" function to Pervasives”, Marcello Seri, GitHub PR, [ocaml/ocaml/pull/1855](https://github.com/ocaml/ocaml/pull/1855)
-- “A guide to recover from interrupts”, Guillaume Munch-Maccagnoni, parf the [`memprof-limits`](https://gitlab.com/gadmm/memprof-limits/) documentation
+* [“Exceptions”](/manual/coreexamples.html#s%3Aexceptions) in ”The OCaml Manual, The Core Language”, chapter 1, section 6, December 2022
+* [Module **`option`**](/manual/api/Option.html) in OCaml Library
+* [Module **`result`**](/manual/api/Result.html) in Ocaml Library
+* [“Error Handling”](https://dev.realworldocaml.org/error-handling.html) in “Real World OCaml”, part 7, Yaron Minsky and Anil Madhavapeddy, 2ⁿᵈ edition, Cambridge University Press, October 2022
+* “Add "finally" function to Pervasives”, Marcello Seri, GitHub PR, [ocaml/ocaml/pull/1855](https://github.com/ocaml/ocaml/pull/1855)
+* “A guide to recover from interrupts”, Guillaume Munch-Maccagnoni, parf the [`memprof-limits`](https://gitlab.com/gadmm/memprof-limits/) documentation
```
-$ ocamlcp -p a graphics.cma graphtest.ml -o graphtest
-$ ./graphtest
-$ ocamlprof graphtest.ml
+ocamlcp -p a graphics.cma graphtest.ml -o graphtest
+./graphtest
+ocamlprof graphtest.ml
```
The comments `(* nnn *)` are added by `ocamlprof`, showing how many
@@ -870,7 +902,7 @@ Each sample counts as 0.01 seconds.
### Using `perf` on Linux
-Assuming `perf `is installed and your program is compiled into
+Assuming `perf`is installed and your program is compiled into
native code with `-g` (or ocamlbuild tag `debug`), you just need to type
@@ -892,7 +924,7 @@ macOS ships with a performance monitoring and debugging application called
`Instruments` that comes with a CPU counter, a Time Profiler, and a System
Trace templates.
-Once you launch it and select the template you want, you must start _recording_
+Once you launch it and select the template you want, you must start *recording*
before you launch your application.
As you launch your application, real-time results will appear listed in
@@ -904,6 +936,7 @@ From there, you can click on your program there and dig to see which functions a
taking the longest to execute.
## Summary
+
In summary here are some tips for getting the best performance out of
your programs:
@@ -922,6 +955,7 @@ your programs:
optimization will help you.
### Further Reading
+
You can find out more about how OCaml represents different types by
reading the ("Interfacing C with OCaml") chapter in the OCaml manual and also
looking at the `mlvalues.h` header file.
diff --git a/data/tutorials/guides/1wf_04_multicore_ready.md b/data/tutorials/guides/1wf_04_multicore_ready.md
index d03c0ebe9a..9f461b3fb9 100644
--- a/data/tutorials/guides/1wf_04_multicore_ready.md
+++ b/data/tutorials/guides/1wf_04_multicore_ready.md
@@ -67,7 +67,6 @@ let iter_accounts t f = (* inspect the bank accounts *)
Array.iteri (fun account balance -> f ~account ~balance) t;
```
-
## A Proposed Workflow
Now if we want to see if this code is Multicore ready for OCaml 5.x,
@@ -79,16 +78,15 @@ we can utilise the following workflow:
3. If TSan complains about data races, address the reported issue and
go to step 2.
-
## Following the Workflow
We will now go through the proposed workflow for our example
application.
-
### Install the Instrumenting TSan Compiler (Step 0)
TSan is included in OCaml starting from OCaml 5.2, but has to be explicitly enabled. You can install a TSan switch as follows (here we create a 5.2.0 switch named `5.2.0+tsan`):
+
``` shell
opam switch create 5.2.0+tsan ocaml-variants.5.2.0+options ocaml-option-tsan
```
@@ -104,6 +102,7 @@ example, `PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig`
For a start, we can test our library under parallel usage by running
two `Domain`s in parallel. Here's a quick little test runner in
`bank_test.ml` utilising this idea:
+
``` ocaml
let num_accounts = 7
@@ -136,6 +135,7 @@ let _ =
The runner creates a bank with 7 accounts containing $100
each and then runs two loops in parallel with:
+
- One transfering money with `money_shuffle`
- Another one repeatedly printing the account balances with `print_balances`:
@@ -160,7 +160,6 @@ From the above run under a regular `5.1.0` compiler, one may get the
impression that everything is OK, as the balances sum to a total of
$700 as expected, indicating that no money is lost.
-
### Run the Parallel Tests Under TSan (Step 2)
Let us now perform the same test run under TSan. Doing so is as simple
@@ -206,13 +205,13 @@ WARNING: ThreadSanitizer: data race (pid=26148)
```
Notice we obtain a back trace of the two racing accesses, with
+
- A write in one `Domain` coming from the array assignment in
- `Bank.transfer`
+ `Bank.transfer`
- A read in another `Domain` coming from a call to
`Stdlib.Array.iteri` to read and print the array entries in
`print_balances`.
-
### Address the Reported Races and Rerun the Tests (Steps 3 and 2)
One way to address the reported races is to add a `Mutex`, ensuring
@@ -257,7 +256,6 @@ Fatal error: exception Sys_error("Mutex.lock: Resource deadlock avoided")
How come we may hit a resource deadlock error when adding just two
pairs of `Mutex.lock` and `Mutex.unlock` calls?
-
### Address the Reported Races and Rerun the Tests, Take 2 (Steps 3 and 2)
Oh, wait! When raising an exception in `transfer`, we forgot to unlock
@@ -279,6 +277,7 @@ let transfer t ~src_acc ~dst_acc ~amount =
```
We can now rerun our tests under TSan to confirm the fix:
+
``` shell
$ opam exec -- dune runtest
0 100 1 100 2 100 3 100 4 100 5 100 6 100 total = 700
@@ -298,16 +297,18 @@ $ opam exec -- dune runtest
This works well and TSan no longer complains, so our little library is
ready for OCaml 5.x parallelism, hurrah!
-
### Final Remarks and a Word of Warning
The programming pattern of 'always-having-to-do-something-at-the-end'
that we encountered with the missing `Mutex.unlock` is a recurring
one for which OCaml offers a dedicate function:
+
```ocaml
Fun.protect : finally:(unit -> unit) -> (unit -> 'a) -> 'a
```
+
Using `Fun.protect`, we could have written our final fix as follows:
+
```ocaml
let transfer t ~src_acc ~dst_acc ~amount =
begin
diff --git a/data/tutorials/guides/1wf_05_garbage_collection.md b/data/tutorials/guides/1wf_05_garbage_collection.md
index 2dfdfac0f2..1e94ddc230 100644
--- a/data/tutorials/guides/1wf_05_garbage_collection.md
+++ b/data/tutorials/guides/1wf_05_garbage_collection.md
@@ -11,6 +11,7 @@ In this tutorial, we look at how to use the `Gc` module and how to write your ow
At the end of the tutorial, we give some exercises you might try in order to develop a better understanding.
## The `Gc` Module
+
The `Gc` module contains some useful functions for querying and calling
the garbage collector from OCaml programs.
@@ -85,6 +86,7 @@ but it should be mostly obvious what it does). The above code
causes the GC to print a message at the start of every major collection.
## Finalisation and the Weak Module
+
We can write a function called a **finaliser** which is called when an
object is about to be freed by the GC.
@@ -156,7 +158,6 @@ the in-memory copy is written back out to the file, the program must
release the lock. Here is some code to define the on-disk format and
some low-level functions to read, write, lock, and unlock records:
-
```ocaml
(* In-memory format. *)
@@ -199,7 +200,6 @@ let new_record () =
{ name = String.make name_size ' '; address = String.make addr_size ' ' }
```
-
Because this is a really simple program, we're going to fix the number
of records in advance:
@@ -231,7 +231,6 @@ from the cache. If the cache gives us `Some record`, then we just return
`record` (this promotes the weak pointer to the record to a normal
pointer).
-
```ocaml
(* The finaliser function. *)
@@ -263,17 +262,17 @@ records. But it doesn't necessarily mean that the GC *will* collect the
records straightaway. In fact it's not likely that it will, so to force
the GC to collect the records immediately, we also invoke a major cycle.
-
Finally, we have some test code. I won't reproduce the test code here, but you
can download the complete program and test code
[objcache.ml](/media/tutorials/objcache.ml) and compile it with:
```sh
-$ ocamlc unix.cma objcache.ml -o objcache
+ocamlc unix.cma objcache.ml -o objcache
```
## Exercises
+
Here are some ways to extend the example above, in approximately
increasing order of difficulty:
diff --git a/data/tutorials/guides/rs_00_guidelines.md b/data/tutorials/guides/rs_00_guidelines.md
index 6be67ab76d..a6057d404f 100644
--- a/data/tutorials/guides/rs_00_guidelines.md
+++ b/data/tutorials/guides/rs_00_guidelines.md
@@ -20,7 +20,9 @@ manually, there are some formatting guidelines at the end of this
article.
## General Guidelines
-### Be Simple and Readable
+
+### Be Simple and Readable
+
The time you spend typing the programs is negligible compared to the
time spent reading them. That's the reason why you save a lot of time if
you work hard to optimise readability.
@@ -36,8 +38,8 @@ debugging).
> modifications in mind, and never jeopardize readability.
>
+### Naming Complex Arguments
-### Naming Complex Arguments
In place of
@@ -50,6 +52,7 @@ let temp =
expression” in
...
```
+
write
@@ -64,7 +67,9 @@ let temp =
f x y z t u in
...
```
-### Naming Anonymous Functions
+
+### Naming Anonymous Functions
+
In the case of an iterator whose argument is a complex function, define
the function by a `let` binding as well. In place of
@@ -77,6 +82,7 @@ List.map
blabla)
l
```
+
write
@@ -93,26 +99,31 @@ List.map f l
>
## Programming Guidelines
-### How to Program
+
+### How to Program
>
> *Always put your handiwork back on the bench,
-> then polish it and repolish it.*
+> then polish it and repolish it.*
>
-#### Write Simple and Clear Programs
+#### Write Simple and Clear Programs
+
Reread, simplify, and clarify at every stage of
creation. Use your head!
-#### Subdivide Your Programs Into Little Functions
+#### Subdivide Your Programs Into Little Functions
+
Small functions are easier to master.
-#### Factor out snippets of repeated code by defining them in separate functions
+#### Factor out snippets of repeated code by defining them in separate functions
+
Sharing code obtained in this way facilitates maintenance, since
every correction or improvement automatically spreads throughout the
program. Besides, the simple act of isolating and naming a snippet of
code sometimes lets you identify an unsuspected feature.
-#### Never copy-paste code when programming
+#### Never copy-paste code when programming
+
Pasting code almost surely indicates introducing a code
sharing default and neglecting to identify and write a useful auxiliary
function. Hence, it means that some code sharing is lost in the program.
@@ -130,7 +141,7 @@ more difficult to understand.
In conclusion, copy-pasting code leads to programs that are more
difficult to read and more difficult to maintain. It must be banished.
-### How to Comment Programs
+### How to Comment Programs
Don't hesitate to comment when there's a difficulty. If there's no difficulty,
there's no point in commenting. It merely creates unnecessary noise.
@@ -140,7 +151,8 @@ Prefer one comment at the beginning of the function that explains how a
particular algorithm works. Once more, if there is no difficulty, there is no
point in commenting.
-#### Avoid Nocuous Comments
+#### Avoid Nocuous Comments
+
A *nocuous* comment is a comment that does not add any value, e.g.,
trivial information. The nocuous comment is evidently not of
interest; it is a nuisance that uselessly distracts the reader. It
@@ -173,13 +185,16 @@ let rec print_lambda lam =
| App (l1, l2) ->
printf "(%a %a)" print_lambda l1 print_lambda l2
```
-#### Usage in Module Interface
+
+#### Usage in Module Interface
+
The function's usage must appear in the module's interface that
exports it, not in the program that implements it. Choose comments as
in the OCaml system's interface modules, which will subsequently automatically
extract the documentation of the interface module if necessary.
-#### Use Assertions
+#### Use Assertions
+
Use assertions as much as possible, as they let you avoid verbose comments
while allowing a useful verification upon execution.
@@ -192,13 +207,15 @@ let f x =
assert (x >= 0);
...
```
+
Note as well that an assertion is often preferable to a comment because
it's more trustworthy. An assertion is forced to be pertinent because it
is verified upon each execution, while a comment can quickly become
obsolete, making it detrimental to understanding
the program.
-#### Comments line by line in imperative code
+#### Comments line by line in imperative code
+
When writing difficult code, and particularly in case of highly
imperative code with a lot of memory modifications (physical mutations
in data structures), it is sometimes mandatory to comment inside the body
@@ -207,18 +224,21 @@ here or to follow successive invariant modifications that the
function must maintain. Once more, if there is some difficulty
commenting is mandatory, for each program line if necessary.
-### How to Choose Identifiers
+### How to Choose Identifiers
+
It's hard to choose identifiers whose name evokes the meaning of the
corresponding portion of the program. This is why you must devote
particular care to this, emphasising clarity and regularity of
nomenclature.
-#### Don't use abbreviations for global names
+#### Don't use abbreviations for global names
+
Global identifiers (including the names of functions) can be
long because it's important to understand what purpose they serve far
from their definition.
-#### Separate words by underscores: (`int_of_string`, not `intOfString`)
+#### Separate words by underscores: (`int_of_string`, not `intOfString`)
+
Case modifications are meaningful in OCaml. In effect, capitalised words
are reserved for constructors and module names. In contrast,
regular variables (functions or identifiers) must start with a lowercase
@@ -227,12 +247,14 @@ separation in identifiers. The first word starts the identifier, hence
it must be lowercase, and it is forbidden to choose `IntOfString` as a function
name.
-#### Always give the same name to function arguments which have the same meaning
+#### Always give the same name to function arguments which have the same meaning
+
If necessary, make this nomenclature explicit in a comment at the top of
the file. If there are several arguments with the same meaning, then
attach numeral suffixes to them.
-#### Local identifiers can be brief and should be reused from one function to another
+#### Local identifiers can be brief and should be reused from one function to another
+
This augments style consistency. Avoid using identifiers whose
appearance can lead to confusion, such as `l` or `O`, which are easy to confuse
with `1` and `0`.
@@ -244,14 +266,17 @@ Example:
let add_expression expr1 expr2 = ...
let print_expression expr = ...
```
+
A tolerated exception to the recommendation not to use capitalisation to separate
words within identifiers when interfacing with
existing libraries which use this naming convention. This lets OCaml
library users to orient themselves in the original library
documentation more easily.
-### How to Use Modules
-#### Subdividing into modules
+### How to Use Modules
+
+#### Subdividing into modules
+
You must subdivide your programs into coherent modules.
For each module, you must explicitly write an interface.
@@ -259,7 +284,8 @@ For each module, you must explicitly write an interface.
For each interface, you must document the things defined by the module:
functions, types, exceptions, etc.
-#### Opening modules
+#### Opening modules
+
Avoid `open` directives, using instead the qualified identifier
notation. Thus you will prefer short but meaningful module names.
@@ -277,7 +303,9 @@ let lim = Array.length v - 1 in
... List.map succ ...
... Array.map succ ...
```
-#### When to use open modules rather than leaving them closed
+
+#### When to use open modules rather than leaving them closed
+
Consider it normal to open a module that modifies the
environment and brings other versions of an important set of functions.
For example, the `Format` module automatically provides indented
@@ -296,6 +324,7 @@ instance:
let f () =
Format.print_string "Hello World!"; print_newline ()
```
+
is bogus since it does not call `Format.print_newline` to flush the
pretty-printer queue and output `"Hello World!"`. Instead
`"Hello World!"` is stuck into the pretty-printer queue, while
@@ -329,7 +358,7 @@ these definitions into one or more module(s) without implementations
(containing only types). Then it's acceptable to systematically open the
module that exports the shared type definitions.
-### Pattern-Matching
+### Pattern-Matching
Never be afraid of overusing pattern-matching! On the other hand, be careful to
avoid nonexhaustive pattern-matching constructs. Complete them with care,
@@ -337,22 +366,24 @@ without using a “catch-all” clause such as `| _ -> ...` or `| x -> ...` when
it's unnecessary (for example when matching a concrete type
defined within the program). See also the next section: compiler warnings.
-### Compiler Warnings
+### Compiler Warnings
+
Compiler warnings are meant to prevent potential errors, which is why you
absolutely must heed them and correct your programs if compiling them
produces such warnings. Besides, programs whose compilation produces
warnings have an odor of amateurism which certainly doesn't suit your
own work!
-#### Pattern-matching warnings
+#### Pattern-matching warnings
+
Warnings about pattern-matching must be treated with the upmost care.
* Those with useless clauses should be eliminated, of course.
* For nonexhaustive pattern-matching, you must complete the
corresponding pattern-matching construct without adding a default
- case “catch-all”, such as `| _ -> ... `, but rather with an explicit
+ case “catch-all”, such as `| _ -> ...`, but rather with an explicit
constructor list not examined by the rest of the construct,
- e.g., `| Cn _ | Cn1 _ -> ... `.
+ e.g., `| Cn _ | Cn1 _ -> ...`.
>
> **Justification**: It's not really more complicated to write
@@ -366,12 +397,12 @@ Warnings about pattern-matching must be treated with the upmost care.
> handled by the default case.
>
-
* Nonexhaustive pattern-matches induced by clauses with guards must
also be corrected. A typical case consists in suppressing a
redundant guard.
-#### Destructuring `let` bindings
+#### Destructuring `let` bindings
+
A “destructuring `let` binding” is one which
binds several names to several expressions simultaneously. You pack all
the names you want bound into a collection such as a tuple or a list,
@@ -392,7 +423,8 @@ can use it with more complex or simpler patterns. For instance:
`let _ = ...` does not define anything, it just evaluate the
expression on the right hand side of the `=` symbol.
-#### The destructuring `let` must be exhaustive
+#### The destructuring `let` must be exhaustive
+
Only use destructuring `let` bindings when the
pattern-matching is exhaustive (the pattern can never fail to match).
Typically, you will thus be limited to product-type definitions
@@ -411,7 +443,6 @@ match List.map succ (l1 @ l2) with
| _ -> assert false
```
-
* Global definition with destructuring `let` statements should be rewritten with
explicit pattern-matching and tuples:
@@ -423,13 +454,13 @@ let x, y, l =
| _ -> assert false
```
-
>
> **Justification**: There is no way to make the pattern-matching
> exhaustive if you use general destructuring `let` bindings.
>
-#### Sequence warnings and `let _ = ...`
+#### Sequence warnings and `let _ = ...`
+
When the compiler emits a warning about a sequential expression type,
you must explicitly indicate that you want to ignore this expression's
result. To this end:
@@ -441,6 +472,7 @@ result. To this end:
List.map f l;
print_newline ()
```
+
write
```ocaml
@@ -448,7 +480,6 @@ let _ = List.map f l in
print_newline ()
```
-
* You can also use the predefined function `ignore : 'a -> unit`,
which ignores its argument to return `unit`.
@@ -458,7 +489,6 @@ ignore (List.map f l);
print_newline ()
```
-
* Regardless, the best way to suppress this warning is to understand
why the compiler emits it. The compiler warns you because
your code computes a result that is useless since it's
@@ -480,6 +510,7 @@ print_newline ()
List.iter f l;
print_newline ()
```
+
In actual programs, the suitable (side-effect-only) function may not
exist and must be written. Frequently, a careful separation of the
procedural part from the functional part of the function
@@ -493,11 +524,10 @@ let add x y =
print_newline ();
x + y;;
```
+
into the clearer, separate definition and change old calls to `add`
accordingly.
-
-
In any case, use the `let _ = ...` construction exactly in those cases
where you want to ignore a result. Don't systematically replace
sequences with this construction.
@@ -511,7 +541,8 @@ sequences with this construction.
> e3
> ```
-### The `hd` and `tl` Functions
+### The `hd` and `tl` Functions
+
Don't use the `hd` and `tl` functions, but rather pattern-match the list
argument explicitly.
@@ -522,8 +553,10 @@ argument explicitly.
> functions.
>
-### Loops
-#### `for` loops
+### Loops
+
+#### `for` loops
+
To simply traverse an array or a string, use a `for` loop.
@@ -532,6 +565,7 @@ for i = 0 to Array.length v - 1 do
...
done
```
+
If the loop is complex or returns a result, use a recursive function.
@@ -575,7 +609,8 @@ programs from algorithm courses where they were proved.
> invariants, an interesting but difficult sport.
>
-### Exceptions
+### Exceptions
+
Don't be afraid to define your own exceptions in your programs, but on
the other hand, use as many exceptions predefined by the
system as possible. For example, every search function that fails should raise the
@@ -604,7 +639,8 @@ with x -> close_in ic; close_out oc; raise x
> computation will continue anyway!).
>
-### Data Structures
+### Data Structures
+
One of the great strengths of OCaml is the power of definable data structures
and the simplicity of manipulating them. So you
must take advantage of this to the fullest extent! Don't hesitate to
@@ -686,17 +722,20 @@ types' definitions are then questionable:
type switch = On | Off
type bit = One | Zero
```
+
The same objection is admissible for enumerated types represented as
integers when those integers have an evident interpretation with
respect to the represented data.
-### When to Use Mutables
+### When to Use Mutables
+
Mutable values are useful and sometimes indispensable for simple and
clear programming. Nevertheless, you must use them with discernment because
OCaml's normal data structures are immutable. They are preferred
for the clarity and safety of programming they allow.
-### Iterators
+### Iterators
+
OCaml's iterators are a powerful and useful feature. However, you should
not overuse them nor neglect them. They are provided
by libraries and have every chance of being correct and
@@ -708,6 +747,7 @@ Write:
```ocaml
let square_elements elements = List.map square elements
```
+
rather than:
```ocaml
@@ -715,12 +755,14 @@ let rec square_elements = function
| [] -> []
| elem :: elements -> square elem :: square_elements elements
```
+
On the other hand, avoid writing:
```ocaml
let iterator f x l =
List.fold_right (List.fold_left f) [List.map x l] l
```
+
even though you get:
```ocaml
@@ -728,13 +770,14 @@ even though you get:
List.fold_right (List.fold_left f) [List.map x l] l;;
iterator (fun l x -> x :: l) (fun l -> List.rev l) [[1; 2; 3]]
```
+
In case of express need, be sure to add an explanatory
comment. In my opinion, it's absolutely necessary!
-### How to Optimize Programs
+### How to Optimize Programs
>
> **Pseudo law of optimisation**: No optimisation *a priori*.
-> No optimisation *a posteriori* either.
+> No optimisation *a posteriori* either.
>
Above all, program simply and clearly. Don't start optimising until the
@@ -751,7 +794,8 @@ program that poses a problem.
> efficiency is of prime importance.
>
-### How to Choose Between Classes and Modules
+### How to Choose Between Classes and Modules
+
Use OCaml classes when you need inheritance, i.e.,
incremental refinement of data and their functionality.
@@ -762,7 +806,8 @@ Use modules when the data structures are fixed and their
functionality is equally fixed or it's enough to add new functions in
the programs which use them.
-### Clarity of OCaml Code
+### Clarity of OCaml Code
+
The OCaml language includes powerful constructs that allow simple and
clear programming. The main problem to obtain crystal clear programs is
to use them appropriately.
@@ -779,7 +824,8 @@ to use the language construct that expresses the computation to
implement the algorithm in the most natural and
easiest way.
-#### Style dangers
+#### Style dangers
+
Concerning programming styles, one can usually observe the two
symmetrical problematic behaviors. On one hand, the “all
imperative” way (*systematic* usage of loops and assignment), and on
@@ -788,7 +834,7 @@ assignments). The “100% object” style will certainly appear in the
future.
* **The “too much imperative” danger**:
- * It is a bad idea to use imperative style to code a function that
+ * It is a bad idea to use imperative style to code a function that
is *naturally* recursive. For instance, to compute the length of
a list, you shouldn't write:
@@ -801,6 +847,7 @@ let list_length l =
done;
!res;;
```
+
in place of the following recursive function that's so simple and
clear:
@@ -809,10 +856,10 @@ let rec list_length = function
| [] -> 0
| _ :: l -> 1 + list_length l
```
+
(For those that would contest the equivalence of those two
versions, see the [note below](#imperative-and-functional-versions-of-listlength)).
-
* Another common “over-imperative error” in the imperative world is
not to systematically choose the simple `for` loop to iterate on a vector's
element, but instead to use a complex `while` loop with
@@ -823,22 +870,23 @@ versions, see the [note below](#imperative-and-functional-versions-of-listlength
the record-type definitions should be implicit.
* **The “too much functional” danger**:
- * The programmer that adheres to this dogma avoids
+ * The programmer that adheres to this dogma avoids
using arrays and assignment. In the most severe cases, one
observes a complete denial of writing any imperative
construction, even when it's evidently the most elegant way
to solve the problem.
- * Characteristic symptoms: systematic rewriting `for` loops
+ * Characteristic symptoms: systematic rewriting `for` loops
with recursive functions, using lists in contexts where
imperative data structures seem to be mandatory to anyone,
passing numerous global parameters of the problem to every
function, even when a global reference perfectly avoid
these spurious parameters that are mainly invariants that must
be repeatedly passed.
- * This programmer feels that the `mutable` keyword in record-type
+ * This programmer feels that the `mutable` keyword in record-type
definitions should be suppressed from the language.
-#### OCaml code generally considered unreadable
+#### OCaml code generally considered unreadable
+
The OCaml language includes powerful constructs which allow simple and
clear programming. However, the power of these constructs also lets you
write uselessly complicated code to the point where you get a perfectly
@@ -852,6 +900,7 @@ Here are a number of common ways to write overly-complicated code:
let flush_ps () =
if not !psused then psused := true
```
+
or (more subtle)
```ocaml
@@ -859,7 +908,6 @@ let sync b =
if !last_is_dvi <> b then last_is_dvi := b
```
-
* Code one construct with another. For example, code a `let ... in` by
the application of an anonymous function to an argument. You would
write
@@ -868,6 +916,7 @@ let sync b =
(fun x y -> x + y)
e1 e2
```
+
instead of simply writing
```ocaml
@@ -876,10 +925,8 @@ and y = e2 in
x + y
```
-
* Systematically code sequences with `let in` bindings.
-
* Mix computations and side effects, particularly in function calls.
Recall that the evaluation order of function call arguments
is unspecified, which implies that you must not mix side effects and
@@ -889,7 +936,6 @@ x + y
albeit without endangering the program semantics. This should be absolutely
forbidden.
-
* Misuse of iterators and higher-order functions (i.e., over- or
underuse). For example, it's better to use `List.map` or
`List.iter` than to write their equivalents inline by using your own
@@ -897,7 +943,6 @@ x + y
`List.map` or `List.iter`, but rather write their equivalents in terms of
`List.fold_right` and `List.fold_left`.
-
* Another efficient way to write unreadable code is to mix all or some
of these methods. For example:
@@ -908,17 +953,18 @@ x + y
temp));;
```
-
If you naturally write the program `print_string "Hello world!"` in this
way, please submit your work to the [Obfuscated OCaml
Contest](mailto:Pierre.Weis@inria.fr).
## Managing Program Development
+
Below are tips from veteran OCaml programmers that have served in
developing the compilers. These are good examples of large, complex
programs developed by small teams.
-### How to Edit Programs
+### How to Edit Programs
+
Many developers nurture a kind of veneration towards writing their programs
in the Emacs editor (GNU Emacs, in general). The
editor interfaces with the language well because it is capable of syntax-coloring
@@ -938,7 +984,8 @@ succession of `` CTRL-X-` `` commands permits correction of all signalled
errors; finally, the cycle begins again with a new recompilation
launched by `CTRL-C-CTRL-C`.
-#### Other Emacs tricks
+#### Other Emacs tricks
+
The `ESC-/` command (dynamic-abbrev-expand) automatically completes the
word in front of the cursor with one of the words in a
file being edited. This lets you always choose meaningful
@@ -955,7 +1002,8 @@ messages” from `grep` are compatible with the `` CTRL-X-` `` usage,
which automatically takes you to the file and the place where the string
is found.
-### How to Edit With the Interactive System
+### How to Edit With the Interactive System
+
Under Unix: use the line editor `ledit`, which offers great editing
capabilities “à la Emacs” (including `ESC-/`!) as well as a history
mechanism that lets you retrieve previously-typed commands and even
@@ -963,13 +1011,15 @@ retrieve commands from one session to another. `ledit` is written in
OCaml and can be freely downnloaded
[here](ftp://ftp.inria.fr/INRIA/Projects/cristal/caml-light/bazar-ocaml/ledit.tar.gz).
-### How to Compile
+### How to Compile
+
The `make` utility is indispensable for managing the compilation and
recompilation of programs. Sample `make` files can be found on [The
Hump](https://web.archive.org/web/20170809105617/http://caml.inria.fr/cgi-bin/hump.en.cgi). You can also consult
the `Makefiles` for the OCaml compilers.
-### How to Develop as a Team: Version Control
+### How to Develop as a Team: Version Control
+
Users of the [Git](https://git-scm.com/) software version control system
never run out of good things to say about the productivity gains it
brings. This system supports managing development by a programming team
@@ -994,27 +1044,28 @@ consider these style guidelines when doing it manually.
> keyboard, so press it as often as necessary!
>
-### Delimiters
+### Delimiters
+
A space should always follow a delimiter symbol, and spaces should
surround operator symbols. It has been a great step forward in
typography to separate words by spaces in order to make written texts easier to
read. Do the same in your programs if you want them to be readable.
+### How to Write Pairs
-
-### How to Write Pairs
A tuple is parenthesised, and the commas therein (delimiters) are each
followed by a space: `(1, 2)`, `let triplet = (x, y, z)`...
**Commonly accepted exceptions**:
- - **Definition of the components of a pair**: In place of
+
+* **Definition of the components of a pair**: In place of
`let (x, y) = ...`, you can write `let x, y = ...`.
> **Justification**: The point is to define several values
> simultaneously, not to construct a tuple. Moreover, the
> pattern is set off nicely between `let` and `=`.
- - **Matching several values simultaneously**: It's okay to omit
+* **Matching several values simultaneously**: It's okay to omit
parentheses around n-tuples when matching several values
simultaneously.
@@ -1028,20 +1079,20 @@ followed by a space: `(1, 2)`, `let triplet = (x, y, z)`...
> being matched are set off by `match` and `with`, while the
> patterns are set off nicely by `|` and `->`.
+### How to Write Lists
-### How to Write Lists
Write `x :: l` with spaces around the `::` (since `::` is an infix
operator, hence surrounded by spaces) and `[1; 2; 3]` (since `;` is a
delimiter, hence followed by a space).
-### How to Write Operator Symbols
+### How to Write Operator Symbols
+
Be careful to keep operator symbols separated by spaces. Not only
will your formulas be more readable, but you will also avoid confusion with
multicharacter operators. (Obvious exceptions to this rule are the symbols
`!` and `.`, which are not separated from their arguments.)
Example: write `x + 1` or `x + !y`.
-
> **Justification**: If you left out the spaces then `x+1` would be
> understood, but `x+!y` would change its meaning, since `+!` would
> be interpreted as a multicharacter operator.
@@ -1079,8 +1130,8 @@ Example: write `x + 1` or `x + !y`.
> follow. The space bar is the greatest and best-situated key on the
> keyboard. It is the easiest to use because you cannot miss it!
+### How to Write Long Character Strings
-### How to Write Long Character Strings
Indent long character strings with the convention in force at that line,
plus an indication of string continuation at the end of each line (a `\`
character at the end of the line omits white spaces at the
@@ -1093,7 +1144,8 @@ let universal_declaration =
...
```
-### When to Use Parentheses Within an Expression
+### When to Use Parentheses Within an Expression
+
Parentheses are meaningful. They indicate the necessity of using an
unusual precedence, so they should be used wisely and not sprinkled
randomly throughout programs. To this end, you should know the usual
@@ -1101,10 +1153,12 @@ precedences, i.e., the combinations of operations which do not
require parentheses. Quite fortunately, this is not complicated if you
know a little mathematics or strive to follow the following rules:
-#### Arithmetic operators: the same rules as in mathematics
+#### Arithmetic operators: the same rules as in mathematics
+
For example: `1 + 2 * x` means `1 + (2 * x)`.
-#### Function application: the same rules as those in mathematics for usage of *trigonometric functions*
+#### Function application: the same rules as those in mathematics for usage of *trigonometric functions*
+
In mathematics you write `sin x` to mean `sin (x)`. In the same way
`sin x + cos x` means `(sin x) + (cos x)` not `sin (x + (cos x))`. Use
the same conventions in OCaml: write `f x + g x` to mean
@@ -1113,20 +1167,23 @@ This convention generalises **to all (infix) operators**: `f x :: g x`
means `(f x) :: (g x)`, `f x @ g x` means `(f x) @ (g x)`, and
`failwith s ^ s'` means `(failwith s) ^ s'`, *not* `failwith (s ^ s')`.
-#### Comparisons and Boolean operators
+#### Comparisons and Boolean operators
+
Comparisons are infix operators, so the preceding rules apply. This is
why `f x < g x` means `(f x) < (g x)`. For type reasons (and no other
sensible interpretation), the expression `f x < x + 2` means
`(f x) < (x + 2)`. Similarly, `f x < x + 2 && x > 3` means
`((f x) < (x + 2)) && (x > 3)`.
-#### The relative precedences of the Boolean operators are those of mathematics
+#### The relative precedences of the Boolean operators are those of mathematics
+
Although mathematicians have a tendency to overuse parentheses,
the Boolean “or” operator is analogous to addition and the “and”
to multiplication. So, just as `1 + 2 * x` means `1 + (2 * x)`,
`true || false && x` means `true || (false && x)`.
-### How to Delimit Constructs in Programs
+### How to Delimit Constructs in Programs
+
When it is necessary to delimit syntactic constructs in programs, use
the keywords `begin` and `end` as delimiters rather than parentheses.
However, using parentheses is acceptable if you do it in a consistent,
@@ -1136,7 +1193,8 @@ This explicit delimiting of constructs essentially concerns
pattern-matching constructs or sequences embedded within
`if then else` constructs.
-#### `match` construct in a `match` construct
+#### `match` construct in a `match` construct
+
When a `match ... with` or `try ... with` construct appears in a
pattern-matching clause, it is absolutely necessary to delimit this
embedded construct (otherwise subsequent clauses of the enclosing
@@ -1153,7 +1211,9 @@ match x with
| 2 ->
...
```
-#### Sequences inside branches of `if`
+
+#### Sequences inside branches of `if`
+
In the same way, a sequence which appears in the `then` or `else` part
of a conditional must be delimited:
@@ -1168,7 +1228,6 @@ end else begin
end
```
-
## Indentation of Programs
>
> **Landin's pseudo law**: Treat your program's indentation as if
@@ -1189,39 +1248,40 @@ So each time, you must choose between the different styles
suggested.
The only absolute rule is the first below.
-### Consistency of Indentation
+### Consistency of Indentation
+
Choose a generally accepted style of indentation, then use it
systematically throughout the whole application.
-### Width of the Page
+### Width of the Page
+
The page is 80 columns wide.
> **Justification**: This width makes it possible to read the code on
> all displays and to print it in a legible font on a standard sheet.
+### Height of the Page
-### Height of the Page
A function should always fit within one screenful (of about 70 lines),
or in exceptional cases two, at the very most three. To go beyond this
is unreasonable.
-
> **Justification**: When a function goes beyond one screenful, it's
> time to divide it into subproblems and handle them independently.
> Beyond a screenful, one gets lost in the code. The indentation is not
> readable and is difficult to keep correct.
+### How Much to Indent
-### How Much to Indent
The change in indentation between successive lines of the program is
generally 1 or 2 spaces. Pick an amount to indent and stick with it
throughout the program.
-### Using Tab Stops
+### Using Tab Stops
+
Using the tab character (ASCII character 9) is absolutely *not*
recommended.
-
> **Justification**: Between one display and another, the indentation of
> the program changes completely. It can also become completely wrong
> if the programmer used both tabulations and spaces to indent the
@@ -1235,7 +1295,8 @@ recommended.
> **Answer**: It seems almost impossible to use this method since you
> should always use tabulations to indent, which is hard and unnatural.
-### How to Indent Operations
+### How to Indent Operations
+
When an operator takes complex arguments, or in the presence of multiple
calls to the same operator, start the next the line with the operator,
and don't indent the rest of the operation. For example:
@@ -1260,6 +1321,7 @@ x + y + z
+ “large
expression”
```
+
write
@@ -1269,6 +1331,7 @@ let t =
expression” in
x + y + z + t
```
+
You most certainly must bind expressions too large to be written
in one operation when using a combination of operators. In place of
the unreadable expression
@@ -1279,6 +1342,7 @@ the unreadable expression
/ (“large
expression”)
```
+
write
@@ -1288,6 +1352,7 @@ let u =
expression” in
(x + y + z * t) / u
```
+
These guidelines extend to all operators. For example:
@@ -1299,8 +1364,8 @@ x :: y
:: z + 1 :: t :: u
```
+### How to Indent Global `let ... ;;` Definitions
-### How to Indent Global `let ... ;;` Definitions
The body of a function defined globally in a module is generally
indented normally. However, it's okay to treat this case specially to
better offset the definition.
@@ -1344,7 +1409,6 @@ let f x = function
> **Criticism**: An unpleasant exception to the normal indentation.
>
-
* The body is justified just under the name of the defined function.
@@ -1362,8 +1426,8 @@ let f x =
> **Criticism**: You run into the right margin too quickly.
>
+### How to Indent `let ... in` Constructs
-### How to Indent `let ... in` Constructs
The expression following a definition introduced by `let` is indented to
the same level as the keyword `let`, and the keyword `in`, which
introduces it, is written at the end of the line:
@@ -1407,8 +1471,10 @@ Mult_expression (new_expr, new_expr)
> **Criticism**: Lack of consistency.
>
-### How to Indent `if ... then ... else ... `
-#### Multiple branches
+### How to Indent `if ... then ... else ...`
+
+#### Multiple branches
+
Write conditions with multiple branches at the same level of
indentation:
@@ -1433,6 +1499,7 @@ if cond3 then e3 else
e4
```
+
If expressions in the branches of multiple conditions have to be
enclosed (when they include statements for instance), write:
@@ -1446,6 +1513,7 @@ if cond2 then begin
end else
if cond3 then ...
```
+
Some suggest another method for multiple conditionals: starting each
line by the keyword `else`:
@@ -1467,7 +1535,8 @@ else if cond3 ...
Yet again, choose your style and use it systematically.
-#### Single branches
+#### Single branches
+
Several styles are possible for single branches, according to the size
of the expressions in question and especially the presence of `begin`,
`end`, or `(` `)` delimiters for these expressions.
@@ -1484,6 +1553,7 @@ are used:
> e2
> )
> ```
+>
> Or alternatively first `begin` at beginning of line:
>
> ```ocaml
@@ -1503,6 +1573,7 @@ In fact, the conditional's indentation depends on their expressions' size.
> ```ocaml
> if cond then e1 else e2
> ```
+>
> If the expressions making up a conditional are purely functional
> (without side effects), we advocate binding them within the
> conditional by using `let e = ... in` when they're too big to fit on a single
@@ -1561,8 +1632,10 @@ In fact, the conditional's indentation depends on their expressions' size.
> > ) else e2
> > ```
-### How to Indent Pattern-Matching Constructs
-#### General principles
+### How to Indent Pattern-Matching Constructs
+
+#### General principles
+
All the pattern-matching clauses are introduced by a vertical bar,
*including* the first one.
@@ -1589,7 +1662,8 @@ Then indent normally, starting from the beginning of the clause's pattern.
Arrows of pattern-matching clauses should not be aligned.
-#### `match` or `try`
+#### `match` or `try`
+
For a `match` or a `try`, align the clauses with the beginning of the
construct:
@@ -1604,6 +1678,7 @@ try f x with
| Not_found -> ...
| Failure "not yet implemented" -> ...
```
+
Put the keyword `with` at the end of the line. If the preceding
expression extends beyond one line, put `with` on its own line:
@@ -1621,7 +1696,8 @@ with
> the program enters the pattern-matching part of the construct.
>
-#### Indenting expressions inside clauses
+#### Indenting expressions inside clauses
+
If the expression on the right of the pattern-matching arrow is too
large, cut the line after the arrow.
@@ -1634,6 +1710,7 @@ match lam with
size_lambda lam1 + size_lambda lam2
| Var v ->
```
+
Some programmers generalise this rule to all clauses as soon as one
expressions overflows. They will then indent the last clause like this:
@@ -1642,6 +1719,7 @@ expressions overflows. They will then indent the last clause like this:
| Var v ->
1
```
+
Other programmers go one step further and apply this rule systematically
to any clause of any pattern-matching.
@@ -1665,7 +1743,8 @@ let rec fib = function
> OCaml programs!
>
-#### Pattern-matching in anonymous functions
+#### Pattern-matching in anonymous functions
+
Similarly to `match` or `try`, pattern-matching of anonymous functions,
starting with `function`, are indented with respect to the `function`
keyword:
@@ -1679,7 +1758,9 @@ map
| Var v -> 1)
lambda_list
```
-#### Pattern-matching in named functions
+
+#### Pattern-matching in named functions
+
Pattern-matching in functions defined by `let` or `let rec` gives rise
to several reasonable styles that obey the preceding pattern-matching rules
(the one for anonymous functions being evidently excepted). See
@@ -1697,8 +1778,11 @@ let rec size_lambda accu = function
| App (lam1, lam2) -> size_lambda (size_lambda accu lam1) lam2
| Var v -> succ accu
```
-### Bad Indentation of Pattern-Catching constructs
-#### No *beastly* indentation of functions and case analyses.
+
+### Bad Indentation of Pattern-Catching constructs
+
+#### No *beastly* indentation of functions and case analyses
+
This consists in indenting normally under the keyword `match` or
`function` that has previously been pushed to the right. Don't write:
@@ -1708,6 +1792,7 @@ let rec f x = function
| [] -> ...
...
```
+
but choose to indent the line under the `let` keyword:
@@ -1721,7 +1806,8 @@ let rec f x = function
> doubtful.
>
-#### No *beastly* alignment of the `->` symbols in pattern-matching clauses.
+#### No *beastly* alignment of the `->` symbols in pattern-matching clauses
+
Careful alignment of pattern-matching arrows is considered bad
practice, as exemplified in the following fragment:
@@ -1739,8 +1825,10 @@ let f = function
> In this case, it's better not to align the arrows in the first place!).
>
-### How to Indent Function Calls
-#### Indentation to the function's name:
+### How to Indent Function Calls
+
+#### Indentation to the function's name
+
No problem arises except for functions with many arguments—or very
complicated arguments—which can't fit on the same line. You
must indent the expressions with respect to the fucntion's name (1
@@ -1760,9 +1848,10 @@ construction.
> explicitly define the evaluation order.
>
+## Notes
+
+### Imperative and Functional Versions of `list_length`
-## Notes
-### Imperative and Functional Versions of `list_length`
The two versions of `list_length` are not completely equivalent in terms
of complexity. The imperative version uses a constant amount of
stack room to execute, whereas the functional version needs to store
@@ -1781,12 +1870,14 @@ let list_length l =
| _ :: l -> loop (accu + 1) l in
loop 0 l
```
+
This way, you get a program that has the same computational properties
as the imperative program with the additional clarity and natural
look of an algorithm that performs pattern-matching and recursive
calls to handle an argument that belongs to a recursive sum data type.
## Credits
+
Original translation from French: [Ruchira
Datta](mailto:datta@math.berkeley.edu).
diff --git a/data/tutorials/guides/rs_01_common_error_messages.md b/data/tutorials/guides/rs_01_common_error_messages.md
index 13483b9b22..269ce02750 100644
--- a/data/tutorials/guides/rs_01_common_error_messages.md
+++ b/data/tutorials/guides/rs_01_common_error_messages.md
@@ -11,7 +11,9 @@ messages that are emitted by the OCaml compilers. Longer explanations
are usually given in dedicated sections of this tutorial.
## Type Errors
+
### `This expression has type ... but is here used with type ...`
+
When the type of an object is not compatible with the context in which
it is used, it is frequent to obtain this kind of message:
@@ -21,6 +23,7 @@ Line 1, characters 5-8:
Error: This expression has type float but an expression was expected of type
int
```
+
"This expression has type *X* but is here used with type *Y*" means that
if the contents of the expression is isolated (2.5), its type is
inferred as *X* (float). But the context, i.e. everything which is
@@ -32,6 +35,7 @@ More disturbing is the following message:
```text
This expression has type my_type but is here used with type my_type
```
+
This error happens often while testing some type definitions using the
interactive toplevel. In OCaml, it is perfectly legal
to define a type with a name
@@ -54,6 +58,7 @@ Error: This expression has type my_type/1
toplevel session. Some toplevel values still refer to old versions
of this type. Did you try to redefine them?
```
+
For the compiler, the second definition of my_type is totally
independent from the first definition. So we have defined two types
which have the same name. Since "a" was defined earlier, it belongs to
@@ -64,6 +69,7 @@ the same name for the same type in the same module, which is highly
discouraged.
### `Warning: This optional argument cannot be erased`
+
Functions with optional arguments must have at least one non-labelled
argument. For instance, this is not OK:
@@ -75,16 +81,19 @@ Line 1, characters 9-14:
Warning 16 [unerasable-optional-argument]: this optional argument cannot be erased.
val f : ?x:int -> ?y:int -> unit =
```
+
The solution is simply to add one argument of type unit, like this:
```ocaml
# let f ?(x = 0) ?(y = 0) () = print_int (x + y);;
val f : ?x:int -> ?y:int -> unit -> unit =
```
+
See the [Labels](/docs/labels) section for more details on
functions with labelled arguments.
### `The type of this expression... contains type variables that cannot be generalized`
+
This happens in some cases when the full type of an object is not known
by the compiler when it reaches the end of the compilation unit (file)
but for some reason it cannot remain polymorphic. Example:
@@ -93,6 +102,7 @@ but for some reason it cannot remain polymorphic. Example:
# let x = ref None;;
val x : '_weak1 option ref = {contents = None}
```
+
triggers the following message during the compilation:
```text
@@ -106,6 +116,7 @@ Solution: help the compiler with a type annotation, like for instance:
# let x : string option ref = ref None;;
val x : string option ref = {contents = None}
```
+
or:
```ocaml env=ref
@@ -130,6 +141,7 @@ using `x` later, the compiler can infer the type of `x`:
# x := Some 0;;
- : unit = ()
```
+
Now `x` has a known type:
```ocaml env=ref
@@ -138,7 +150,9 @@ Now `x` has a known type:
```
## Pattern Matching Warnings and Errors
+
### `This pattern is unused`
+
This warning should be considered as an error, since there is no reason
to intentionally keep such code. It may happen when the programmer
introduced a catch-all pattern unintentionally such as in the following
@@ -158,6 +172,7 @@ Only the first match will be used to evaluate the guard expression.
(See manual section 11.5)
val test_member : 'a -> 'a * 'a -> bool =
```
+
Obviously, the programmer had a misconception of what OCaml's pattern
matching is about. Remember the following:
@@ -178,7 +193,9 @@ will ever be tested. This leads to the following results:
# test_member 1 (0, 1);;
- : bool = false
```
+
### `This pattern-matching is not exhaustive`
+
OCaml's pattern matching can check whether a set of patterns is
exhaustive or not, based on the *type* only. So in the following
example, the compiler doesn't know what range of ints the "mod" operator
@@ -190,12 +207,14 @@ let is_even x =
| 0 -> true
| 1 | -1 -> false
```
+
A short solution without pattern matching would be:
```ocaml
# let is_even x = x mod 2 = 0;;
val is_even : int -> bool =
```
+
In general, that kind of simplification is not possible and the best
solution is to add a catch-all case which should never be reached:
@@ -209,7 +228,9 @@ val is_even : int -> bool =
```
## Problems Recompiling Valid Programs
+
### `x.cmi is not a compiled interface`
+
When recompiling some old program or compiling a program from an
external source that was not cleaned properly, it is possible to get
this error message:
@@ -220,10 +241,11 @@ some_module.cmi is not a compiled interface
It means that some_module.cmi is not valid according to the *current
version* of the OCaml compiler. Most of the time, removing the old
-compiled files (*.cmi, *.cmo, *.cmx, ...) and recompiling is
+compiled files (*.cmi,*.cmo, *.cmx, ...) and recompiling is
sufficient to solve this problem.
### `Warning: Illegal backslash escape in string`
+
Recent versions of OCaml warn you against unprotected backslashes in
strings since they should be doubled. Such a message may be displayed
when compiling an older program, and can be turned off with the `-w x`
diff --git a/data/tutorials/guides/rs_02_comparison_of_standard_containers.md b/data/tutorials/guides/rs_02_comparison_of_standard_containers.md
index abcc126564..ff39f0d2a1 100644
--- a/data/tutorials/guides/rs_02_comparison_of_standard_containers.md
+++ b/data/tutorials/guides/rs_02_comparison_of_standard_containers.md
@@ -6,7 +6,7 @@ description: >
category: "Resources"
---
-This is a rough comparison of the different container types
+This is a rough comparison of the different container types
provided by the OCaml standard library. In each
case, _n_ is the number of valid elements in the container.
@@ -17,6 +17,7 @@ you can read the [documentation](/manual/api/index.html) about each of the modul
is also instructive to read the corresponding implementation.
## Lists: Immutable Singly-Linked Lists
+
Adding an element always creates a new list _l_ from an element _x_ List
_tl_. _tl_ remains unchanged, but it is not copied either.
@@ -30,6 +31,7 @@ Well-suited for: I/O, pattern-matching
Not very efficient for: random access, indexed elements
## Arrays: Mutable Vectors
+
Arrays are mutable data structures with a fixed length and random access.
* Adding an element (by creating a new array): _O(n)_
@@ -40,6 +42,7 @@ Arrays are mutable data structures with a fixed length and random access.
Well-suited for the following cases: dealing with sets of elements of known size, accessing elements by numeric index, and modifying in-place elements. Basic arrays have a fixed length.
## Strings: Immutable Vectors
+
Strings are very similar to arrays, but they are immutable. Strings are
specialised for storing chars (bytes) and have some convenient syntax.
Strings have a fixed length. For extensible strings, the standard buffer
@@ -51,6 +54,7 @@ module can be used (see below).
* Finding an element: _O(n)_
## Set and Map: Immutable Trees
+
Like lists, these are immutable, and they may share some subtrees. These trees
are a good solution for keeping older versions of sets of items.
@@ -62,6 +66,7 @@ Sets and maps are very useful in compilation and metaprogramming, but
in other situations, hash tables are often more appropriate (see below).
## Hashtbl: Automatically Growing Hash Tables
+
OCaml hash tables are mutable data structures, which are a good solution
for storing (key, data) pairs in one single place.
@@ -73,6 +78,7 @@ for storing (key, data) pairs in one single place.
* Finding an element: _O(1)_
## Buffer: Extensible Strings
+
Buffers provide an efficient way to accumulate a byte sequence in a
single place. They are mutable.
@@ -84,6 +90,7 @@ single place. They are mutable.
* Accessing cell `i`: _O(1)_
## Queue
+
OCaml queues are mutable first-in-first-out (FIFO) data structures.
* Adding an element: _O(1)_
@@ -91,6 +98,7 @@ OCaml queues are mutable first-in-first-out (FIFO) data structures.
* Length: _O(1)_
## Stack
+
OCaml stacks are mutable last-in-first-out (LIFO) data structures. They
are just like lists except they are mutable, i.e., adding an
element doesn't create a new stack but simply adds it to the stack.
diff --git a/data/tutorials/language/0it_00_values_functions.md b/data/tutorials/language/0it_00_values_functions.md
index cbbd8f41c9..8d374ff9ad 100644
--- a/data/tutorials/language/0it_00_values_functions.md
+++ b/data/tutorials/language/0it_00_values_functions.md
@@ -114,6 +114,11 @@ Arbitrary combinations of chaining or nesting are allowed.
In both examples, `d` and `e` are local definitions.
+## Forms of Pattern Matching
+
+[Pattern matching](https://en.wikipedia.org/wiki/Pattern_matching) is a programming language construct that generalizes case analysis, it makes subexpression inspection possible and applies to values of any type.
+
+In the following sections, we explore matching inside `let` bindings, which is a special case. In the next chapter on [Basic Data Types and Pattern Matching](/docs/basic-data-types), we examine pattern matching in general cases using `match...with`. The chapter on [Error Handling](/docs/error-handling) explores how destructuring values aids in error handling when using `try...with`.
## Pattern Matching in Definitions
@@ -126,6 +131,7 @@ When pattern matching only has one case, it can be used in name definitions and
name may be defined. This applies to tuples, records, and custom single-variant
types.
+
### Pattern Matching on Tuples
A common case is tuples. It allows the creation of two names with a single `let`.
@@ -194,7 +200,6 @@ Above, the pattern does not contain any identifier, meaning no name is defined.
**Note**: In order for compiled files to only evaluate an expression for its side effects, you must write them after `let () =`.
-
### Pattern Matching on User-Defined Types
@@ -209,15 +214,124 @@ val forename : string = "Robin"
val surname : string = "Milner"
```
+### Nested Pattern Matching on User-Defined Types
+
+Pattern matching also works with nested user-defined types. In the example below, we deconstruct nested tuples:
+
+```ocaml
+# let (name, (street, city, zip), (email, phone)) =
+ ("John Doe", ("123 Elm St", "Springfield", 12345), ("john@example.com", 1234567890));;
+val name : string = "John Doe"
+val street : string = "123 Elm St"
+val city : string = "Springfield"
+val zip : int = 12345
+val email : string = "john@example.com"
+val phone : int = 1234567890
+```
+
+In the following example, we deconstruct a tuple nested within a record.
+
+Records require explicit type definitions wherein field names are annotated with types. We first define a `person` record type, then create an instance of that type named `jane`:
+
+```ocaml
+# type person = {
+ name : string;
+ street : string;
+ city : string;
+ zip : int;
+ contact: string * int;
+ };;
+type person = {
+ name : string;
+ street : string;
+ city : string;
+ zip : int;
+ contact : string * int;
+
+# let jane = {
+ name = "Jane Doe";
+ street = "123 Elm St";
+ city = "Springfield";
+ zip = 12345;
+ contact = ("jane@example.com", 1234567890);
+ };;
+val jane : person =
+ {name = "Jane Doe"; street = "123 Elm St"; city = "Springfield"; zip = 12345;
+ contact = ("jane@example.com", 1234567890)}
+```
+
+The following examples demonstrate two methods by which we can extract Jane's `email` and `phone` number data in the nested `contact` tuple. We will do so first by using nested deconstruction, then demonstrate another approach by first extracting `content` followed by accessing the `email` and `phone` by deconstructing `contact`.
+
+First, lets use nested deconstruction to access the contents of the `contact` tuple directly:
+
+```ocaml
+# let { name; street; city; zip; contact = (email, phone) } = jane;;
+val name : string = "Jane Doe"
+val street : string = "123 Elm St"
+val city : string = "Springfield"
+val zip : int = 12345
+val email : string = "jane@example.com"
+val phone : int = 1234567890
+```
+
+Notice that `contact` is not available in our top-level scope:
+
+```ocaml
+# contact;;
+Error: Unbound value contact
+```
+
+This is because "contact" is not a top-level definition; rather, it is a local definition from deconstructive pattern-matching.
+
+Next, we demonstrate the two-phase approach for accessing `email` and `phone`. This brings `contact` into our top-level scope and requires two steps:
+
+```ocaml
+(* Step 1: deconstruct all record fields *)
+# let { name; street; city; zip; contact } = jane;;
+val name : string = "Jane Doe"
+val street : string = "123 Elm St"
+val city : string = "Springfield"
+val zip : int = 12345
+val contact : string * int = ("jane@example.com", 1234567890)
+
+(* Step 2: deconstruct the tuple *)
+# let ( email, phone ) = contact;;
+val email : string = "jane@example.com"
+val phone : int = 1234567890
+```
+
+Notice that `contact` is now available at the top-level as a bound variable:
+
+```ocaml
+# contact;;
+- : string * int = ("jane@example.com", 1234567890)
+```
+
### Discarding Values Using Pattern Matching
-
-As seen in the last example, the catch-all pattern (`_`) can be used in definitions.
+
+When pattern matching, it is possible to discard or ignore values that are not desired. The method by which this is done depends on the data structure being destructured.
+
+Continuing from the above `jane` record example, we can simply omit the `zip` field in the pattern if we don't want to bind it to a variable:
+
```ocaml
-# let (_, y) = List.split [(1, 2); (3, 4); (5, 6); (7, 8)];;
-val y : int list = [2; 4; 6; 8]
+# let { name; street; city; contact = (email, phone) } = john;;
+val name : string = "Nohn Doe"
+val street : string = "123 Elm St"
+val city : string = "Springfield"
+val email : string = "jane@example.com"
+val phone : int = 1234567890
```
-The `List.split` function returns a pair of lists. We're only interested in the second list, we give it the name `y` and discard the first list by using `_`.
+Tuples behave differently from records; contained data is anonymous, and its position is used to access it. To discard the `email` value from the tuple of the `contact` field, we need to use the catch-all pattern (`_`):
+
+```ocaml
+# let { name; street; city; contact = (_, phone) } = john;;
+val name : string = "Jane Doe"
+val street : string = "123 Elm St"
+val city : string = "Springfield"
+val phone : int = 1234567890
+```
+
## Scopes and Environments
diff --git a/data/tutorials/language/0it_01_basic_datatypes.md b/data/tutorials/language/0it_01_basic_datatypes.md
index b2d8b96e5e..0d775399ba 100644
--- a/data/tutorials/language/0it_01_basic_datatypes.md
+++ b/data/tutorials/language/0it_01_basic_datatypes.md
@@ -11,7 +11,7 @@ prerequisite_tutorials:
## Introduction
-This document covers atomic types, such as integers and Booleans; predefined compound types, like strings and lists; and user-defined types, namely variants and records. We show how to pattern matching on those types.
+This document covers atomic types, such as integers and Booleans; predefined compound types, like strings and lists; and user-defined types, namely variants and records. We show how to pattern match on those types.
In OCaml, there are no type checks at runtime, and values don't change type unless explicitly converted. This is what being statically- and strongly-typed means. This allows safe processing of structured data.
@@ -33,6 +33,7 @@ The goal of this tutorial is to provide for the following capabilities:
#### Integers
The `int` type is the default and basic integer type in OCaml. When you enter a whole number, OCaml recognises it as an integer, as shown in this example:
+
```ocaml
# 42;;
- : int = 42
@@ -49,6 +50,7 @@ There are no dedicated types for unsigned integers in OCaml. Bitwise operations
Float numbers have type `float`.
OCaml does not perform any implicit type conversion between values. Therefore, arithmetic expressions can't mix integers and floats. Arguments are either all `int` or all `float`. Arithmetic operators on floats are not the same, and they are written with a dot suffix: `+.`, `-.`, `*.`, `/.`.
+
```ocaml
# let pi = 3.14159;;
val pi : float = 3.14159
@@ -64,11 +66,13 @@ Error: This expression has type int but an expression was expected of type
Error: This expression has type float but an expression was expected of type
int
```
+
Operations on `float` are provided by the [`Stdlib`](/manual/api/Stdlib.html) and the [`Float`](/manual/api/Float.html) modules.
#### Booleans
Boolean values are represented by the type `bool`.
+
```ocaml
# true;;
- : bool = true
@@ -83,6 +87,7 @@ Boolean values are represented by the type `bool`.
Operations on `bool` are provided by the [`Stdlib`](/manual/api/Stdlib.html) and the [`Bool`](/manual/api/Bool.html) modules. The conjunction “and” is written `&&` and disjunction “or” is written `||`. Both are short-circuited, meaning that they don't evaluate the argument on the right if the left one's value is sufficient to decide the whole expression's value.
In OCaml, `if … then … else …` is a _conditional expression_. It has the same type as its branches.
+
```ocaml
# 3 * if "foo" = "bar" then 5 else 5 + 2;;
- : int = 21
@@ -91,6 +96,7 @@ In OCaml, `if … then … else …` is a _conditional expression_. It has the s
The test subexpression must have type `bool`. Branches subexpressions must have the same type.
Conditional expression and pattern matching on a Boolean are the same:
+
```ocaml
# 3 * match "foo" = "bar" with true -> 5 | false -> 5 + 2;;
- : int = 21
@@ -99,10 +105,12 @@ Conditional expression and pattern matching on a Boolean are the same:
#### Characters
Values of type `char` correspond to the 256 symbols of the Latin-1 set. Character literals are surrounded by single quotes, as shown below:
+
```ocaml
# 'd';;
- : char = 'd'
```
+
Operations on `char` values are provided by the [`Stdlib`](/manual/api/Stdlib.html) and the [`Char`](/manual/api/Char.html) modules.
The module [`Uchar`](/manual/api/Uchar.html) provides support for Unicode characters.
@@ -116,10 +124,12 @@ Strings are finite and fixed-sized sequences of char values. Strings are immutab
-->
Strings are immutable, meaning it is impossible to change a character's value inside a string.
+
```ocaml
# "hello" ^ " " ^ "world!";;
- : string = "hello world!"
```
+
Strings are finite and fixed-sized sequences of `char` values. The string concatenation operator symbol is `^`.
Indexed access to string characters is possible using the following syntax:
+
```ocaml
# "buenos dias".[4];;
- : char : 'o'
```
+
Operations on `string` values are provided by the [`Stdlib`](/manual/api/Stdlib.html) and the [`String`](/manual/api/String.html) modules.
#### Byte Sequences
@@ -143,6 +155,7 @@ Moved intro pp to after example, as it contains the definition. Perhaps a short
# String.to_bytes "hello";;
- : bytes = Bytes.of_string "hello"
```
+
Like strings, byte sequences are finite and fixed-sized. Each individual byte is represented by a `char` value. Like arrays, byte sequences are mutable, meaning they can't be extended or shortened, but each component byte may be updated. Essentially, a byte sequence (type `bytes`) is a mutable string that can't be printed. There is no way to write `bytes` literally, so they must be produced by a function.
Operations on `bytes` values are provided by the [`Stdlib`](/manual/api/Stdlib.html) and the [`Bytes`](/manual/api/Bytes.html) modules. Only the function `Bytes.get` allows direct access to the characters contained in a byte sequence. Unlike arrays, there is no direct access operator on byte sequences.
@@ -153,6 +166,7 @@ The memory representation of `bytes` is four times more compact that `char array
#### Arrays
Arrays are finite and fixed-sized sequences of values of the same type. Here are a couple of examples:
+
```ocaml
# [| 0; 1; 2; 3; 4; 5 |];;
- : int array = [|0; 1; 2; 3; 4; 5|]
@@ -165,19 +179,23 @@ Arrays are finite and fixed-sized sequences of values of the same type. Here are
```
Arrays may contain values of any type. Here arrays are `int array`, `char array`, and `string array`, but any type of data can be used in an array. Usually, `array` is said to be a polymorphic type. Strictly speaking, it is a type operator, and it accepts a type as argument (here `int`, `char`, and `string`) to form another type (those inferred here). This is the empty array.
+
```ocaml
# [||];;
- : 'a array = [||]
```
+
Remember, `'a` ("alpha") is a type parameter that will be replaced by another type.
Like `string` and `bytes`, arrays support direct access, but the syntax is not the same.
+
```ocaml
# [| 'x'; 'y'; 'z' |].(2);;
- : char = 'z'
```
Arrays are mutable, meaning they can't be extended or shortened, but each element may be updated.
+
```ocaml
# let letter = [| 'v'; 'x'; 'y'; 'z' |];;
val letter : char array = [|'v'; 'x'; 'y'; 'z'|]
@@ -196,6 +214,7 @@ Operations on arrays are provided by the [`Array`](/manual/api/Array.html) modul
#### Lists
As literals, lists are very much like arrays. Here are the same previous examples turned into lists.
+
```ocaml
# [ 0; 1; 2; 3; 4; 5 ];;
- : int list = [0; 1; 2; 3; 4; 5]
@@ -216,6 +235,7 @@ There are symbols of special importance with respect to lists:
- The list constructor operator, written `::` and pronounced “cons,” is used to add a value at the head of a list.
Together, they are the basic means to build a list and access the data it stores. For instance, here is how lists are built by successively applying the cons (`::`) operator:
+
```ocaml
# 3 :: [];;
- : int list = [3]
@@ -228,6 +248,7 @@ Together, they are the basic means to build a list and access the data it stores
```
Pattern matching provides the basic means to access data stored inside a list.
+
```ocaml
# match [1; 2; 3] with
| x :: u -> x
@@ -248,6 +269,7 @@ In the above expressions, `[1; 2; 3]` is the value that is matched over. Each ex
#### Options
The `option` type is also a polymorphic type. Option values can store any kind of data or represent the absence of any such data. Option values can only be constructed in two different ways: either `None`, when no data is available, or `Some`, otherwise.
+
```ocaml
# None;;
- : 'a option = None
@@ -260,6 +282,7 @@ The `option` type is also a polymorphic type. Option values can store any kind o
```
Here is an example of pattern matching on an option value:
+
```ocaml
# match Some 42 with None -> raise Exit | Some x -> x;;
- : int = 42
@@ -283,6 +306,7 @@ Operations on results are provided by the [`Result`](/manual/api/Result.html) mo
### Tuples
Here is a tuple containing two values, also known as a pair.
+
```ocaml
# (3, 'K');;
- : int * char = (3, 'K')
@@ -293,6 +317,7 @@ That pair contains the integer `3` and the character `'K'`; its type is `int * c
This generalises to tuples with 3 or more elements. For instance, `(6.28, true, "hello")` has type `float * bool * string`. The types `int * char` and `float * bool * string` are called _product types_. The `*` symbol is used to denote types bundled together in products.
The predefined function `fst` returns the first element of a pair, while `snd` returns the second element of a pair.
+
```ocaml
# fst (3, 'g');;
- : int = 3
@@ -302,6 +327,7 @@ The predefined function `fst` returns the first element of a pair, while `snd` r
```
In the standard library, both are defined using pattern matching. Here is how a function extracts the third element of the product of four types:
+
```ocaml
# let f x = match x with (h, i, j, k) -> j;;
val f : 'a * 'b * 'c * 'd -> 'c =
@@ -314,6 +340,7 @@ Note that types `int * char * bool`, `int * (char * bool)`, and `(int * char) *
### Functions
The type of functions from type `m` to type `n` is written `m -> n`. Here are a few examples:
+
```ocaml
# fun x -> x * x;;
- : int -> int =
@@ -348,7 +375,7 @@ The following example shows that the identity function can apply to arguments of
val f : 'a -> 'a =
# f 9;;
-- : int = 81
+- : int = 9
# f "hello";;
- : string = "hello"
@@ -367,6 +394,7 @@ val g : int -> int =
Executable OCaml code consists primarily of functions, so it's beneficial to make them as concise and clear as possible. The function `g` is defined here using a shorter, more common, and maybe more intuitive syntax.
In OCaml, functions may terminate without returning the expected type value by throwing an exception (of type `exn`), which does not appear in its type. There is no way to know if a function may raise an exception without inspecting its code.
+
```ocaml
# raise;;
- : exn -> 'a' =
@@ -375,6 +403,7 @@ In OCaml, functions may terminate without returning the expected type value by t
Exceptions are discussed in the [Error Handling](/docs/error-handling) guide.
Functions may have several parameters.
+
```ocaml
# fun s r -> s ^ " " ^ r;;
- : string -> string -> string =
@@ -392,6 +421,7 @@ Like the product type symbol `*`, the function type symbol `->` is not associati
Uniquely, the type `unit` has only one value. It is written `()` and pronounced “unit.”
The `unit` type has several uses. Mainly, it serves as a token when a function does not need to be passed data or doesn't have any data to return once it has completed its computation. This happens when functions have side effects such as OS-level I/O. Functions need to be applied to something for their computation to be triggered, and they also must return something. When nothing meaningful can be passed or returned, `()` should be used.
+
```ocaml
# read_line;;
- : unit -> string =
@@ -401,6 +431,7 @@ The `unit` type has several uses. Mainly, it serves as a token when a function d
```
The function `read_line` reads an end-of-line terminated sequence of characters from standard input and returns it as a string. Reading input begins when `()` is passed.
+
```ocaml
# read_line ();;
foo bar
@@ -432,6 +463,7 @@ Variants are also called [_tagged unions_](https://en.wikipedia.org/wiki/Tagged_
The simplest form of a variant type corresponds to an [enumerated type](https://en.wikipedia.org/wiki/Enumerated_type). It is defined by an explicit list of named values. Defined values are called constructors and must be capitalised.
For example, here is how variant data types could be defined to represent Dungeons & Dragons character classes and alignment.
+
```ocaml
# type character_class =
| Barbarian
@@ -471,6 +503,7 @@ ordering is defined on values following the definition order (e.g., `Druid
< Ranger`).
Pattern matching can be performed on the types defined above:
+
```ocaml
# let rectitude_to_french = function
| Evil -> "Mauvais"
@@ -487,6 +520,7 @@ Note that:
#### Constructors With Data
It is possible to wrap data in constructors. The following type has several constructors with data (e.g., `Hash of string`) and some without (e.g., `Head`). It represents the different means to refer to a Git [revision](https://git-scm.com/docs/gitrevisions).
+
```ocaml
# type commit =
| Hash of string
@@ -508,6 +542,7 @@ type commit =
```
Here is how to convert a `commit` to a `string` using pattern matching:
+
```ocaml
# let commit_to_string = function
| Hash sha -> sha
@@ -521,6 +556,7 @@ val commit_to_string : commit -> string =
```
Above, the `function …` construct is used instead of the `match … with …` construct used previously:
+
```ocaml
let commit_to_string' x = match x with
| Hash sha -> sha
@@ -532,9 +568,11 @@ let commit_to_string' x = match x with
| Merge_head -> "MERGE_HEAD";;
val commit_to_string' : commit -> string =
```
+
We need to pass an inspected expression to the `match … with …` construct. The `function …` is a special form of an anonymous function that takes a parameter and forwards it to a `match … with …` construct, as shown above.
**Warning**: Wrapping product types with parentheses turns them into a single parameter.
+
```ocaml
# type t =
| C1 of int * bool
@@ -559,6 +597,7 @@ The constructor `C1` has two parameters of type `int` and `bool`, whilst the con
A variant definition referring to itself is recursive. A constructor may wrap data from the type being defined.
This is the case for the following definition, which can be used to store JSON values.
+
```ocaml
# type json =
| Null
@@ -581,6 +620,7 @@ type json =
Both constructors `Array` and `Object` contain values of type `json`.
Functions defined using pattern matching on recursive variants are often recursive too. This function checks if a name is present in a whole JSON tree:
+
```ocaml
# let rec has_field name = function
| Array u ->
@@ -606,12 +646,14 @@ type 'a option = None | Some of 'a
```
The predefined type `list` is polymorphic in the same sense. It is a variant with two constructors and can hold data of any type. Here is how it is defined in the standard library:
+
```ocaml
# #show list;;
type 'a list = [] | (::) of 'a * 'a list
```
The only magic here is turning constructors into symbols, which we don't cover in this tutorial. The types `bool` and `unit` also are regular variants, with the same magic:
+
```ocaml
# #show unit;;
type unit = ()
@@ -621,6 +663,7 @@ type bool = false | true
```
Implicitly, product types also behave as variant types. For instance, pairs can be seen as inhabitants of this type:
+
```ocaml
# type ('a, 'b) pair = Pair of 'a * 'b;;
type ('a, 'b) pair = Pair of 'a * 'b
@@ -632,9 +675,10 @@ Even integers and floats can be seen as enumerated-like variant types, with many
In the end, the only type construction that does not reduce to a variant is the function arrow type. Pattern matching allows the inspection of values of any type, except functions.
-#### User-Defined Polymorphic
+#### User-Defined Polymorphic Types
Here is an example of a variant type that combines constructors with data, constructors without data, polymorphism, and recursion:
+
```ocaml
# type 'a tree =
| Leaf
@@ -643,6 +687,7 @@ type 'a tree = Leaf | Node of 'a * 'a tree * 'a tree
```
It can be used to represent arbitrarily labelled binary trees. Assuming such a tree would be labelled with integers, here is a possible way to compute the sum of its integers, using recursion and pattern matching.
+
```ocaml
# let rec sum = function
| Leaf -> 0
@@ -651,6 +696,7 @@ val sum : int tree -> int =
```
Here is how the map function can be defined in this type:
+
```ocaml
# let rec map f = function
| Leaf -> Leaf
@@ -677,6 +723,7 @@ issue - "The polymorphic variants tutorial is unreleased, so the best at this po
Records are like tuples in that several values are bundled together. In a tuple, elements are identified by their position in the corresponding product type. They are either first, second, third, or at some other position. In a record, each element has a name and a value. This name-value pair is known as a field. That's why record types must be declared before being used.
For instance, here is the definition of a record type meant to partially represent a Dungeons & Dragons character class. Please note that the following code is dependent upon the definitions earlier in this tutorial. Ensure you have entered the definitions in the [Enumerated Data Types](/docs/basic-data-types#enumerated-data-types) section.
+
```ocaml
# type character = {
name : string;
@@ -695,9 +742,11 @@ type character = {
armor_class : int;
}
```
+
Values of type `character` carry the same data as inhabitants of this product: `string * int * string * character_class * character_alignment * int`.
Access the fields by using the dot notation, as shown:
+
```ocaml
# let ghorghor_bey = {
name = "Ghôrghôr Bey";
@@ -722,6 +771,7 @@ val ghorghor_bey : character =
```
To construct a new record with some field values changed without typing in the unchanged fields we can use record update syntax as shown:
+
```ocaml
# let togrev = { ghorghor_bey with name = "Togrev"; level = 20; armor_class = -6 };;
val togrev : character =
@@ -730,6 +780,7 @@ val togrev : character =
```
Note that records behave like single constructor variants. That allows pattern matching on them.
+
```ocaml
# match ghorghor_bey with { level; _ } -> level;;
- : int = 17
@@ -740,6 +791,7 @@ Note that records behave like single constructor variants. That allows pattern m
#### Type Aliases
Just like values, any type can be given a name.
+
```ocaml
# type latitude_longitude = float * float;;
type latitude_longitude = float * float
@@ -750,6 +802,7 @@ This is mostly useful as a means of documentation or to shorten long type expres
#### Function Parameter Aliases
Function parameters can also be given a name with pattern matching for tuples and records.
+
```ocaml
(* Tuples as parameters *)
# let tuple_sum (x, y) = x + y;;
@@ -780,6 +833,7 @@ val meaning_of_life : int option -> int option =
## A Complete Example: Mathematical Expressions
This example shows how to represent simple mathematical expressions like `n * (x + y)` and multiply them out symbolically to get `n * x + n * y`:
+
```ocaml env=expr
# type expr =
| Plus of expr * expr (* a + b *)
@@ -796,6 +850,7 @@ type expr =
```
The expression `n * (x + y)` would be written:
+
```ocaml env=expr
# let e = Times (Var "n", Plus (Var "x", Var "y"));;
val e : expr = Times (Var "n", Plus (Var "x", Var "y"))
@@ -803,6 +858,7 @@ val e : expr = Times (Var "n", Plus (Var "x", Var "y"))
Here is a function that prints out `Times (Var "n", Plus (Var "x", Var "y"))`
as something more like `n * (x + y)`:
+
```ocaml env=expr
# let rec to_string = function
| Plus (e1, e2) -> "(" ^ to_string e1 ^ " + " ^ to_string e2 ^ ")"
@@ -815,6 +871,7 @@ val to_string : expr -> string =
We can write a function to multiply out expressions of the form `n * (x + y)`
or `(x + y) * n`, and for this we will use a nested pattern:
+
```ocaml env=expr
# let rec distrib = function
| Times (e1, Plus (e2, e3)) ->
@@ -832,6 +889,7 @@ val distrib : expr -> expr =
```
This is how it can be used:
+
```ocaml env=expr
# e |> distrib |> to_string |> print_endline;;
((n * x) + (n * y))
@@ -852,6 +910,7 @@ the remaining patterns with a simple `e -> e` rule.)
The reverse operation, i.e., factorising out common subexpressions, can be
implemented in a similar fashion. The following version only works for the top-level expression.
+
```ocaml env=expr
# let top_factorise = function
| Plus (Times (e1, e2), Times (e3, e4)) when e1 = e3 ->
diff --git a/data/tutorials/language/0it_02_loops_and_recursion.md b/data/tutorials/language/0it_02_loops_and_recursion.md
index 67ac29be8e..aed6145030 100644
--- a/data/tutorials/language/0it_02_loops_and_recursion.md
+++ b/data/tutorials/language/0it_02_loops_and_recursion.md
@@ -16,6 +16,7 @@ it's an example of how to write the code.
OCaml supports a rather limited form of the familiar `for` loop:
+
```ocaml
for variable = start_value to end_value do
expression
@@ -25,6 +26,7 @@ for variable = start_value downto end_value do
expression
done
```
+
A simple but real example from LablGtk:
@@ -33,6 +35,7 @@ for i = 1 to n_jobs () do
do_next_job ()
done
```
+
In OCaml, `for` loops are just shorthand for writing:
@@ -64,6 +67,7 @@ Line 1, characters 20-21:
Warning 10 [non-unit-statement]: this expression should have type unit.
- : unit = ()
```
+
Functional programmers tend to use recursion instead of explicit loops,
and it's wise to regard **for** loops with suspicion since it can't return anything,
hence OCaml's relatively powerless **for** loop. We talk about recursion
@@ -77,6 +81,7 @@ while boolean-condition do
expression
done
```
+
As with `for` loops, the language doesn't provide a way to break out
of a `while` loop, except by throwing an exception, so this means that
`while` loops have fairly limited use. Again, remember that functional
@@ -84,7 +89,7 @@ programmers like recursion, so `while` loops are second-class citizens
in OCaml.
If you stop to consider `while` loops, you may see that they aren't really
-any use at all, except in conjunction with our old friend *references*.
+any use at all except in conjunction with our old friend *references*.
Let's imagine that OCaml didn't have references for a moment:
@@ -97,6 +102,7 @@ let quit_loop = false in
(* how do I set quit_loop to true ?!? *)
done
```
+
Remember that `quit_loop` is not a real "variable." The let-binding
just makes `quit_loop` shorthand for `false`. This means the `while`
loop condition (shown in red) is always true, and the loop runs on
@@ -123,7 +129,7 @@ If you want to loop over a list, don't be an imperative programmer and
reach for your trusty six-shooter Mr. `For` Loop! OCaml has some better
and faster ways to loop over lists, and they are all located in the
`List` module. In fact, there are dozens of good functions in `List`, but
-I'll only talk about the most useful ones here.
+we'll restrict ourselves to the most useful ones in this chapter.
First off, let's define a list for us to use:
@@ -190,8 +196,8 @@ For operating over two lists at the same time, there are "-2" variants
of some of these functions, namely `iter2`, `map2`, `for_all2`,
`exists2`.
-The `map` and `filter` functions operate on individual list elements in
-isolation. **Fold** is a more unusual operation that is best
+While `map` and `filter` functions operate on individual list elements in
+isolation, **Fold** is a more unusual operation that is best
thought about as "inserting an operator between each element of the
list." Suppose I wanted to add all the numbers in my list together. In
hand-waving terms, I want to insert a plus (+) sign between the
@@ -269,7 +275,7 @@ this:
### Approach 1
Get the length of the file and read it all at once using the
-`really_input` method. This is the simplest, but it might not work on
+[`really_input`](https://ocaml.org/manual/api/Stdlib.html#VALreally_input) method. This is the simplest, but it might not work on
channels that are not really files (e.g., reading keyboard input), which
is why we have two other approaches.
@@ -361,7 +367,7 @@ has a polymorphic type, so it will unify with whatever value is returned
by the `with` branch.
Here's the recursive version. Notice that it's *shorter* than Approach
-2, but it's not so easy to understand, for imperative programmers at least:
+2, but it's not so easy to understand (for imperative programmers at least):
```ocaml
(* Read whole file: Approach 3 *)
@@ -423,6 +429,7 @@ directory:
End_of_file -> None;;
val readdir_no_ex : dir_handle -> string option =
```
+
The type of `readdir_no_ex` is this. Recall our earlier discussion about
null pointers.
@@ -560,6 +567,7 @@ let rec loop () =
| base case -> []
| recursive case -> element :: loop ()
```
+
Compare this to our previous `range` function. The pattern of recursion
is exactly the same:
@@ -581,6 +589,7 @@ let rec read_directory path =
else
Leaf file
```
+
All that remains now to make this a working program is a little bit of
code to call `read_directory` and display the result:
@@ -601,6 +610,7 @@ let rec loop () =
| base case -> []
| recursive case -> element :: loop ()
```
+
The key here is actually the use of the match / base case / recursive
case pattern. In this example (finding the maximum element in a list),
we'll have two base cases and one recursive case. But before we
@@ -651,6 +661,7 @@ Let's now code those rules above to get a working function:
max x (list_max remainder);;
val list_max : 'a list -> 'a =
```
+
I've added comments so you can see how the rules / special cases we
decided upon above really correspond to lines of code.
@@ -666,6 +677,7 @@ Exception: Failure "list_max called on empty list".
# list_max [5; 4; 3; 2; 1; 100];;
- : int = 100
```
+
Notice how the solution proposed is both (a) very different from the
imperative for-loop solution, and (b) much more closely tied to the
problem specification. Functional programmers will tell you that it's
@@ -686,6 +698,7 @@ Let's look at the `range` function again for about the twentieth time:
else a :: range (a+1) b;;
val range : int -> int -> int list =
```
+
I'm going to rewrite it slightly to make something about the structure
of the program clearer (still the same function however):
@@ -696,6 +709,7 @@ of the program clearer (still the same function however):
a :: result;;
val range : int -> int -> int list =
```
+
Let's call it:
```ocaml
@@ -704,6 +718,7 @@ Let's call it:
# List.length (range 1 1000000);;
Stack overflow during evaluation (looping recursion?).
```
+
Hmmm ... at first sight this looks like a problem with recursive
programming, and hence with the whole of functional programming! If you
write your code recursively instead of iteratively then you necessarily
@@ -724,6 +739,7 @@ let rec loop () =
(* do something *)
loop ()
```
+
Because the recursive call to `loop ()` happens last,
`loop` is tail-recursive and the compiler will turn the whole thing into
a `while` loop.
@@ -760,6 +776,7 @@ let rec range2 a b accum =
else
(* ... *)
```
+
If `a > b` (i.e., if we've reached the end of the recursion), then stop
and return the result (`accum`).
@@ -773,6 +790,7 @@ tail-recursive:
else range2 (a + 1) b (a :: accum);;
val range2 : int -> int -> int list -> int list =
```
+
There's only one slight problem with this function. It constructs the
list backwards! However, this is easy to rectify by redefining range as:
@@ -780,6 +798,7 @@ list backwards! However, this is easy to rectify by redefining range as:
# let range a b = List.rev (range2 a b []);;
val range : int -> int -> int list =
```
+
It works this time, although it's a bit slow to run because it really
does have to construct a list with a million elements in it:
@@ -787,6 +806,7 @@ does have to construct a list with a million elements in it:
# List.length (range 1 1000000);;
- : int = 1000000
```
+
The following implementation is twice as fast as the previous one,
because it does not need to reverse a list:
@@ -799,6 +819,7 @@ val range2 : int -> int -> int list -> int list =
range2 a b [];;
val range : int -> int -> int list =
```
+
That was a brief overview of tail recursion, but in real world
situations, determining if a function is tail-recursive can be quite
hard.
@@ -886,6 +907,7 @@ let you:
Line 1, characters 1-23:
Error: The record field name is not mutable
```
+
References, with which we should be familiar by now, are implemented
using records with a mutable `contents` field. Check out the definition
in `Stdlib`:
@@ -927,6 +949,7 @@ val a : int array = [|0; 0; 0; 0; 0; 0; 0; 0; 0; 0|]
# a;;
- : int array = [|0; 1; 2; 3; 4; 5; 6; 7; 8; 9|]
```
+
Notice the syntax for writing arrays: `[| element; element; ... |]`
The OCaml compiler was designed with heavy numerical processing in mind
diff --git a/data/tutorials/language/0it_04_higher_order_functions.md b/data/tutorials/language/0it_04_higher_order_functions.md
index 8af2ae0f6e..c47b35e7d2 100644
--- a/data/tutorials/language/0it_04_higher_order_functions.md
+++ b/data/tutorials/language/0it_04_higher_order_functions.md
@@ -266,7 +266,7 @@ Sometimes it helps to apply _parts_ of a function in different orders, and somet
This is what we call currying and uncurrying:
* A curried `add` function will be called like `add x y`.
-* An uncurried `add` functoin will be called liked `add (x, y)`. Note how this is really just one argument!
+* An uncurried `add` function will be called liked `add (x, y)`. Note how this is really just one argument!
Before we get to some examples, let's define some helper functions that will help us curry and uncurry functions.
@@ -444,16 +444,16 @@ let c = baz (bar (foo ())) in
(* ... *)
```
-But this is not so easy read sometimes, especially as the number of functions grows, as it goes from the inside out.
+But this is not so easy to read sometimes, especially as the number of functions grows, as it goes from the inside out.
-To avoid this we have use the `|>` operator:
+To avoid this we have to use the `|>` operator:
```ocaml
let c = foo () |> bar |> baz in
(* ... *)
```
-This operator translates to the exact same nested calls we would've done by hand, and is really no magic. It is defined as a function:
+This operator translates to the exact same nested calls we would've done by hand (there is really no magic to it). It is defined as a function:
```ocaml
(* the pipeline operator *)
@@ -482,6 +482,9 @@ Thanks to OCaml currying functions by default, it is practical to _partially app
This is true for functions that have the most important argument in the last position (which we call **t-last**) and for functions that use labeled arguments and allow the most important argument to be passed last by passing all the names arguments first (which we usually call **t-first**).
+**Note**:
+The "t" in "**t-first**" and "**t-last**" stand for "**target argument**", as in "**target argument first**" and "**target argument last**".
+
These two cases sound very similar, but have a big practical difference when it comes to usability. Let's revisit our example above using labeled argument versions of those functions:
```ocaml
@@ -514,13 +517,13 @@ We usually think of iteration when we think of looping, and going through collec
But in OCaml the pattern for iteration can be extended to other kinds of data types, like optional values or results, or trees and lazy sequences.
-Iterating in OCaml means that if there is a value (or more), we'd like to apply a function to it.
+Iterating in OCaml means that if there is one value (or more), we'd like to apply a function to it.
#### Iterating over Lists
-A list in OCaml is a linked-list that is composed by a head (the first element) and a tail (the rest of the list).
+A list in OCaml is a linked-list that is composed of a head (the first element) and a tail (the rest of the list).
-We can iterate over lists by pattern matching on then. When doing so, we either get an empty list (`[]`), or we get a pattern with a head and a tail (`n :: rest`). On the branch with a head and a tail, we can directly use the head value and apply a function to it, and then recurse with the tail.
+We can iterate over lists by pattern matching on them. When doing so, we either get an empty list (`[]`), or we get a pattern with a head and a tail (`n :: rest`). On the branch with a head and a tail we can directly use the head value and apply a function to it and then recurse with the tail.
```ocaml
let rec print_nums nums =
@@ -574,7 +577,7 @@ This is how `Option.iter` and `Result.iter` are defined in the standard library.
#### Iterating over Maps and Sets
-Larger collections of data like maps and sets, are also common in OCaml. We have dedicated modules for them but they have a _functor_ interface. This means you can't really use `Set` or `Map` directly, but you have to call the module-level function `Set.Make` to create your own custom version of the Set module for the specific types you want to store in it.
+Larger collections of data like maps and sets are also common in OCaml. We have dedicated modules for them but they have a _functor_ interface. This means you can't really use `Set` or `Map` directly, but you have to call the module-level function `Set.Make` to create your own custom version of the Set module for the specific types you want to store in it.
Once you create your Set or Map module, you'll find they provide functions to convert their values into lists.
@@ -593,7 +596,7 @@ let iter_map map fn = iter IntMap.bindings map fn ;;
let iter_set set fn = iter StringSet.elements set fn ;;
```
-You'll notice that we did not use pattern-matching this time around to iterate over the values of the Map or the Set directly. This is because the representation of Sets and Maps is private.
+You'll notice that we did not use pattern-matching this time around to iterate over the values of the Map or the Set directly. This is because the representation of Sets and Maps is private.
The actual implementation of iteration functions for Maps and Sets does use pattern-matching under the hood.
@@ -623,7 +626,7 @@ This is almost exactly how `Seq.iter` is defined in the standard library.
So far we've seen how to iterate over data types from the standard library. Now we'll see how to iterate over our own data type for trees.
-We'll define our tree type to include 2 constructors. One for a leaf node, which is a node at the _end_ of the tree. The other one for nodes that have children.
+We'll define our tree type to include 2 constructors. One is for a leaf node (which is a node at the _end_ of the tree), and the other is for nodes that have children.
```ocaml
type 'value tree =
@@ -673,7 +676,7 @@ This is called _mapping_.
Mapping lists is very similar to iterating over them. We pattern match on a list, get the head of it, run a function over it, and recurse over the body.
-The main difference is that instead of throwing away the resulting value from running our function over the elemnts, we will _reconstruct_ a list from it.
+The main difference is that instead of throwing away the resulting value from running our function over the elements, we will _reconstruct_ a list from it.
```ocaml
let rec map list fn =
@@ -788,7 +791,7 @@ let rec fold_tree tree fn acc =
;;
```
-And voila! Our function now types correctly and we can use it to reduce our trees down to any value.
+And voila! Our function now type-checks correctly and we can use it to reduce our trees down to any value.
### Sorting
@@ -820,6 +823,7 @@ Most OCaml modules include a `compare` function that can be pass in to `sort`:
```ocaml
let int_array = [|3;0;100|];;
+
Array.sort Int.compare int_array;;
List.sort String.compare ["z";"b";"a"];;
@@ -831,7 +835,7 @@ List.sort Bool.compare [true;false;false];;
One last common higher-order pattern in functional programming is the ability to _join_ data from within. For historical reasons, this is normally called a _bind_.
-For example, if we have a list, and map over it with a function that returns a list, then we'll have a list of lists. Sometimes we want this, but some times we would rather the new list was _flattened_ instead of _nested_.
+For example, if we have a list and we map over it with a function that returns a list, then we'll have a list of lists. Sometimes we want this, but sometimes we would rather the new list was _flattened_ instead of _nested_.
To do this with lists we can use the `concat_map` function, which looks like this:
diff --git a/data/tutorials/language/0it_05_labels.md b/data/tutorials/language/0it_05_labels.md
index 77d1876116..7218e02a79 100644
--- a/data/tutorials/language/0it_05_labels.md
+++ b/data/tutorials/language/0it_05_labels.md
@@ -15,12 +15,14 @@ Throughout this tutorial, the code is written in UTop. In this document paramete
## Passing Labelled Arguments
The function `Option.value` from the standard library has a parameter labelled `default`.
+
```ocaml
# Option.value;;
- : 'a option -> default:'a -> 'a =
```
Labelled arguments are passed using a tilde `~` and can be placed at any position and in any order.
+
```ocaml
# Option.value (Some 10) ~default:42;;
- : int = 10
@@ -42,6 +44,7 @@ Error: Syntax error
## Labelling Parameters
Here is how to name parameters in a function definition:
+
```ocaml
# let rec range ~first:lo ~last:hi =
if lo > hi then []
@@ -54,6 +57,7 @@ The parameters of `range` are named
- `first` and `last` when calling the function; these are the labels.
Here is how `range` is used:
+
```ocaml
# range ~first:1 ~last:10;;
- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
@@ -63,6 +67,7 @@ Here is how `range` is used:
```
It is possible to use a shorter syntax when using the same name as label and parameter.
+
```ocaml
# let rec range ~first ~last =
if first > last then []
@@ -75,6 +80,7 @@ At parameter definition `~first` is the same as `~first:first`. Passing argument
## Passing Optional Arguments
Optional arguments can be omitted. When passed, a tilde `~` or a question mark `?` must be used. They can be placed at any position and in any order.
+
```ocaml
# let sum ?(init=0) u = List.fold_left ( + ) init u;;
val sum : ?init:int -> int list -> int =
@@ -87,6 +93,7 @@ val sum : ?init:int -> int list -> int =
```
It is also possible to pass optional arguments as values of type `option`. This is done using a question mark when passing the argument.
+
```ocaml
# sum [0; 1; 2; 3; 4; 5] ?init:(Some 100);;
- : int = 115
@@ -98,6 +105,7 @@ It is also possible to pass optional arguments as values of type `option`. This
## Defining Optional Parameters With Default Values
In the previous section, we've defined a function with an optional parameter without explaining how it works. Let's look at a different variant of this function:
+
```ocaml
# let sum ?init:(x=0) u = List.fold_left ( + ) x u;;
val sum : ?init:int -> int list -> int =
@@ -128,6 +136,7 @@ val get_ok : ?exn:('a -> exn) -> ('b, 'a) result -> 'b =
An optional parameter can be declared without specifying a default value.
+
```ocaml
# let sub ?(pos=0) ?len:len_opt s =
let default = String.length s - pos in
@@ -146,6 +155,7 @@ When an optional parameter isn't given a default value, its type inside the func
This enables the following usages:
+
```ocaml
# sub ~len:5 ~pos:2 "immutability";;
- : string = "mutab"
@@ -161,6 +171,7 @@ This enables the following usages:
```
It is possible to use the same name for the `len` parameter and label name.
+
```ocaml
# let sub ?(pos=0) ?len s =
let default = String.length s - pos in
@@ -174,6 +185,7 @@ val sub : ?pos:int -> ?len:int -> string -> string =
Let's compare two possible variants of the `String.concat` function from the standard library which has type `string -> string list -> string`.
In the first version, the optional separator is the last declared parameter.
+
```ocaml
# let concat_warn ss ?(sep="") = String.concat sep ss;;
Line 1, characters 15-18:
@@ -192,6 +204,7 @@ val concat_warn : string list -> ?sep:string -> string =
```
In the second version, the optional separator is the first declared parameter.
+
```ocaml
# let concat ?(sep="") ss = String.concat sep ss;;
val concat : ?sep:string -> string list -> string =
@@ -232,6 +245,7 @@ val range : int -> first:int -> last:int -> int list =
## Function with Only Optional Arguments
When all parameters of a function need to be optional, a dummy, positional and occurring last parameter must be added. The unit `()` value comes in handy for this. This is what is done here.
+
```ocaml
# let hello ?(who="world") () = "hello, " ^ who;;
val hello : ?who:string -> string =
@@ -260,6 +274,7 @@ Without the unit parameter, the `optional argument cannot be erased` warning wou
## Forwarding an Optional Argument
Passing an optional argument with a question mark sign `?` allows forwarding it without unwrapping. These examples reuse the `sub` function defined in the [Optional Arguments Without Default Values](#optional-arguments-without-default-values) section.
+
```ocaml
# let take ?len s = sub ?len s;;
val take : ?len:int -> string -> string =
diff --git a/data/tutorials/language/0it_06_imperative.md b/data/tutorials/language/0it_06_imperative.md
index 14e2e04487..30b77c12a0 100644
--- a/data/tutorials/language/0it_06_imperative.md
+++ b/data/tutorials/language/0it_06_imperative.md
@@ -40,6 +40,7 @@ In the following sections, we introduce OCaml's language features for dealing wi
## References
There is a special kind of value, called _reference_, whose contents can be updated:
+
```ocaml
# let d = ref 0;;
val d : int ref = {contents = 0}
@@ -79,6 +80,7 @@ The update takes place as a [side effect](https://en.wikipedia.org/wiki/Side_eff
# ( ! );;
- : 'a ref -> 'a =
```
+
The dereference operator is a function that takes a reference and returns its contents.
Refer to the [Operators](/docs/operators) documentation for more information on how unary and binary operators work in OCaml.
@@ -90,6 +92,7 @@ When working with mutable data in OCaml,
## Mutable Record Fields
Any field in a record can be tagged using the `mutable` keyword. Such a field can be updated.
+
```ocaml
# type book = {
series : string;
@@ -112,6 +115,7 @@ For instance, here is how a bookshop could track its inventory:
* Field `stock` is mutable because this value changes with each sale or restocking.
Such a database should have an entry like this:
+
```ocaml
# let vol_7 = {
series = "Murderbot Diaries";
@@ -126,6 +130,7 @@ val vol_7 : book =
```
When the bookshop receives a delivery of 10 of these books, we update the mutable `stock` field:
+
```ocaml
# vol_7.stock <- vol_7.stock + 10;;
- : unit = ()
@@ -142,9 +147,12 @@ Mutable record fields are updated using the left arrow symbol `<-`. In the expre
In contrast to references, there is no special syntax to dereference a mutable record field.
+**Remark**: the left arrow symbol `<-` for mutating mutable record field values is not an operator function, like the assignment operator `( := )` is for `refs`. It is rather a _construct_ of the language, it has no type.
+
### Remark: References Are Single Field Records
In OCaml, references are records with a single mutable field:
+
```ocaml
# #show_type ref;;
type 'a ref = { mutable contents : 'a; }
@@ -153,6 +161,7 @@ type 'a ref = { mutable contents : 'a; }
The type `'a ref` is a record with a single field `contents`, which is marked with the `mutable` keyword.
Since references are single field records, we can define functions `create`, `assign`, and `deref` using the mutable record field update syntax:
+
```ocaml
# let create v = { contents = v };;
val create : 'a -> 'a ref =
@@ -184,6 +193,7 @@ The functions:
## Arrays
In OCaml, an array is a mutable, fixed-size data structure that can store a sequence of elements of the same type. Arrays are indexed by integers, provide constant-time access, and allow the update of elements.
+
```ocaml
# let g = [| 2; 3; 4; 5; 6; 7; 8 |];;
val g : int array = [|2; 3; 4; 5; 6; 7; 8|]
@@ -207,6 +217,7 @@ For a more detailed discussion on arrays, see the [Arrays](/docs/arrays) tutoria
## Byte Sequences
The `bytes` type in OCaml represents a fixed-length, mutable byte sequence. In a value of type `bytes`, each element has 8 bits. Since characters in OCaml are represented using 8 bits, `bytes` values are mutable `char` sequences. Like arrays, byte sequences support indexed access.
+
```ocaml
# let h = Bytes.of_string "abcdefghijklmnopqrstuvwxyz";;
val h : bytes = Bytes.of_string "abcdefghijklmnopqrstuvwxyz"
@@ -251,6 +262,7 @@ These attributes need to be set correctly (i.e., turn off echoing and disable ca
We read characters from standard input using the `input_char` function from the OCaml standard library.
Below is the first implementation. If you're working in macOS, run `#require "unix";;` first to avoid an `Unbound module error`.
+
```ocaml
# let get_char () =
let open Unix in
@@ -272,6 +284,7 @@ In this implementation, we update the fields of `termio`
* after `input_char`, restoring the initial values.
Here is the second implementation:
+
```ocaml
# let get_char () =
let open Unix in
@@ -321,9 +334,11 @@ Hello,
world!
- : int = 42
```
+
In this example, the first two expressions are `print_endline` function calls, which produce side effects (printing to the console), and the last expression is simply the integer `42`, which becomes the value of the entire sequence. The `;` operator is used to separate these expressions.
**Remark** Even though it's called the sequence operator, the semicolon is not truly an operator because it is not a function of type `unit -> 'a -> 'a`. It is rather a _construct_ of the language. It allows adding a semicolon at the end of a sequence expression.
+
```ocaml
# (); 42; ;;
- : int = 42
@@ -340,18 +355,20 @@ Imagine we want to write a function that:
2. Updates the reference's contents to _2 × (n + 1)_
This is arguably convoluted and does not work:
+
```ocaml
# let f r = r := incr r; 2 * !r;;
Error: This expression has type unit but an expression was expected of type int
```
But here is how it can be made to work:
+
```ocaml
# let f r = r := begin incr r; 2 * !r end;;
val f : int ref -> unit =
```
-The error came from assign `:=`, which associates stronger than a semicolon `;`. Here is what we want to do, in order:
+The error came from the assign operator `:=`, which associates stronger than a semicolon `;`. Here is what we want to do, in order:
1. Increment `r`
2. Compute `2 * !r`
3. Assign into `r`
@@ -359,6 +376,7 @@ The error came from assign `:=`, which associates stronger than a semicolon `;`.
Remember the value of a semicolon-separated sequence is the value of its last expression. Grouping the first two steps with `begin … end` fixes the error.
**Fun fact**: `begin … end` and parentheses are literally the same:
+
```ocaml
# begin end;;
- : unit = ()
@@ -367,12 +385,14 @@ Remember the value of a semicolon-separated sequence is the value of its last ex
### `if … then … else …` and Side Effects
In OCaml, `if … then … else …` is an expression.
+
```ocaml
# 6 * if "foo" = "bar" then 5 else 5 + 2;;
- : int = 42
```
A conditional expression return type can be `unit` if both branches are too.
+
```ocaml
# if 0 = 1 then print_endline "foo" else print_endline "bar";;
bar
@@ -380,6 +400,7 @@ bar
```
The above can also be expressed this way:
+
```ocaml
# print_endline (if 0 = 1 then "foo" else "bar");;
bar
@@ -387,18 +408,21 @@ bar
```
The `unit` value `()` can serve as a [no-op](https://en.wikipedia.org/wiki/Noop) when only one branch has something to execute.
+
```ocaml
# if 0 = 1 then print_endline "foo" else ();;
- : unit = ()
```
But OCaml also allows writing `if … then … ` expressions without an `else` branch, which is the same as the above.
+
```ocaml
# if 0 = 1 then print_endline "foo";;
- : unit = ()
```
In parsing, conditional expressions groups more than sequencing:
+
```ocaml
# if true then print_endline "A" else print_endline "B"; print_endline "C";;
A
@@ -409,7 +433,8 @@ C
Here `; print_endline "C"` is executed after the whole conditional expression, not after `print_endline "B"`.
If you want to have two prints in a conditional expression branch, use `begin … end`.
- ```ocaml
+
+```ocaml
# if true then
print_endline "A"
else begin
@@ -421,6 +446,7 @@ A
```
Here is an error you might encounter:
+
```ocaml
# if true then
print_endline "A";
@@ -435,6 +461,7 @@ Failing to group in the first branch results in a syntax error. What's before th
### For Loop
A `for` loop is an expression of type `unit`. Here, `for`, `to`, `do`, and `done` are keywords.
+
```ocaml
# for i = 0 to 5 do Printf.printf "%i\n" i done;;
0
@@ -455,6 +482,7 @@ Here:
The iteration evaluates the body expression (which may contain `i`) until `i` reaches `5`.
The body of a `for` loop must be an expression of type `unit`:
+
```ocaml
# let j = [| 2; 3; 4; 5; 6; 7; 8 |];;
val j : int array = [|2; 3; 4; 5; 6; 7; 8|]
@@ -468,6 +496,7 @@ Warning 10 [non-unit-statement]: this expression should have type unit.
When you use the `downto` keyword (instead of the `to` keyword), the counter decreases on every iteration of the loop.
`for` loops are convenient to iterate over and modify arrays:
+
```ocaml
# let sum = ref 0 in
for i = 0 to Array.length j - 1 do sum := !sum + j.(i) done;
@@ -476,6 +505,7 @@ When you use the `downto` keyword (instead of the `to` keyword), the counter dec
```
**Note:** Here is how to do the same thing using an iterator function:
+
```ocaml
# let sum = ref 0 in Array.iter (fun i -> sum := !sum + i) j; !sum;;
- : int = 35
@@ -484,6 +514,7 @@ When you use the `downto` keyword (instead of the `to` keyword), the counter dec
### While Loop
A `while` loop is an expression of type `unit`. Here, `while`, `do`, and `done` are keywords.
+
```ocaml
# let i = ref 0 in
while !i <= 5 do
@@ -512,6 +543,7 @@ In this example, the `while` loop continues to execute as long as the value held
Throwing the `Exit` exception is a recommended way to immediately exit from a loop.
The following example uses the `get_char` function we defined earlier (in the section [Example: `get_char` Function](#example-get_char-function)).
+
```ocaml
# try
print_endline "Press Escape to exit";
@@ -528,15 +560,16 @@ This `while` loop echoes characters typed on the keyboard. When the ASCII `Escap
### References Inside Closures
-In the following example, the function `create_counter` returns a closure that hides a mutable reference `n`. This closure captures the environment where `n` is defined and can modify `n` each time it's invoked. The `n` reference is "hidden" within the closure, encapsulating its state.
+In the following example, the function `create_counter` returns a closure that “hides” a mutable reference `counter`. This closure captures the environment where `counter` is defined and can modify `counter` each time it's invoked. The `counter` reference is “hidden” within the closure, encapsulating its state.
```ocaml
# let create_counter () =
- let n = ref 0 in
- fun () -> incr n; !n;;
+ let counter = ref (-1) in
+ fun () -> incr counter; !counter;;
val create_counter : unit -> unit -> int =
```
-First, we define a function named `create_counter` that takes no arguments. Inside `create_counter`, a reference `n` is initialised with the value 0. This reference will hold the state of the counter. Next, we define a closure that takes no arguments (fun () ->). The closure increments the value of `n` (the counter) using `incr n`, then returns the current value of `n` using `!n`.
+
+First, we define a function named `create_counter` that takes no arguments. Inside `create_counter`, a reference `counter` is initialised with the value -1. This reference will hold the state of the counter. Next, we define a closure that takes no arguments `(fun () ->)`. The closure increments `counter` using `incr counter`, then returns the current value of `counter` using `!counter`.
```ocaml
# let c1 = create_counter ();;
@@ -545,24 +578,26 @@ val c1 : unit -> int =
# let c2 = create_counter ();;
val c2 : unit -> int =
```
-Now, we shall create a closure `c1` that encapsulates a counter. Calling `c1 ()` will increment the counter associated with `c1` and return its current value. Similarly, we create another closure `c2` with its own independent counter.
+
+Now, using partial application, we create two closure `c1` and `c2` that encapsulates a counter. Calling `c1 ()` will increment the counter associated with `c1` and return its current value. Similarly, calling `c2 ()` will update its own independent counter.
```ocaml
# c1 ();;
-- : int = 1
+- : int = 0
# c1 ();;
-- : int = 2
+- : int = 1
# c2 ();;
-- : int = 1
+- : int = 0
# c1 ();;
-- : int = 3
+- : int = 2
```
-Calling `c1 ()` increments the counter associated with `c1` and returns its current value. Since this is the first call, the counter starts at 1. Another call to `c1 ()` increments the counter again, so it returns 2.
-Calling `c2 ()` increments the counter associated with `c2`. Since `c2` has its own independent counter, it starts at 1. Another call to `c1 ()` increments its counter, resulting in 3.
+Calling `c1 ()` increments the counter associated with `c1` and returns its current value. Since this is the first call, the counter starts at 0. Another call to `c1 ()` increments the counter again, so it returns 1.
+
+Calling `c2 ()` increments the counter associated with `c2`. Since `c2` has its own independent counter, it starts at 0. Another call to `c1 ()` increments its counter, resulting in 2.
## Recommendations for Mutable State and Side Effects
@@ -571,6 +606,7 @@ Functional and imperative programming styles are often used together. However, n
### Good: Function-Encapsulated Mutability
Here is a function that computes the sum of an array of integers.
+
```ocaml
# let sum m =
let result = ref 0 in
@@ -592,6 +628,7 @@ Some applications maintain some state while they are running. Here are a couple
- A cache.
The following is a toy line editor, using the `get_char` function [defined earlier](#example-getchar-function). It waits for characters on standard input and exits on end-of-file, carriage return, or newline. Otherwise, if the character is printable, it prints it and records it in a mutable list used as a stack. If the character is the delete code, the stack is popped and the last printed character is erased.
+
```ocaml
# let record_char state c =
(String.make 1 c, c :: state);;
@@ -635,6 +672,7 @@ This is a possible way to handle an application-wide state. As in the [Function-
### Good: Precomputing Values
Let's imagine you store angles as fractions of the circle in 8-bit unsigned integers, storing them as `char` values. In this system, 64 is 90 degrees, 128 is 180 degrees, 192 is 270 degrees, 256 is full circle, and so on. If you need to compute cosine on those values, an implementation might look like this:
+
```ocaml
# let char_cos c =
c |> int_of_char |> float_of_int |> ( *. ) (Float.pi /. 128.0) |> cos;;
@@ -642,6 +680,7 @@ val char_cos : char -> float =
```
However, it is possible to make a faster implementation by precomputing all the possible values in advance. There are only 256 of them, which you'll see listed after the first result below:
+
```ocaml
# let char_cos_tab = Array.init 256 (fun i -> i |> char_of_int |> char_cos);;
val char_cos_tab : float array =
@@ -658,9 +697,7 @@ However, instead of precomputing everything, memoization uses a cache that is po
* are found in the cache (it is a hit) and the stored result is returned, or they
* are not found in the cache (it's a miss), and the result is computed, stored in the cache, and returned.
-You can find a concrete example of memoization and a more in-depth explanation in the chapter on [Memoization](https://cs3110.github.io/textbook/chapters/ds/memoization.html) of "OCaml Programming: Correct + Efficient + Beautiful."
-
-
+You can find a concrete example of memoization and an in-depth explanation in the chapter on [Memoization](https://ocaml.org/docs/memoization) of "OCaml Programming: Correct + Efficient + Beautiful."
### Good: Functional by Default
@@ -668,16 +705,17 @@ By default, OCaml programs should be written in a mostly functional style. This
It is possible to use an imperative programming style without losing the benefits of type and memory safety. However, it doesn't usually make sense to only program in an imperative style. Not using functional programming idioms at all would result in non-idiomatic OCaml code.
-Most existing modules provide an interface meant to be used in a functional way. Some would require the development and maintenance of [wrapper libraries](https://en.wikipedia.org/wiki/Wrapper_library) to be used in an imperative setting and such use would in many cases be inefficient.
+Most existing modules provide an interface meant to be used in a functional way. Some require the development and maintenance of [wrapper libraries](https://en.wikipedia.org/wiki/Wrapper_library) to be used in an imperative setting and such use results in inefficient code.
### It Depends: Module State
A module may expose or encapsulate a state in several different ways:
1. Good: expose a type representing a state, with state creation or reset functions
-1. It depends: only expose state initialisation, which implies there only is a single state
+1. It depends: only expose state initialisation, which implies there is only a single state
1. Bad: mutable state with no explicit initialisation function or no name referring to the mutable state
For example, the [`Hashtbl`](/manual/api/Hashtbl.html) module provides an interface of the first kind. It has the type `Hashtbl.t` representing mutable data. It also exposes `create`, `clear`, and `reset` functions. The `clear` and `reset` functions return `unit`. This strongly signals the reader that they perform the side-effect of updating the mutable data.
+
```ocaml
# #show Hashtbl.t;;
type ('a, 'b) t = ('a, 'b) Hashtbl.t
@@ -696,7 +734,10 @@ On the other hand, a module may define mutable data internally impacting its beh
### Bad: Undocumented Mutation
+**Note**: The following example code will purposely not run in your REPL; the function `Array.truncate` is not defined. It is provided as an example to contemplate and avoid.
+
Here's an example of bad code:
+
```ocaml
# let partition p k =
let m = Array.copy k in
@@ -715,7 +756,6 @@ Here's an example of bad code:
Error: Unbound value Array.truncate
```
-**Note:** This example will not run in the REPL, since the function `Array.truncate` is not defined.
To understand why this is bad code, assume that the function `Array.truncate` has type `int -> 'a array -> 'a array`. It behaves such that `Array.truncate 3 [5; 6; 7; 8; 9]` returns `[5; 6; 7]`, and the returned array physically corresponds to the 3 first cells of the input array.
@@ -739,6 +779,9 @@ GOTCHA: This is the dual of the previous anti-pattern. “Mutable in disguise”
### Bad: Undocumented Side Effects
Consider this code:
+
+**Note**: The following example will not run in your REPL; there is no module `Analytics` defined. It is provided as an example to contemplate. [Analytics](https://en.wikipedia.org/wiki/Web_analytics) are remote monitoring libraries.
+
```ocaml
# module Array = struct
include Stdlib.Array
@@ -749,8 +792,6 @@ Consider this code:
Error: Unbound module Analytics
```
-**Note:** This code will not run because there is no module called `Analytics`. [Analytics](https://en.wikipedia.org/wiki/Web_analytics) are remote monitoring libraries.
-
A module called `Array` is defined; it shadows and includes the [`Stdlib.Array`](/manual/api/Array.html) module. See the [Module Inclusion](docs/modules#module-inclusion) part of the [Modules](docs/modules) tutorial for details about this pattern.
To understand why this code is bad, figure out that `Analytics.collect` is a function that makes a network connection to transmit data to a remote server.
@@ -762,6 +803,7 @@ If you're writing functions with non-obvious side effects, don't shadow existing
### Bad: Side Effects Depending on Order of Evaluation
Consider the following code:
+
```ocaml
# let id_print s = print_string (s ^ " "); s;;
val id_print : string -> string =
@@ -781,6 +823,7 @@ In the second line, we apply `id_print` to the arguments `"Monday"`, `"Tuesday"`
Since the evaluation order for function arguments in OCaml is not explicitly defined, the order in which the `id_print` side effects take place is unreliable. In this example, the arguments are evaluated from right to left, but this could change in future compiler releases.
This issue also arises when applying arguments to variant constructors, building tuple values, or initialising record fields. Here, it is illustrated on a tuple value:
+
```ocaml
# let r = ref 0 in ((incr r; !r), (decr r; !r));;
- : int * int = (0, -1)
@@ -788,10 +831,11 @@ This issue also arises when applying arguments to variant constructors, building
The value of this expression depends on the order of subexpression evaluation. Since this order is not specified, there is no reliable way to know what this value is. At the time of writing this tutorial, the evaluation produced `(0, -1)`, but if you see something else, it is not a bug. Such an unreliable value must a avoided.
-To ensure that evaluation takes place in a specific order, use the means to put expressions in sequences. Check the [Evaluating Expressions in Sequence](#evaluating-expressions-in-sequence) section.
+To ensure that evaluation takes place in a specific order, use the means to put expressions in sequences using either `let … in` expressions or the semi-colon sequence operator (`;`). Check the [Evaluating Expressions in Sequence](#evaluating-expressions-in-sequence) section.
```ocaml
let hello () = print_endline "Hello from Athens"
```
Here is the file `berlin.ml`:
+
```ocaml
let () = Athens.hello ()
@@ -43,17 +45,21 @@ let () = Athens.hello ()
To compile them using [Dune](https://dune.build/), at least two
configuration files are required:
* The `dune-project` file contains project-wide configuration.
+
```lisp
(lang dune 3.7)
```
+
* The `dune` file contains actual build directives. A project may have several
`dune` files, one per directory containing things to build. This single line is
sufficient in this example:
+
```lisp
(executable (name berlin))
```
After you create those files, build and run them:
+
```bash
$ opam exec -- dune build
@@ -88,6 +94,7 @@ anything the module provides.
If you are using a module heavily, you might want to `open` it. This brings the
module's definitions into scope. In our example, `berlin.ml` could have been
written:
+
```ocaml
open Athens
@@ -98,6 +105,7 @@ Using `open` is optional. Usually, we don't open a module like `List` because it
provides names other modules also provide, such as [`Array`](/manual/api/Array.html) or `Option`. Modules
like `Printf` provide names that aren't subject to conflicts, such as `printf`.
Placing `open Printf` at the top of a file avoids writing `Printf.printf` repeatedly.
+
```ocaml
open Printf
let data = ["a"; "beautiful"; "day"]
@@ -110,6 +118,7 @@ let () = List.iter (printf "%s\n") data
at the top of every file. Refer to Dune documentation if you need to opt-out.
You can open a module inside a definition, using the `let open ... in` construct:
+
```ocaml
# let list_sum_sq m =
let open List in
@@ -118,6 +127,7 @@ val list_sum_sq : int -> int =
```
The module access notation can be applied to an entire expression:
+
```ocaml
# let array_sum_sq m =
Array.(init m Fun.id |> map (fun i -> i * i) |> fold_left ( + ) 0);;
@@ -139,6 +149,7 @@ interface. By default, when no corresponding `.mli` file is provided, an
implementation has a default interface where everything is public.
Copy the `athens.ml` file into `cairo.ml` and change its contents:
+
```ocaml
let message = "Hello from Cairo"
@@ -146,6 +157,7 @@ let hello () = print_endline message
```
As it is, `Cairo` has the following interface:
+
```ocaml
val message : string
@@ -158,6 +170,7 @@ acts as a mask over the module's implementation. The `cairo.ml` file defines
Filenames without extensions must be the same.
To turn `message` into a private definition, don't list it in the `cairo.mli` file:
+
```ocaml
val hello : unit -> unit
@@ -178,11 +191,13 @@ let () = Cairo.hello ()
Update the `dune` file to allow this example's compilation aside from the
previous one.
+
```lisp
(executables (names berlin delhi))
```
Compile and execute both programs:
+
```shell
$ opam exec -- dune exec ./berlin.exe
Hello from Athens
@@ -192,6 +207,7 @@ Hello from Cairo
```
You can check that `Cairo.message` is not public by attempting to compile a `delhi.ml` file containing:
+
```ocaml
let () = print_endline Cairo.message
```
@@ -239,12 +255,14 @@ let dalet_of = function
```
Update file `dune` to have three targets; two executables: `berlin` and `delhi`; and a library `exeter`.
+
```lisp
(executables (names berlin delhi) (modules athens berlin cairo delhi))
(library (name exeter) (modules exeter))
```
Run the `opam exec -- dune utop` command. This triggers `Exeter`'s compilation, launches `utop`, and loads `Exeter`.
+
```ocaml
# open Exeter;;
@@ -253,12 +271,14 @@ type aleph = Ada | Alan | Alonzo
```
Type `aleph` is public. Values can be created or accessed.
+
```ocaml
# #show bet;;
Unknown element.
```
Type `bet` is private. It is not available outside of the implementation where it is defined, here `Exeter`.
+
```ocaml
# #show gimel;;
type gimel
@@ -277,6 +297,7 @@ val gimel_of_bool : bool -> gimel
```
Type `gimel` is _abstract_. Values can be created or manipulated, but only as function results or arguments. Just the provided functions `gimel_of_bool`, `gimel_flip`, and `gimel_to_string` or polymorphic functions can receive or return `gimel` values.
+
```ocaml
# #show dalet;;
type dalet = private Dennis of int | Donald of string | Dorothy
@@ -337,6 +358,7 @@ executable:
To define a submodule's interface, we can provide a _module signature_. This
is done in this second version of the `florence.ml` file:
+
```ocaml
module Hello : sig
val print : unit -> unit
@@ -353,6 +375,7 @@ The first version made `Florence.Hello.message` public. In this version it can't
### Module Signatures are Types
The role played by module signatures to implementations is akin to the role played by types to values. Here is a third possible way to write file `florence.ml`:
+
```ocaml
module type HelloType = sig
val print : unit -> unit
@@ -389,6 +412,7 @@ module Unit :
```
The OCaml compiler tool chain can be used to dump an `.ml` file's default interface.
+
```shell
$ ocamlc -i cairo.ml
val message : string
@@ -423,6 +447,7 @@ module from another `.ml` file, we need to add `open Extlib` at the beginning.
## Stateful Modules
A module may have an internal state. This is the case for the `Random` module from the standard library. The functions `Random.get_state` and `Random.set_state` provide read and write access to the internal state, which is nameless and has an abstract type.
+
```ocaml
# let s = Random.get_state ();;
val s : Random.State.t =
diff --git a/data/tutorials/language/1ms_01_functors.md b/data/tutorials/language/1ms_01_functors.md
index 16dffbcedf..9cceaf187a 100644
--- a/data/tutorials/language/1ms_01_functors.md
+++ b/data/tutorials/language/1ms_01_functors.md
@@ -12,7 +12,7 @@ prerequisite_tutorials:
In this tutorial, we look at how to apply functors and how to write functors. We also show some use cases involving functors.
-As suggested by the name, a _functor_ is almost like a function. However, while functions are between values, functors are between modules. A functor has a module as a parameter and returns a module as a result. A functor in OCaml is a parametrised module, not to be confused with a [functor in mathematics](https://en.wikipedia.org/wiki/Functor).
+As suggested by the name, a _functor_ is almost like a function. However, while the inputs and outputs of functions are values, functors are between modules. A functor has a module as a parameter and returns a module as a result. A functor in OCaml is a parametrised module, not to be confused with a [functor in mathematics](https://en.wikipedia.org/wiki/Functor).
**Note**: The files illustrating this tutorial are available as a [Git repo](https://github.com/ocaml-web/ocamlorg-docs-functors).
@@ -25,12 +25,14 @@ $ mkdir funkt; cd funkt
```
Place the following in the file **`dune-project`**:
+
```lisp
(lang dune 3.7)
(package (name funkt))
```
The content of the file **`dune`** should be this:
+
```lisp
(executable
(name funkt)
@@ -47,6 +49,7 @@ Check that this works using the `opam exec -- dune exec funkt` command. It shoul
The standard library contains a [`Set`](/manual/api/Set.html) module which is designed to handle sets. This module enables you to perform operations such as union, intersection, and difference on sets. You may check the [Set](/docs/sets) tutorial to learn more about this module, but it is not required to follow the present tutorial.
To create a set module for a given element type (which allows you to use the provided type and its associated [functions](/manual/api/Set.S.html)), it's necessary to use the functor `Set.Make` provided by the `Set` module. Here is a simplified version of `Set`'s interface:
+
```ocaml
module type OrderedType = sig
type t
@@ -67,7 +70,6 @@ Here is how this reads (starting from the bottom, then going up):
Here is an example of how to use `Set.Make`:
**`funkt.ml`**
-
```ocaml
module StringCompare = struct
type t = string
@@ -86,11 +88,13 @@ This defines a module `Funkt.StringSet`. What `Set.Make` needs are:
another type, here `string`. If that other type is also called `t`, the compiler
will trigger an error _“The type abbreviation t is cyclic”_. This can be worked
around by adding the `nonrec` keyword to the type definition, like this:
+
```ocaml
type nonrec t = t
```
This can be simplified using an _anonymous module_ expression:
+
```ocaml
module StringSet = Set.Make(struct
type t = string
@@ -105,6 +109,7 @@ However, since the module `String` already defines
- Function `compare` of type `t -> t -> bool` compares two strings
This can be simplified even further into this:
+
```ocaml
module StringSet = Set.Make(String)
```
@@ -178,6 +183,7 @@ That's the case for the sets, maps, and hash tables provided by the standard lib
* The functor returns a module that implements what is promised, as described by the result interface
Here is the module's signature that the functors `Set.Make` and `Map.Make` expect:
+
```ocaml
module type OrderedType = sig
type t
@@ -186,6 +192,7 @@ end
```
Here is the module's signature that the functor `Hashtbl.Make` expects:
+
```ocaml
module type HashedType = sig
type t
@@ -208,6 +215,7 @@ There are many kinds of [heap](https://en.wikipedia.org/wiki/Heap_(data_structur
The kind of data structures and algorithms used to implement a heap is not discussed in this document.
The common prerequisite to implement any heap is a means to compare the elements they contain. That's the same signature as the parameter of `Set.Make` and `Map.Make`:
+
```ocaml
module type OrderedType = sig
type t
@@ -216,6 +224,7 @@ end
```
Using such a parameter, a heap implementation must provide at least this interface:
+
```ocaml
module type HeapType = sig
type elt
@@ -321,7 +330,7 @@ Check the program's behaviour using `opam exec -- dune exec funkt < dune`.
[Dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) is a way to parametrise over a dependency.
-Here is a refactoring of the module `IterPrint` to use this technique:
+Here is a refactoring of the module `IterPrint` to use dependency injection:
**`iterPrint.ml`**
```ocaml
@@ -340,12 +349,11 @@ module Make(Dep: Iterable) : S with type 'a t := 'a Dep.t = struct
end
```
-The module `IterPrint` is refactored into a functor that takes a module providing the function `iter` as a parameter. The `with type 'a t := 'a Dep.t` constraint means the type `t` from the parameter `Dep` replaces the type `t` in the result module. This allows `f`'s type to use `Dep`'s `t` type. With this refactoring, `IterPrint` only has one dependency. At its compilation time, no implementation of function `iter` is available yet.
+The module `IterPrint` is refactored into a functor that takes a module providing the function `iter` as a parameter. The `with type 'a t := 'a Dep.t` is called a "destructive type substitution", and it is a constraint that means the type `t` from the parameter `Dep` replaces the type `t` in the result module. This allows `f`'s type to use `Dep`'s `t` type. With this refactoring, `IterPrint` only has one dependency. At its compilation time, no implementation of function `iter` is available yet.
-**Note**: An OCaml interface file (`.mli`) must be a module, not a functor. Functors must be embedded inside modules. Therefore, it is customary to call them `Make`.
+**Note**: An OCaml file (`.ml`) defines a module, not a functor. Functors must be embedded inside modules. It is customary to call the functor `Make`. This allows writing `module IntSet = Set.Make(Int)`.
**`funkt.ml`**
-
```ocaml
module StringSet = Set.Make(String)
module IterPrint = IterPrint.Make(List)
@@ -425,7 +433,9 @@ module Make(Dep: Iterable) : S with type 'a t := 'a Dep.t = struct
end
```
-In the example above, `t` from `with type` takes precedence over the local `t`, which only has a local scope. Don't shadow names too often because it makes the code harder to understand.
+In the example above, `t` from `with type` takes precedence over the local `t`, which only has a local scope.
+
+**Warning**: Don't shadow names too often because it makes the code harder to understand.
## Write a Functor to Extend Modules
@@ -496,11 +506,11 @@ Modules can hold a state. Functors can provide a means to initialise stateful mo
**`random.ml`**
```ocaml
-module type SeedType : sig
+module type SeedType = sig
val v : int array
end
-module type S : sig
+module type S = sig
val reset_state : unit -> unit
val bits : unit -> int
@@ -535,6 +545,7 @@ end
```
Create this file and launch `utop`.
+
```ocaml
# #mod_use "random.ml";;
diff --git a/data/tutorials/language/1ms_02_dune.md b/data/tutorials/language/1ms_02_dune.md
index a5e56f692e..586679e9e0 100644
--- a/data/tutorials/language/1ms_02_dune.md
+++ b/data/tutorials/language/1ms_02_dune.md
@@ -15,9 +15,16 @@ Dune provides several means to arrange modules into libraries. We look at Dune's
This tutorial uses the [Dune](https://dune.build) build tool. Make sure you have version 3.7 or later installed.
+In the following toy project, we create an OCaml library for the World Meteorological Organization, with clouds as its subject.
+
+We use unique terms for different elements of our project to avoid ambiguity. For instance, the directory containing the file `cloud.ml` isn't named `cloud`. A different term is used. We also use the Spanish word "nube" and the [Nahuatl](https://en.wikipedia.org/wiki/Nahuatl) word "mixtli," where both terms mean "cloud."
+
+**Note**: The other terms we use are classifications of clouds. These include "[cumulus](https://en.wikipedia.org/wiki/Cumulus_cloud)" (fluffy), "[nimbus](https://en.wiktionary.org/wiki/nimbus)" (precipitating), "[cumulonimbus](https://en.wikipedia.org/wiki/Cumulonimbus_cloud)" (fluffy and precipitating), "[nimbostratus](https://en.wikipedia.org/wiki/Nimbostratus_cloud)" (flat, amorphous and precipitating), and "[altocumulus](https://en.wikipedia.org/wiki/Altocumulus_cloud)" (high altitude and fluffy).
+
## Minimum Project Setup
This section details the structure of an almost-minimum Dune project setup. Check [Your First OCaml Program](/docs/your-first-program) for automatic setup using the `dune init proj` command.
+
```shell
$ mkdir mixtli; cd mixtli
```
@@ -62,6 +69,7 @@ let () =
```
Here is the resulting output:
+
```shell
$ opam exec -- dune exec nube
Nimbostratus (Ns)
@@ -70,6 +78,7 @@ Cumulonimbus (Cb)
Here is the directory contents:
+
```shell
$ tree
.
@@ -80,15 +89,17 @@ $ tree
```
Dune stores the files it creates, and a copy of the sources, in a directory named `_build`. You don't need to edit anything there. In a project managed using Git, the `_build` directory should be ignored
+
```shell
$ echo _build >> .gitignore
```
+
You can also configure your editor or IDE to ignore it too.
In OCaml, each `.ml` file defines a module. In the `mixtli` project, the file `cloud.ml` defines the `Cloud` module, the file `wmo.ml` defines the `Wmo` module that contains two submodules: `Stratus` and `Cumulus`.
Here are the different names:
-* `mixtli` is the project's name (it means *cloud* in Nahuatl).
+* `mixtli` is the project's name (it means *cloud* in [Nahuatl](https://en.wikipedia.org/wiki/Nahuatl)
* `cloud.ml` is the OCaml source file's name, referred as `cloud` in the `dune` file.
* `nube` is the executable command's name (it means *cloud* in Spanish).
* `Cloud` is the name of the module associated with the file `cloud.ml`.
@@ -96,6 +107,7 @@ Here are the different names:
* `wmo-clouds` is the name of the package built by this project.
The `dune describe` command allows having a look at the project's module structure. Here is its output:
+
```lisp
((root /home/cuihtlauac/caml/mixtli-dune)
(build_context _build/default)
@@ -123,6 +135,7 @@ The `dune describe` command allows having a look at the project's module structu
In OCaml, a library is a collection of modules. By default, when Dune builds a library, it wraps the bundled modules into a module. This allows having several modules with the same name, inside different libraries, in the same project. That feature is known as [_namespaces_](https://en.wikipedia.org/wiki/Namespace) for module names. This is similar to what modules do for definitions; they avoid name clashes.
Dune creates libraries from directories. Let's look at an example. Here the `lib` directory contains its sources. This is different from the Unix standard, where `lib` stores compiled library binaries.
+
```shell
$ mkdir lib
```
@@ -138,6 +151,7 @@ The `lib` directory is populated with the following source files:
```ocaml
val nimbus : string
```
+
**`lib/cumulus.ml`**
```ocaml
@@ -155,6 +169,7 @@ let nimbus = "Nimbostratus (Ns)"
```
All the modules found in the `lib` directory are bundled into the `Wmo` module. This module is the same as what we had in the `wmo.ml` file. To avoid redundancy, we delete it:
+
```shell
$ rm wmo.ml
```
@@ -209,6 +224,7 @@ Using a wrapper file makes several things possible:
By default, Dune builds a library from the modules found in the same directory as the `dune` file, but it doesn't look into subdirectories. It is possible to change this behaviour.
In this example, we create subdirectories and move files there.
+
```shell
$ mkdir lib/cumulus lib/stratus
$ mv lib/cumulus.ml lib/cumulus/m.ml
@@ -243,6 +259,7 @@ The `include_subdirs qualified` stanza works recursively, except on subdirectori
It is possible to start an empty Dune project from a single file.
Create a fresh directory.
+
```shell
$ mkdir foo.dir; cd foo.dir
```
@@ -325,6 +342,7 @@ let nimbus = "Nimbostratus (Ns)"
```
In this setup, running `dune utop` allows discovering what's available.
+
```ocaml
# #show Wmo;;
module Wmo : sig module Cumulus = Wmo.Cumulus module Stratus = Wmo.Stratus end
@@ -376,6 +394,7 @@ Wrapping can be disabled in Dune's configuration.
In that case, the “library” only contains the modules `Cumulus` and `Stratus`,
bundled together, side by side. Check the following in `dune utop`, twice. Once
with file `lib/wmo.ml` unchanged, and a second time after deleting it.
+
```ocaml
# #show Cumulus;;
module Cumulus : sig val nimbus : string val altus : string end
diff --git a/data/tutorials/language/3ds_00_options.md b/data/tutorials/language/3ds_00_options.md
index 26e59f78c6..703cef6c4b 100644
--- a/data/tutorials/language/3ds_00_options.md
+++ b/data/tutorials/language/3ds_00_options.md
@@ -9,6 +9,7 @@ category: "Data Structures"
## Introduction
An [option](https://en.wikipedia.org/wiki/Option_type) value wraps another value or contains nothing if there isn't anything to wrap. The predefined type `option` represents such values.
+
```ocaml
# #show option;;
@@ -16,6 +17,7 @@ type 'a option = None | Some of 'a
```
Here are option values:
+
```ocaml
# Some 42;;
- : int option = Some 42
@@ -33,6 +35,7 @@ The option type is useful when the lack of data is better handled as the special
## Exceptions _vs_ Options
The function `Sys.getenv : string -> string` from the OCaml standard library queries the value of an environment variable; however, it throws an exception if the variable is not defined. On the other hand, the function `Sys.getenv_opt : string -> string option` does the same, except it returns `None` if the variable is not defined. Here is what may happen if we try to access an undefined environment variable:
+
```ocaml
# Sys.getenv "UNDEFINED_ENVIRONMENT_VARIABLE";;
Exception: Not_found.
@@ -50,6 +53,7 @@ Most of the functions in this section, as well as other useful ones, are provide
### Map Over an Option
Using pattern matching, it is possible to define functions, allowing to work with option values. Here is `map` of type `('a -> 'b) -> 'a option -> 'b option`. It allows to apply a function to the wrapped value inside an `option`, if present:
+
```ocaml
let map f = function
| None -> None
@@ -57,6 +61,7 @@ let map f = function
```
In the standard library, this is `Option.map`.
+
```ocaml
# Option.map (fun x -> x * x) (Some 3);;
- : int option = Some 9
@@ -77,6 +82,7 @@ let join = function
```
In the standard library, this is `Option.join`.
+
```ocaml
# Option.join (Some (Some 42));;
- : int option = Some 42
@@ -91,6 +97,7 @@ In the standard library, this is `Option.join`.
### Access the Content of an Option
The function `get` of type `'a option -> 'a` allows access to the value contained inside an `option`.
+
```ocaml
let get = function
| Some v -> v
@@ -98,6 +105,7 @@ let get = function
```
Beware, `get o` throws an exception if `o` is `None`. To access the content of an `option` without the risk of raising an exception, the function `value` of type `'a option -> 'a -> 'a` can be used:
+
```ocaml
let value opt ~default = match opt with
| Some v -> v
@@ -111,6 +119,7 @@ In the standard library, these functions are `Option.get` and `Option.value`.
### Fold an Option
The function `fold` of type `none:'a -> some:('b -> 'a) -> 'b option -> 'a` can be seen as a combination of `map` and `value`
+
```ocaml
let fold ~none ~some o = o |> Option.map some |> Option.value ~default:none
```
@@ -118,6 +127,7 @@ let fold ~none ~some o = o |> Option.map some |> Option.value ~default:none
In the standard library, this function is `Option.fold`.
The `Option.fold` function can be used to implement a fall-back logic without writing pattern matching. For instance, here is a function that turns the contents of the `$PATH` environment variable into a list of strings, or the empty list if undefined. This version uses pattern matching:
+
```ocaml
# let path () =
let split_on_colon = String.split_on_char ':' in
@@ -129,6 +139,7 @@ val path : unit -> string list =
```
Here is the same function, using `Option.fold`:
+
```ocaml
# let path () =
let split_on_colon = String.split_on_char ':' in
@@ -141,6 +152,7 @@ val path : unit -> string list =
The `bind` function of type `'a option -> ('a -> 'b option) -> 'b option` works a bit like `map`. But whilst `map` expects a function parameter `f` that returns an unwrapped value of type `b`, `bind` expects an `f` that returns a value already wrapped in an option `'b option`.
Here, we display the type of `Option.map`, with parameters flipped and show a possible implementation of `Option.bind`.
+
```ocaml
# Fun.flip Option.map;;
- : 'a option -> ('a -> 'b) -> 'b option =
diff --git a/data/tutorials/language/3ds_01_arrays.md b/data/tutorials/language/3ds_01_arrays.md
index a831822a5d..7a1cfbb5e6 100644
--- a/data/tutorials/language/3ds_01_arrays.md
+++ b/data/tutorials/language/3ds_01_arrays.md
@@ -139,6 +139,7 @@ These functions derive a single value from the whole array. For example, they ca
To sort an array, we can use the `Array.sort` function. This function takes as arguments:
- a comparison function
- an array
+
It sorts the provided array in place and in ascending order, according to the provided comparison function. Sorting performed by `Array.sort` modifies the content of the provided array, which is why it returns `unit`. For example, to sort the array `even_numbers` created above, we can use:
```ocaml
diff --git a/data/tutorials/language/3ds_02_maps.md b/data/tutorials/language/3ds_02_maps.md
index c18d6c2140..e24bbf2c71 100644
--- a/data/tutorials/language/3ds_02_maps.md
+++ b/data/tutorials/language/3ds_02_maps.md
@@ -8,18 +8,24 @@ category: "Data Structures"
## Introduction
-The [`Map`](/manual/api/Map.html) module lets you create _immutable_ key-value [association
-tables](https://en.wikipedia.org/wiki/Associative_array) for your types. Such
-maps are never modified, and every operation returns a new map instead.
-
-**Note**: Maps described in this tutorial should not be confused with map
-functions such as `List.map`, `Array.map`, `Option.map`, and others. The maps
-described in this tutorial are also called dictionaries or associative tables.
-
-To use `Map`, we first have to use the [`Map.Make`](/manual/api/Map.Make.html) functor to create our custom
-map module. Refer to the [Functors](/docs/functors) for more information on
-functors. This functor has a module parameter that defines the keys' type to
-be used in the maps, and a function for comparing them.
+In the most general sense, the [`Map`](/manual/api/Map.html) module lets you create _immutable_ key-value
+[associative array](https://en.wikipedia.org/wiki/Associative_array) for your types. More concretely,
+OCaml's `Map` module is implemented using a binary search tree alogorithm to support fast lookups (of O(Log n)).
+
+**Note**: The concept of a `Map` in this tutorial refers to a data structure that stores a
+set of key-value pairs. It is sometimes called a dictionary or an association table. This
+is a disinct concept from the maps we've explored in previous lessons, such as `List.map`,
+`Array.map`, and `Option.map`, which are functions that operate over data rather than
+being data themselves.
+
+To use `Map`, we first have to use the [`Map.Make`](/manual/api/Map.Make.html) functor to
+create our custom map module. Refer to the [Functors](/docs/functors) for more information
+on functors. This functor has a module parameter that defines the keys' type to be used in
+the maps, and a function for comparing them.
+
+For an different implementation of an association table in OCaml's Standard Library, see the tutorial
+on [Hash Tables](/docs/hash-tables).
+
```ocaml
# module StringMap = Map.Make(String);;
@@ -41,8 +47,10 @@ After naming the newly-created module `StringMap`, OCaml's toplevel displays the
module's signature. Since it contains a large number of functions, the output
copied here is shortened for brevity `(...)`.
-This module doesn't define the values' type. It will be defined when we create
-our first map.
+When we created the `StringMap` module, we fed the `Map.Make` functor the `String` module to
+define the type of the map's keys, which we can observe in the `StringMap`'s signature
+(`type key string`). However, we did not yet define the value's type. The value's type will
+be defined when we create our first map.
## Creating a Map
@@ -50,17 +58,18 @@ The `StringMap` module has an `empty` value that has a type parameter `'a` in
its type: `empty : 'a t`.
This means that we can use `empty` to create new empty maps where the value is of any type.
+
+The type of the values can be specified in two ways:
+* When you create your new map with an annotation
+* By adding an element to the map:
+
+**Example 1**: By Annotation
```ocaml
# let int_map : int StringMap.t = StringMap.empty;;
val int_map : int StringMap.t =
-
-# let float_map : float StringMap.t = StringMap.empty;;
-val float_map : float StringMap.t =
```
-The type of the values can be specified in two ways:
-* When you create your new map with an annotation
-* By adding an element to the map:
+**Example 2**: By Adding an Element
```ocaml
# let int_map = StringMap.(empty |> add "one" 1);;
val int_map : int StringMap.t =
@@ -68,7 +77,8 @@ val int_map : int StringMap.t =
## Working With Maps
-Throughout the rest of this tutorial, we use the following map:
+Throughout the rest of this tutorial, we will use the following map:
+
```ocaml
# let lucky_numbers = StringMap.of_seq @@ List.to_seq [
("leostera", 2112);
@@ -81,6 +91,7 @@ val lucky_numbers : int StringMap.t =
## Finding Entries in a Map
To find entries in a map, use the `find_opt` or `find` functions:
+
```ocaml
# StringMap.find_opt "leostera" lucky_numbers;;
- : int option = Some 2112
@@ -91,7 +102,7 @@ To find entries in a map, use the `find_opt` or `find` functions:
When the searched key is present from the map:
- `find_opt` returns the associated value, wrapped in an option
-- `find` returns the associated
+- `find` returns the associated value
When the searched key is absent from the map:
- `find_opt` returns `None`
@@ -99,6 +110,7 @@ When the searched key is absent from the map:
We can also use `find_first_opt` and `find_last_opt` if we want to use a
predicate function:
+
```ocaml
# let first_under_10_chars : (string * int) option =
StringMap.find_first_opt
@@ -116,7 +128,8 @@ not just the value.
## Adding Entries to a Map
To add an entry to a map, use the `add` function that takes a key, a value, and
-a map. It returns a new map with that key-value pair added:
+the map to which it will be added. It returns a new map with that key-value pair added:
+
```ocaml
# let more_lucky_numbers = lucky_numbers |> StringMap.add "paguzar" 108;;
val more_lucky_numbers : int StringMap.t =
@@ -136,14 +149,15 @@ Note that the initial map `lucky_numbers` remains unchanged.
To remove an entry from a map, use the `remove` function, which takes a key and
a map. It returns a new map with that key's entry removed.
+
```ocaml
-# let less_lucky_numbers = lucky_numbers |> StringMap.remove "divagnz";;
-val less_lucky_numbers : int StringMap.t =
+# let fewer_lucky_numbers = lucky_numbers |> StringMap.remove "divagnz";;
+val fewer_lucky_numbers : int StringMap.t =
# StringMap.find_opt "divagnz" lucky_numbers;;
- : int option = Some 13
-# StringMap.find_opt "divagnz" less_lucky_numbers;;
+# StringMap.find_opt "divagnz" fewer_lucky_numbers;;
- : int option = None
```
@@ -174,7 +188,8 @@ You should experiment with different update functions; several behaviors are pos
## Checking if a Key is Contained in a Map
-To check if a map contains a key, use the `mem` function:
+To check if a key is a member of a map, use the `mem` function:
+
```ocaml
# StringMap.mem "paguzar" less_lucky_numbers;;
- : bool = false
@@ -182,9 +197,11 @@ To check if a map contains a key, use the `mem` function:
## Merging Maps
-To merge two maps, use the `union` function, which takes two maps, and a
-function deciding how to handle entries with identical keys. It returns a new map.
-Note that the input maps are not modified.
+To merge two maps, use the `union` function. This function takes two maps, a
+function deciding how to handle entries with identical keys, and it returns a new map.
+
+**Note**: As with all the other functions of `Map`, the input maps are not modified.
+
```ocaml
# StringMap.union;;
- : (string -> 'a -> 'a -> 'a option) ->
@@ -193,6 +210,7 @@ Note that the input maps are not modified.
```
Here are examples of duplicate key resolution functions:
+
```ocaml
# let pick_fst key v1 _ = Some v1;;
val pick_fst : 'a -> 'b -> 'c -> 'b option =
@@ -233,6 +251,7 @@ val drop : 'a -> 'b -> 'c -> 'd option =
To filter a map, use the `filter` function. It takes a predicate to filter
entries and a map. It returns a new map containing the entries satisfying the
predicate.
+
```ocaml
# let even_numbers =
StringMap.filter
@@ -244,24 +263,28 @@ val even_numbers : int StringMap.t =
## Map a Map
Map modules have a `map` function:
+
```ocaml
StringMap.map;;
- : ('a -> 'b) -> 'a StringMap.t -> 'b StringMap.t =
```
-The `lucky_numbers` map associates string keys with integer values.
+The `lucky_numbers` map associates string keys with integer values:
+
```ocaml
# lucky_numbers;;
- : int StringMap.t =
```
Using `StringMap.map`, we create a map associating keys with string values:
+
```ocaml
# let lucky_strings = StringMap.map string_of_int lucky_numbers;;
val lucky_strings : string StringMap.t =
```
The keys are the same in both maps. For each key, a value in `lucky_numbers` is converted into a value in `lucky_strings` using `string_of_int`.
+
```ocaml
# lucky_numbers |> StringMap.find "leostera" |> string_of_int;;
- : string = "2112"
@@ -275,17 +298,15 @@ The keys are the same in both maps. For each key, a value in `lucky_numbers` is
If you need to create a map with a custom key type, you can call the `Map.Make`
functor with a module of your own, provided that it implements two things:
-1. A type `t` type with no type parameters
+1. A type `t` exposing the type of the map's key
2. A function `compare : t -> t -> int` function that compares `t` values
Let's define our custom map below for non-negative numbers.
We'll start by defining a module for strings that compares them in case-insensitive way.
+
```ocaml
-# module Istring : sig
- type t
- val compare : t -> t -> int
- end = struct
+# module Istring = struct
type t = string
let compare a b = String.(compare (lowercase_ascii a) (lowercase_ascii b))
end;;
@@ -310,6 +331,13 @@ module IstringMap :
val remove : key -> 'a t -> 'a t
(* ... *)
end
+
+# let lucky_int_numbers = IstringMap.of_seq @@ List.to_seq [
+ ("leostera", 2112);
+ ("charstring88", 88);
+ ("divagnz", 13);
+ ];;
+val lucky_int_numbers : int IstringMap.t =
```
## Conclusion
diff --git a/data/tutorials/language/3ds_03_sets.md b/data/tutorials/language/3ds_03_sets.md
index 6215aea91d..e407723871 100644
--- a/data/tutorials/language/3ds_03_sets.md
+++ b/data/tutorials/language/3ds_03_sets.md
@@ -13,6 +13,7 @@ category: "Data Structures"
**Disclaimer:** The examples in this tutorial require OCaml 5.1. If you're running a previous version of OCaml , you can either use `elements` instead of `to_list`, which is a new function in OCaml 5.1, or upgrade OCaml by running `opam update`, then `opam upgrade ocaml`. Check your current version with `ocaml --version`.
If you need to work with string sets, you must invoke `Set.Make(String)`. That returns a new module.
+
```ocaml
# module StringSet = Set.Make(String);;
module StringSet :
@@ -38,6 +39,7 @@ This module also defines two types:
## Creating a Set
1. We can create an empty set using `StringSet.empty`:
+
```ocaml
# StringSet.empty ;;
- : StringSet.t =
@@ -51,6 +53,7 @@ For `StringSet.empty`, you can see that the OCaml toplevel displays the placehol
(Remember, for OCaml versions before 5.1, it will be `StringeSet.empty |> StringSet.elements;;`)
2. A set with a single element is created using `StringSet.singleton`:
+
```ocaml
# StringSet.singleton "hello";;
- : StringSet.t =
@@ -60,6 +63,7 @@ For `StringSet.empty`, you can see that the OCaml toplevel displays the placehol
```
3. Converting a list into a set using `StringSet.of_list`:
+
```ocaml
# StringSet.of_list ["hello"; "hi"];;
- : StringSet.t =
@@ -73,6 +77,7 @@ There's another relevant function `StringSet.of_seq: string Seq.t -> StringSet.t
## Working With Sets
Let's look at a few functions for working with sets using these two sets.
+
```ocaml
# let first_set = ["hello"; "hi"] |> StringSet.of_list;;
- : val first_set : StringSet.t =
@@ -189,9 +194,11 @@ You can see that this module has the intended behavior:
# CISS.singleton "hello" |> CISS.add "HELLO" |> CISS.to_list;;
- : string list = ["hello"]
```
+
The value `"HELLO"` is not added to the set because it is considered equal to the value `"hello"`, which is already contained in the set.
You can use any type for elements, as long as you define a meaningful `compare` operation.
+
```ocaml
# type color = Red | Green | Blue;;
type color = Red | Green | Blue
diff --git a/data/tutorials/language/3ds_04_hashtbl.md b/data/tutorials/language/3ds_04_hashtbl.md
index c1c4ae05f0..cc3ce4e1c5 100644
--- a/data/tutorials/language/3ds_04_hashtbl.md
+++ b/data/tutorials/language/3ds_04_hashtbl.md
@@ -15,6 +15,7 @@ create a hash table we could write:
# let my_hash = Hashtbl.create 123456;;
val my_hash : ('_weak1, '_weak2) Hashtbl.t =
```
+
The 123456 is the initial size of the hashtbl. This initial number is
just your best guess as to the amount of data that you will be putting
into the hash table. The hash table can grow if you under-estimate the
diff --git a/data/tutorials/language/3ds_05_seq.md b/data/tutorials/language/3ds_05_seq.md
index c5c282de1d..bdcc60d81c 100644
--- a/data/tutorials/language/3ds_05_seq.md
+++ b/data/tutorials/language/3ds_05_seq.md
@@ -12,7 +12,7 @@ prerequisite_tutorials:
## Introduction
Sequences are very much like lists. However, from a pragmatic perspective, one
-should imagine they may be infinite. That's the key intuition to understanding
+should imagine they can be either finite or infinite. That's the key intuition to understanding
and using sequences. To achieve this, sequence elements are computed on demand
and not stored in memory. Perhaps more frequently, sequences also allow for
reducing memory consumption from linear to constant space
@@ -23,20 +23,24 @@ Still in the intro: for people familiar with Python, I believe it would be very
One way to look at a value of type `'a Seq.t` is to consider it as a list, but it contains
a twist when it's not empty: its tail is frozen. To understand this analogy,
-consider how sequences are defined in the standard library:
+consider how sequences are defined in the Standard Library:
+
```ocaml
type 'a node =
| Nil
| Cons of 'a * 'a t
and 'a t = unit -> 'a node
```
+
This is the mutually recursive definition of two types: `Seq.node`, which is
almost the same as `list`:
+
```ocaml
type 'a list =
| []
| (::) of 'a * 'a list
```
+
and `Seq.t`, which is merely a type alias for `unit -> 'a Seq.node`. The whole
point of this definition is `Seq.Cons` second component's type, which is a
function returning a sequence while its `list` counterpart is a list. Let's
@@ -64,29 +68,54 @@ writing `fun _ -> a` or `fun () -> a`. The latter function is called a
[_thunk_](https://en.wikipedia.org/wiki/Thunk). Using this terminology, `Seq.t`
values are thunks. With the analogy used earlier, `a` is frozen in its thunk.
-Here is how to build seemingly infinite sequences of integers:
+## Constructing Sequences
+
+With this understanding, we can manually construct a sequence like so:
+
+``` ocaml
+let my_seq =
+ fun () ->
+ Seq.Cons (1, fun () -> Seq.Cons (2, fun () -> Seq.Cons (3, fun () -> Seq.Nil)))
+```
+
+**Note:** The second component of each `Seq.Con`'s tuple is a function. This has
+the effect of providing a means of acquiring a value rather than providing a
+value directly.
+
+We can also construct sequences using functions. Here is how to build an
+infinite sequence of integers:
+
```ocaml
# let rec ints n : int Seq.t = fun () -> Seq.Cons (n, ints (n + 1));;
val ints : int -> int Seq.t =
```
+
The function `ints n` looks as if building the infinite sequence `(n; n + 1; n +
2; n + 3;...)`. In reality, since machine integers have bounds, the sequence
-isn't indefinitely increasing. When reaching `max_int`, it will circle
+isn't indefinitely increasing. For technical reasons, when `max_int` is reached, it will circle
down to `min_int`.
-The OCaml standard library contains a module on sequences called
-[`Seq`](/releases/5.0/api/Seq.html). It contains a `Seq.iter` function, which
-has the same behaviour as `List.iter`. Writing this:
+The OCaml Standard Library contains a module for sequences called
+[`Seq`](/releases/api/Seq.html). It contains `Seq.int`, which we implemented above.
+
+## Iterating Over Sequences
+
+The OCaml Standard Library also contains a `Seq.iter` function, which has the
+same behavior as `List.iter`. Writing this:
+
```ocaml
# Seq.iter print_int (ints 0);;
```
+
in an OCaml toplevel means “print integers forever,” and you have to press
`Ctrl-C` to interrupt the execution. The following code is the same infinite
loop without any output:
+
```ocaml
# Seq.iter ignore (ints 0);;
```
-The key point is: it doesn't leak memory. This example is running in constant
+
+The key point is that it doesn't leak memory. This example runs in constant
space. It is effectively nothing more than an infinite loop, which can be
confirmed by monitoring the space consumption of the program and by noticing
that it spins forever without crashing. Whereas a version of this with a list
@@ -94,11 +123,12 @@ that it spins forever without crashing. Whereas a version of this with a list
proportional to the running time, and thus would crash by running out of memory
pretty quickly.
-## Example
+## Taking Parts of a Sequence
-The `Seq` module of the OCaml standard library contains the definition of the
+The `Seq` module of the OCaml Standard Library contains the definition of the
function `Seq.take`, which returns a specified number of elements from the
beginning of a sequence. Here is a simplified implementation:
+
```ocaml
let rec take n seq () =
if n <= 0 then
@@ -108,11 +138,12 @@ let rec take n seq () =
| Seq.Cons (x, seq) -> Seq.Cons (x, take (n - 1) seq)
| _ -> Seq.Nil
```
+
`take n seq` returns, at most, the `n` first elements of the sequence `seq`. If
`seq` contains less than `n` elements, an identical sequence is returned. In
particular, if `seq` is empty, or `n` is negative, an empty sequence is returned.
-Observe the first line of `take`. It is the common pattern for recursive
+Observe the first line of our `take` function. It is the common pattern for recursive
functions over sequences. The last two parameters are:
* a sequence called `seq`
* a `unit` value
@@ -124,6 +155,7 @@ seq` does not compute anything. It is a partial application and returns a
function needing a `unit` to produce a result.
This can be used to print integers without looping forever, as shown previously:
+
```ocaml
# Seq.ints 0 |> Seq.take 43 |> List.of_seq;;
- : int list =
@@ -132,24 +164,30 @@ This can be used to print integers without looping forever, as shown previously:
41; 42]
```
+## Filtering a Sequence
+
The `Seq` module also has a function `Seq.filter`:
+
```ocaml
# Seq.filter;;
- : ('a -> bool) -> 'a Seq.t -> 'a Seq.t =
```
+
It builds a sequence of elements satisfying a condition.
Using `Seq.filter`, taking inspiration from the [trial division](https://en.wikipedia.org/wiki/Trial_division) algorithm, it is possible to define a function which seemingly generates the list of all primes numbers.
+
```ocaml
let rec trial_div seq () = match seq () with
- | Seq.Cons (m, seq) -> Seq.Cons (m, trial_div (Seq.filter (fun n -> n mod m > 0) seq))
- | seq -> seq
+ | Seq.Cons (m, seq_rest) -> Seq.Cons (m, trial_div (Seq.filter (fun n -> n mod m > 0) seq_rest))
+ | Seq.Nil -> Seq.Nil
let primes = Seq.ints 2 |> trial_div;;
val trial_div : int Seq.t -> int Seq.t =
val primes : int Seq.t =
```
-For instance, here is the list of 100 first prime numbers:
+For instance, here is a list of 100 first prime numbers:
+
```ocaml
# primes |> Seq.take 100 |> List.of_seq;;
- : int list =
@@ -162,11 +200,31 @@ For instance, here is the list of 100 first prime numbers:
509; 521; 523; 541]
```
-The function `trial_div` is recursive in OCaml and common sense. It is defined
-using the `rec` keyword and calls itself. However, some call that kind of
-function [corecursive](https://en.wikipedia.org/wiki/Corecursion). This word is
-used to emphasise that, although it may not terminate, it can indefinitely
-produce valid output.
+The function `trial_div` is recursive in OCaml and can be understood if we break
+it down into its constituent parts. It is defined using the `rec` keyword, allowing the
+function to call itself. For each loop in the recursive call, it pattern-matches
+on either `Seq.Cons (m, seq)` or the end of the sequence, `Seq.Nil`.
+
+If it matches on the first branch `Seq.Cons (m, seq)`, we filter the
+remaining sequence of all integers that are divisible by `m` before recursively
+calling `trial_div` on the filtered sequence. This branch is matched on until we
+reach the end of the sequence for every recursive call.
+
+So far, we recursively traveled down our sequence until we reached the 100th
+prime number. Next, we retrace our steps up the recursive trail, wherein we
+construct our result by calling `Seq.Cons` on `m` and the previously constructed
+filtered sequence beginning with `Seq.Nil`.
+
+**Side Note**: It may be interesting to learn that `trial_div`, while it can
+colloquially be called a recursive, is an example of a kind of recursion called
+[corecursion](https://en.wikipedia.org/wiki/Corecursion). Corecursion differs
+from recursion in that it constructs results incrementally rather than consuming
+it's input incrementally. Unlike traditional recursion, which works towards a
+base case, corecursive functions must indefinitely produce values as a stream. The `trial_div` function is corecursive
+because it does not immediately compute the complete sequence of primes. Instead, it
+produces prime numbers on-demand, filtering and deferring further computation until
+more elements are requested. This allows the sequence to be processed
+incrementally rather than requiring a complete traversal upfront.
## Unfolding Sequences
@@ -176,60 +234,95 @@ instance:
* `Seq.map`
* `Seq.fold_left`
-All those are also available for [`Array`](/manual/api/Array.html), `List`, and `Set` and behave
-essentially the same. Observe that there is no `fold_right` function. Since
-OCaml 4.11, there is something which isn't (yet) available on other types:
-`unfold`. Here is how it is implemented:
+All of these kinds of higher-order functions are also available for
+[`Array`](/manual/api/Array.html), `List`, and `Set` and behave essentially the
+same. Observe that there is no `fold_right` function. Since OCaml 4.11, there is
+something which isn't (yet) available on other types: `unfold`. Here is how it
+is implemented:
+
```ocaml
let rec unfold f x () = match f x with
| None -> Seq.Nil
| Some (x, seq) -> Seq.Cons (x, unfold f seq)
```
+
And here is its type:
+
```ocaml
val unfold : ('a -> ('b * 'a) option) -> 'a -> 'b Seq.t =
```
+
Unlike previously mentioned iterators, `Seq.unfold` does not have a sequence
parameter, but a sequence result. `unfold` provides a general means to build
-sequences. The result returned by `Seq.unfold f x` is the sequence built by accumulating the results of successive calls to `f` until it returns `None`. This is:
+sequences. The result returned by `Seq.unfold f x` is the sequence built by
+accumulating the results of successive calls to `f` until it returns `None`.
+This is:
+
```
(fst p₀, fst p₁, fst p₂, fst p₃, fst p₄, ...)
```
+
where `Some p₀ = f x` and `Some pₙ₊₁ = f (snd pₙ)`.
For instance, `Seq.ints` can be implemented using `Seq.unfold` in a
fairly compact way:
+
```ocaml
# let ints = Seq.unfold (fun n -> Some (n, n + 1));;
val ints : int -> int Seq.t =
```
-
-As a fun fact, one should observe `map` over sequences can be implemented using
+As a fun fact, we can observe that a `map` over sequences can be implemented using
`Seq.unfold`. Here is how to write it:
+
```ocaml
# let map f = Seq.unfold (fun seq -> seq |> Seq.uncons |> Option.map (fun (x, seq) -> (f x, seq)));;
val map : ('a -> 'b) -> 'a Seq.t -> 'b Seq.t =
```
-Here is a quick check:
+
+We can check our `map` function by applying a square root function to a sequence:
+
```ocaml
# Seq.ints 0 |> map (fun x -> x * x) |> Seq.take 10 |> List.of_seq;;
- : int list = [0; 1; 4; 9; 16; 25; 36; 49; 64; 81]
```
+
The function `Seq.uncons` returns the head and tail of a sequence if it is not
empty. Otherwise, it returns `None`.
-Using this function:
+### Reading a File with `Seq.Unfold`
+
+For the next example, we will demonstrate the versatility of `Seq.unfold` by
+using it to read a file.
+
+Before doing so, let's define a function that reads a file's line from a
+provided channel, with the type signature needed by `Seq.unfold`.
+
```ocaml
-let input_line_opt chan =
- try Some (input_line chan, chan)
- with End_of_file -> None
+# let input_line_opt chan =
+ try Some (In_Channel.input_line chan, chan)
+ with End_of_file -> None;;
+val input_line_opt : in_channel -> (string * in_channel) option =
+```
+
+**Note**: To make the code in the next section work, create a file named "README.md" and add dummy content. We use a file generated by the following command:
+
+``` shell
+cat > README.md < Seq.unfold input_line_opt |> Seq.iter print_endline;
-close_in cin
+# let cin = open_in "README.md" in
+ cin |> Seq.unfold In_channel.input_line_opt |> Seq.iter print_endline;
+ close_in cin;;
+This is the first line.
+This is the second line.
+- : unit = ()
```
+
Although this looks like a possible way to define the [Fibonacci
sequence](https://en.wikipedia.org/wiki/Fibonacci_number):
+
```ocaml
# let rec fibs m n = Seq.cons m (fibs n (n + m));;
val fibs : int -> int -> int Seq.t =
```
+
It actually isn't. It's an unending recursion which blows away the stack.
+
```
# fibs 0 1;;
Stack overflow during evaluation (looping recursion?).
```
-This definition is behaving as expected (spot the differences, there are four):
+
+This definition is behaving as expected (spot the differences, there are four):
+
```ocaml
# let rec fibs m n () = Seq.Cons (m, fibs n (n + m));;
val fibs : int -> int -> int Seq.t =
```
+
It can be used to produce some Fibonacci numbers:
+
```ocaml
# fibs 0 1 |> Seq.take 10 |> List.of_seq;;
- : int list = [0; 1; 1; 2; 3; 5; 8; 13; 21; 34]
```
+
Why is it so? The key difference lies in the recursive call `fibs n (n + m)`. In
the former definition, the application is complete because `fibs` is provided
with all the arguments it expects. In the latter definition, the application is
partial because the `()` argument is missing. Since evaluation is
[eager](https://en.wikipedia.org/wiki/Evaluation_strategy#Eager_evaluation) in
-OCaml, in the former case, evaluation of the recursive call is triggered and a
-non-terminating looping occurs. In contrast, in the latter case, the partially
+OCaml, in the former case evaluation of the recursive call is triggered again and again, without ever terminating (this is what "looping recursion" in the error message refers to). In the latter case, the partially
applied function is immediately returned as a
[closure](https://en.wikipedia.org/wiki/Closure_(computer_programming)).
Sequences are functions, as stated by their type:
+
```ocaml
# #show Seq.t;;
type 'a t = unit -> 'a Seq.node
```
+
Functions working with sequences must be written accordingly.
* Sequence consumer: partially applied function parameter
* Sequence producer: partially applied function result
@@ -333,20 +444,26 @@ Throughout the standard library, sequences are used as a bridge to perform
conversions between many datatypes. For instance, here are the signatures of
some of those functions:
* Lists
+
```ocaml
val List.to_seq : 'a list -> 'a Seq.t
val List.of_seq : 'a Seq.t -> 'a list
```
+
* Arrays
+
```ocaml
val Array.to_seq : 'a array -> 'a Seq.t
val Array.of_seq : 'a Seq.t -> 'a array
```
+
* Strings
+
```ocaml
val String.to_seq : string -> char Seq.t
val String.of_seq : char Seq.t -> string
```
+
Similar functions are also provided for sets, maps, hash tables (`Hashtbl`), and
others. When implementing a datatype module, it is
advised to expose `to_seq` and `of_seq` functions.
@@ -365,7 +482,7 @@ There used to be a module called [`Stream`](/releases/4.13/api/Stream.html) in
the OCaml standard library. It was
[removed](https://github.com/ocaml/ocaml/pull/10482) in 2021 with the release of
OCaml 4.14. Beware books and documentation written before may still mention it.
-
```ocaml
Printf.printf "This program has been compiled by user: %s" [%get_env "USER"]
-
```
When you compile the code with the preprocessor, it will replace [%get_env "USER"] with the content of the USER environment variable. If the USER environment variable is set to "JohnDoe" for example, the line would become:
@@ -27,7 +26,6 @@ When you compile the code with the preprocessor, it will replace [%get_env "USER
```ocaml
Printf.printf "This program has been compiled by user: %s" "JohnDoe"
-
```
@@ -141,7 +139,7 @@ module files using our previously written `preprocessor.sh`:
The complexities of a programming language syntax makes it very hard to
manipulate text in a way that is tied to the syntax. Suppose for instance that,
-similarly to the previous example, you want to rewrite all occurences of "World"
+similarly to the previous example, you want to rewrite all occurrences of "World"
by "Universe," but _inside the OCaml strings of the program_ only. It is quite
involved and requires a good knowledge of the OCaml syntax to do so, as there
are several delimiters for strings (such as `{| ...|}`) and line breaks, or
@@ -200,7 +198,6 @@ or even directly with the OCaml toplevel using the option `-dparsetree` (also
available in UTop).
```shell
-
$ ocaml -dparsetree
[Omitted output]
# let x = 1 + 2 ;;
@@ -434,6 +431,7 @@ type t = int [@@deriving_inline yojson]
```
Now, we run the PPX and promote the generated code in the original file:
+
```shell
$ opam exec -- dune build @lint
File "lib/lib.ml", line 1, characters 0-0:
@@ -452,8 +450,10 @@ index 4999e06..5516d41 100644
[@@@deriving.end]
Promoting _build/default/lib/lib.ml.lint-corrected to lib/lib.ml.
```
+
The file now contains the generated value. While it is still a development
dependency, the PPX dependency can be dropped for compiling the project:
+
```shell
$ cat lib.ml
type t = int [@@deriving_inline yojson]
diff --git a/data/tutorials/language/4ad_01_operators.md b/data/tutorials/language/4ad_01_operators.md
index 9ead141fce..d06d428613 100644
--- a/data/tutorials/language/4ad_01_operators.md
+++ b/data/tutorials/language/4ad_01_operators.md
@@ -16,6 +16,7 @@ The learning goals of this tutorial are:
## Using Binary Operators
In OCaml, almost all binary operators are regular functions. The function underlying an operator is referred by surrounding the operator symbol with parentheses. Here are the addition, string concatenation, and equality functions:
+
```ocaml
# (+);;
- : int -> int -> int =
@@ -26,12 +27,14 @@ In OCaml, almost all binary operators are regular functions. The function underl
```
**Note**: the operator symbol for multiplication is ` * `, but can't be referred as `(*)`. This is because comments in OCaml are delimited by `(*` and `*)`. To resolve the parsing ambiguity, space characters must be inserted to get the multiplication function.
+
```ocaml
# ( * );;
- : int -> int -> int =
```
Using operators as functions is convenient when combined with partial application. For instance, here is how to get the values that are greater than or equal to 10 in a list of integers, using the function `List.filter` and an operator.
+
```ocaml
# List.filter;;
- : ('a -> bool) -> 'a list -> 'a list =
@@ -56,6 +59,7 @@ Finally, in the third line, all the arguments expected by `List.filter` are prov
## Defining Binary Operators
It is also possible to define binary operators. Here is an example:
+
```ocaml
# let cat s1 s2 = s1 ^ " " ^ s2;;
val cat : string -> string -> string =
@@ -71,6 +75,7 @@ It is a recommended practice to define operators in two steps, like shown in the
## Unary Operators
Unary operators are also called prefix operators. In some contexts, it can make sense to shorten a function's name into a symbol. This is often used as a way to shorten the name of a function that performs some sort of conversion over its argument.
+
```ocaml
# let ( !! ) = Lazy.force;;
val ( !! ) : 'a lazy_t -> 'a =
@@ -123,6 +128,7 @@ Tips:
## Operator Associativity and Precedence
Let's illustrate operator associativity with an example. The following function concatenates its string arguments, surrounded by `|` characters and separated by a `_` character.
+
```ocaml
# let par s1 s2 = "|" ^ s1 ^ "_" ^ s2 ^ "|";;
val par : string -> string -> string =
@@ -132,6 +138,7 @@ val par : string -> string -> string =
```
Let's turn `par` into two different operators:
+
```ocaml
# let ( @^ ) = par;;
val ( @^ ) : string -> string -> string =
@@ -141,6 +148,7 @@ val ( &^ ) : string -> string -> string =
```
At first sight, operators `@^` and `&^` are the same. However, the OCaml parser allows forming expressions using several operators without parentheses.
+
```ocaml
# "foo" @^ "bar" @^ "bus";;
- : string = "|foo_|bar_bus||"
@@ -154,6 +162,7 @@ Although both expressions are calling the same function (`par`), they are evalua
1. Expression `"foo" &^ "bar" &^ "bus"` is evaluated as if it was `"(foo" &^ "bar") &^ "bus"`. Parentheses are added at the left, therefore `&^` _associates to the left_
Operator _precedence_ rules how expressions combining different operators without parentheses are interpreted. For instance, using the same operators, here is how expressions using both are evaluated:
+
```ocaml
# "foo" &^ "bar" @^ "bus";;
- : string = "|foo_|bar_bus||"
@@ -176,6 +185,7 @@ The complete list of precedence is longer because it includes the predefined ope
OCaml allows the creation of custom `let` operators. This is often used on monad-related functions such as `Option.bind` or `List.concat_map`. See [Monads](/docs/monads) for more on this topic.
The `doi_parts` function attempts to extract the registrant and identifier parts from string expected to contain a [Digital Object Identifier (DOI)](https://en.wikipedia.org/wiki/Digital_object_identifier).
+
```ocaml
# let ( let* ) = Option.bind;;
val ( let* ) : 'a option -> ('a -> 'b option) -> 'b option =
@@ -198,7 +208,6 @@ val ( let* ) : 'a option -> ('a -> 'b option) -> 'b option =
# doi_parts "https://doi.org/10.1000/182";;
- : (string * string) option = Some ("1000", "182")
-
```
This function is using `Option.bind` as a custom binder over the calls to `rindex_opt` and `rindex_from_opt`. This allows to only consider the case where both searches are successful and return the positions of the found characters. If any of them fails, `doi_parts` implicitly returns `None`.
diff --git a/data/tutorials/language/4ad_02_objects.md b/data/tutorials/language/4ad_02_objects.md
index e2ef19578d..d304c46d4c 100644
--- a/data/tutorials/language/4ad_02_objects.md
+++ b/data/tutorials/language/4ad_02_objects.md
@@ -93,6 +93,7 @@ object. We use the familiar `new` operator:
# let s = new stack_of_ints;;
val s : stack_of_ints =
```
+
Now we'll push and pop some elements off the stack:
```ocaml
@@ -115,6 +116,7 @@ Popped 2 off the stack.
Popped 1 off the stack.
- : unit = ()
```
+
Notice the syntax. `object#method` means call `method` on `object`. This
is the same as `object.method` or `object->method` that you will be
familiar with in imperative languages.
@@ -245,6 +247,7 @@ widget (superclass for all widgets)
|
+-------------> label
```
+
(Notice that a `button` is a `container` because it can contain either a
label or an image, depending on what is displayed on the button).
@@ -265,6 +268,7 @@ Error: Some type variables are unbound in this type:
object method get_name : 'a method virtual repaint : unit end
The method get_name has type 'a where 'a is unbound
```
+
Oops! I forgot that OCaml cannot infer the type of `name` so will assume
that it is `'a`. But that defines a polymorphic class, and I didn't
declare the class as polymorphic (`class ['a] widget`). I need to narrow
@@ -280,6 +284,7 @@ the type of `name` like this:
class virtual widget :
string -> object method get_name : string method virtual repaint : unit end
```
+
Now there are several new things going on in this code. Firstly, the
class contains an **initialiser**. This is an argument to the class
(`name`) which you can think of as exactly the equivalent of an argument
@@ -294,6 +299,7 @@ public class Widget
}
}
```
+
In OCaml, a constructor constructs the whole class; it's not just a
specially named function, so we write the arguments as if they are
arguments to the class:
@@ -366,10 +372,10 @@ Notes:
basically comes down to the fact that OCaml's linked lists are
immutable. Let's imagine that someone wrote this code:
- ```ocaml
- # let list = container#get_widgets in
- x :: list;;
- ```
+```ocaml
+# let list = container#get_widgets in
+ x :: list;;
+```
Would this modify the private internal representation of my `container`
class, by prepending `x` to the list of widgets? No it wouldn't. If you run
@@ -393,6 +399,7 @@ and I also use an anonymous function expression that might be unfamiliar:
# (fun w -> w#repaint);;
- : < repaint : 'a; .. > -> 'a =
```
+
This defines an anonymous function with one argument `w` that just
calls `w#repaint` (the `repaint` method on widget `w`).
@@ -490,6 +497,7 @@ class label :
string ->
string -> object method get_name : string method repaint : unit end
```
+
Let's create a label which says "Press me!" and add it to the button:
```ocaml
@@ -513,6 +521,7 @@ class name =
(* ... *)
end
```
+
The reference to `self` names the object,
allowing you to call methods in the same class or pass the object to
functions outside the class. In other words, it's exactly the same as
@@ -535,6 +544,7 @@ Error: This expression has type label but an expression was expected of type
button
The first object type has no method add
```
+
We created a button `b` and a label `l` and then tried to create a list
containing both, but we got an error. Yet `b` and `l` are both `widget`s,
so maybe we can't put them into the same list because OCaml can't guess
@@ -629,6 +639,7 @@ but doing so can make things clearer. We can do it like this:
# type counter = ;;
type counter = < get : int; incr : unit >
```
+
Compare with an equivalent record type definition:
```ocaml
@@ -637,6 +648,7 @@ Compare with an equivalent record type definition:
incr : unit -> unit};;
type counter_r = { get : unit -> int; incr : unit -> unit; }
```
+
The implementation of a record working like our object would be:
```ocaml
@@ -646,6 +658,7 @@ The implementation of a record working like our object would be:
incr = (fun () -> incr n)};;
val r : counter_r = {get = ; incr = }
```
+
In terms of functionality, both the object and the record are similar,
but each solution has its own advantages:
diff --git a/data/tutorials/language/5rt_01_garbage-collector.md b/data/tutorials/language/5rt_01_garbage-collector.md
index 8fe9ae94de..5cb0ab3626 100644
--- a/data/tutorials/language/5rt_01_garbage-collector.md
+++ b/data/tutorials/language/5rt_01_garbage-collector.md
@@ -167,7 +167,6 @@ profile). This setting can be overridden via the `s=` argument to
`OCAMLRUNPARAM`. You can change it after the program has started by calling
the `Gc.set` function:
-
```ocaml env=tune
# open Core;;
# let c = Gc.get ();;
diff --git a/data/tutorials/language/5rt_02_compiler_frontend.md b/data/tutorials/language/5rt_02_compiler_frontend.md
index 9d1c89d2bb..a5ad1355d7 100644
--- a/data/tutorials/language/5rt_02_compiler_frontend.md
+++ b/data/tutorials/language/5rt_02_compiler_frontend.md
@@ -288,7 +288,6 @@ illustrate their use without requiring any external tools. Let's first look
at the use of the standalone attribute `@@@warning` to toggle an OCaml
compiler warning.
-
```ocaml env=main
# module Abc = struct
@@ -1195,7 +1194,6 @@ $ ocamlc -dparsetree typedef.ml 2>&1
None
]
]
-
```
This is rather a lot of output for a simple two-line program, but it shows
@@ -1247,7 +1245,6 @@ $ ocamlc -dtypedtree typedef.ml 2>&1
[]
]
]
-
```
The typed AST is more explicit than the untyped syntax tree. For instance,
diff --git a/data/tutorials/language/5rt_03_compiler_backend.md b/data/tutorials/language/5rt_03_compiler_backend.md
index 6dbbb93c27..214564f2e6 100644
--- a/data/tutorials/language/5rt_03_compiler_backend.md
+++ b/data/tutorials/language/5rt_03_compiler_backend.md
@@ -275,7 +275,6 @@ Estimated testing time 750ms (3 benchmarks x 250ms). Change using '-quota'.
Monomorphic large pattern 6.54ns 67.89%
Monomorphic small pattern 9.63ns 100.00%
Polymorphic large pattern 9.63ns 99.97%
-
```
These results confirm the performance hypothesis that we obtained earlier by
@@ -346,7 +345,6 @@ L2: closure L1, 0
makeblock 1, 0
pop 1
setglobal Pattern_monomorphic_small!
-
```
The preceding bytecode has been simplified from the lambda form into a set of
@@ -420,7 +418,6 @@ bytecode archive:
```
$ ocamlc -a -o mylib.cma a.cmo b.cmo -dllib -lmylib
-
```
The `dllib` flag embeds the arguments in the archive file. Any subsequent
@@ -434,7 +431,6 @@ a *custom runtime* mode and is built as follows:
```
$ ocamlc -a -o mylib.cma -custom a.cmo b.cmo -cclib -lmylib
-
```
The custom mode is the most similar mode to native code compilation, as both
@@ -847,8 +843,6 @@ output:
(libraries core))
```
-
-
```sh dir=examples/back-end/alternate_list
$ opam exec -- dune build alternate_list.exe
$ ./_build/default/alternate_list.exe -ascii -quota 1
diff --git a/data/tutorials/platform/1_06_configuring_your_editor.md b/data/tutorials/platform/1_06_configuring_your_editor.md
deleted file mode 100644
index 8cfbf1b13f..0000000000
--- a/data/tutorials/platform/1_06_configuring_your_editor.md
+++ /dev/null
@@ -1,89 +0,0 @@
----
-id: "configuring-your-editor"
-title: "Configuring Your Editor"
-description: |
- How to set up Editor support for OCaml on VSCode, Vim and Emacs
-category: "Editor Support"
----
-
-OCaml has plugins for many editors, but the most actively maintained are for Visual Studio Code, Emacs, and Vim.
-
-## Visual Studio Code
-
-> **TL;DR**
->
-> Install the VSCode extension `ocamllabs.ocaml-platform` and the packages `ocaml-lsp-server` and `ocamlformat` in your [opam switch](/docs/opam-switch-introduction).
-
-Install the [OCaml Platform Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=ocamllabs.ocaml-platform) from the Visual Studio Marketplace.
-
-The extension depends on OCaml LSP and ocamlformat. To install them in your switch, you can run:
-
-```shell
-$ opam install ocaml-lsp-server ocamlformat
-```
-
-Upon first loading an OCaml source file, you may be prompted to select the toolchain in use. Pick the version of OCaml you are using, e.g., 4.14.0, from the list.
-
-### Editor Features at Your Disposal
-If your editor is setup correctly, here are some important features you can begin using to your advantage:
-#### 1) Hovering for Type Information:
-
-
-
-This is a great feature that let's you see type information of any OCaml variable or function. All you have to do is place your cursor over the code and it will be displayed in the tooltip.
-
-#### 2) Jump to Definitions With `Ctrl + Click`:
-
-
-
-If you hold down the Ctrl key while hovering, the code appears as a clickable link which if clicked takes you to the file where the implementation is. This can be great if you want to understand how a piece of code works under the hood. In this example, hovering and `Ctrl + Clicking` over the `peek` method of the `Queue` module will take you to the definiton of the `peek` method itself and how it is implemented.
-
-#### 3) OCaml Commands With `Ctrl + Shift + P`:
-
-
-
-Pressing the key combination Ctrl + Shift + P opens a modal dialog at the top. If you type the word `ocaml`, you will be presented with a list of various OCaml commands at your disposal which can be used for different purposes.
-
-### Windows Users
-
-If you used the DkML distribution, you will need to:
- 1. Go to `File` > `Preferences` > `Settings` view (or press `Ctrl ,`)
- 2. Select `User` > `Extensions` > `OCaml Platform`
- 3. Uncheck `OCaml: Use OCaml Env`. That's it!
-
-## Vim and Emacs
-
-**For Vim and Emacs**, we won't use the LSP server but rather directly talk to Merlin.
-
-```shell
-$ opam install merlin
-```
-
-After installing Merlin above, instructions will be printed on how to link Merlin with your editor. If you do not have them visible, just run this command:
-
-```shell
-$ opam user-setup install
-```
-
-### Talking to Merlin
-
-#### Getting Type Information
-
-**Vim**
-
-
-
-- In the Vim editor, press the Esc to enter command mode.
-- Place the cursor over the variable.
-- Type `:MerlinTypeOf` and press Enter.
-- The type information will be displayed in the command bar.
-Other Merlin commands for Vim are available and you can checkout their usage on the [Merlin official documentation for Vim](https://ocaml.github.io/merlin/editor/vim/).
-
-**Emacs**
-
-
-
-- In the Emacs editor, place you cursor over the variable.
-- Use the keyboard shortcut Alt + x followed by `merlin-type-enclosing`
-- The type information will be displayed in the mini-buffer.
-Other Merlin commands for Emacs are available and you can checkout their usage on the [Merlin Official documentation for Emacs](https://ocaml.github.io/merlin/editor/emacs/).
diff --git a/data/tutorials/platform/2_08_odoc.md b/data/tutorials/platform/2_08_odoc.md
index 5b6689a679..e343527715 100644
--- a/data/tutorials/platform/2_08_odoc.md
+++ b/data/tutorials/platform/2_08_odoc.md
@@ -43,4 +43,4 @@ include this stanza:
A common place to put `.mld` files is a directory named `doc` or `docs`.
For more information on how to write documentation pages for `odoc`,
-see the [`odoc` for authors documentation](https://ocaml.github.io/odoc/odoc_for_authors.html#doc-pages).
+see the [`odoc` for authors documentation](https://ocaml.github.io/odoc/odoc/odoc_for_authors.html).
diff --git a/data/v2/conferences/ocaml/2013/call.md b/data/v2/conferences/ocaml/2013/call.md
new file mode 100644
index 0000000000..4668644b77
--- /dev/null
+++ b/data/v2/conferences/ocaml/2013/call.md
@@ -0,0 +1,81 @@
+
+
+```
+==============================================================================
+
+ OCAML 2013
+ The OCaml Users and Developers Workshop
+ http://ocaml.org/meetings/ocaml/2013/
+ Boston, Massachusetts, USA
+ September 24, 2013
+
+ CALL FOR PRESENTATIONS
+
+ Co-located with ICFP 2013
+ Sponsored by SIGPLAN
+
+ Talk Proposal Submission Extended Deadline:
+ June 18, 2013 (anywhere on earth)
+
+==============================================================================
+
+The first occurrence of the OCaml Users and Developers Workshop was
+colocated with ICFP 2012, in Copenhagen, following the OCaml Meetings
+in Paris in 2010 and 2011. OCaml 2013 will be held on September 24,
+2013, in Boston, colocated with ICFP 2013.
+
+The OCaml Users and Developers Workshop brings together industrial
+users of OCaml with academics and hackers who are working on extending
+the language, type system and tools. Discussions will focus on the
+practical aspects of OCaml programming and the nitty gritty of the
+tool-chain and upcoming improvements and changes. Thus, we aim to
+solicit talks on all aspects related to improving the use or
+development of the language and of its programming environment,
+including, for example:
+
+- compiler developments, new backends, runtime and architectures
+
+- practical type system improvements, such as (but not exhaustively)
+ GADTs, first-class modules, generic programming, or dependent types
+
+- new library or application releases, and their design rationales
+
+- tool enhancements by commercial consultants
+
+- prominent industrial uses of OCaml, or deployments in unusual
+ situations.
+
+It will be an informal meeting, with an online scribe report of the
+meeting, but no formal proceedings. Slides of presentations will be
+available online from the workshop homepage.
+
+To submit a talk, please register a description of the talk (about 2
+pages long) at http://ocaml.org/meetings/ocaml/2013/talks/ providing a
+clear statement of what will be brought by the talk: the problems that
+are addressed, the technical solutions or methods that are
+proposed. If you wish to perform a demo or require any special setup,
+we will do our best to accommodate you.
+
+Schedule
+========
+
+Abstract Submission (extended) Deadline: June 18, 2013 (anywhere on earth)
+Notification to Speakers: Friday, July 7, 2013
+Workshop: Tuesday, September 24, 2013
+
+Program Committee
+=================
+
+* Damien Doligez, INRIA Paris-Rocquencourt, France
+* Jun Furuse, Standard Chartered Bank, Singapore
+* Jacques Le Normand, Google, USA
+* Michel Mauny, ENSTA-ParisTech, France (chair)
+* Mark Shinwell, Jane Street Europe, UK
+* David Walker, Princeton University, USA
+* Jeremy Yallop, University of Cambridge, UK
+* Sarah Zennou, EADS IW, France
+
+If you have any questions, please e-mail:
+Michel Mauny
+
+```
diff --git a/data/v2/conferences/ocaml/2013/index.md b/data/v2/conferences/ocaml/2013/index.md
new file mode 100644
index 0000000000..49c6cefa61
--- /dev/null
+++ b/data/v2/conferences/ocaml/2013/index.md
@@ -0,0 +1,67 @@
+
+
+*Table of contents*
+
+OCaml 2013
+==========
+
+**The OCaml Users and Developers Workshop**
+Boston (MA, USA), September 24, 2013
+Colocated with [ICFP 2013](http://icfpconference.org/icfp2013/)
+
+The meeting is an informal community gathering of users of the language,
+library authors, and developers, using and extending OCaml in new ways.
+
+News
+----
+
+- October 7, 2013: The [final program, with links to papers and
+ slides](program.html) is available.
+- July 11, 2013: The [preliminary program](program.html) is available.
+- June 7, 2013: The submission deadline has been extended to June 18,
+ anywhere on earth.
+- May 7, 2013: The [submission
+ site](https://www.easychair.org/conferences/?conf=ocaml2013) is now
+ open! Please submit a presentation before June 7.
+- May 7, 2013: The [submission
+ site](https://www.easychair.org/conferences/?conf=ocaml2013) is now
+ open! Please submit a presentation before June 7 June 18
+ (anywhere on earth).
+- April 16, 2013: [workshop announcement](call.html). The [submission
+ site](talks/) should open in the next days.
+
+Important dates
+---------------
+
+- June 18, 2013 (anywhere on earth): Extended deadline for submissions
+- July 7, 2013: Notification to speakers
+- September 24, 2013: Workshop
+
+Call for presentations
+----------------------
+
+Please consider submitting a presentation, and/or join us in Boston! See
+[here the call for presentations](call.html).
+
+Program Committee
+-----------------
+
+- Damien Doligez, INRIA Paris-Rocquencourt, France
+- Jun Furuse, Standard Chartered Bank, Singapore
+- Jacques Le Normand, Google, USA
+- Michel Mauny, ENSTA-ParisTech, France (chair)
+- Mark Shinwell, Jane Street Europe, UK
+- David Walker, Princeton University, USA
+- Jeremy Yallop, University of Cambridge, UK
+- Sarah Zennou, EADS IW, France
+
+History
+-------
+
+The [first occurrence of the OCaml Users and Developers
+Workshop](http://oud.ocaml.org/2012/) was colocated with [ICFP
+2012](http://icfpconference.org/icfp2012/), in Copenhagen, following the
+*OCaml User Meetings* in Paris in [2008](../2008/), 2010 and [2011](../2011/)
+and in Grenoble in [2009](../2009/). Building on the success of
+past events, OCaml 2013 will be held on September 24, 2013, in Boston,
+colocated with [ICFP 2013](http://icfpconference.org/icfp2013/).
diff --git a/data/v2/conferences/ocaml/2013/program.md b/data/v2/conferences/ocaml/2013/program.md
new file mode 100644
index 0000000000..8404a40dc8
--- /dev/null
+++ b/data/v2/conferences/ocaml/2013/program.md
@@ -0,0 +1,123 @@
+
+
+OCaml 2013 - Program
+--------------------
+
+### The OCaml Users and Developers Workshop
+
+#### Boston (MA, USA), September 24, 2013
+
+#### Colocated with ICFP 2013
+
+#### 09:00-09:10 - Welcome
+
+#### 09:10-10:10 - Applications
+
+Accessing and using weather-related data in OCaml
+([paper](proposals/weather-related-data.pdf),
+[slides](slides/carty.pdf)), by Hezekiah Carty (MDA Information Systems
+LLC, USA)
+
+The Frenetic Network Controller ([paper](proposals/frenetic.pdf),
+[slides](slides/guha.pdf)), by Nate Foster (Cornell University, USA),
+Arjun Guha (UMass Amherst, USA) and Frenetic Contributors (The Frenetic
+Project, USA)
+
+Pfff: PHP Program analysis at Facebook
+([paper](https://github.com/facebook/pfff/wiki/Main),
+[slides](slides/padioleau.pdf)), by Yoann Padioleau (Facebook, USA)
+
+#### 10:10-10:30 - Break
+
+#### 10:30-11:30 - Bindings
+
+The design of the wxOCaml library ([paper](proposals/wxocaml.pdf),
+[slides](slides/lefessant.pdf)), by Fabrice Le Fessant (INRIA
+Paris-Rocquencourt and OCamlPro SAS, France)
+
+Goji: an Automated Tool for Building High Level OCaml-JavaScript
+Interfaces ([paper](proposals/goji.pdf), [slides](slides/canou.pdf)), by
+Benjamin Canou (Université Pierre et Marie Curie, LIP6 UMR 7606, France)
+
+ctypes: foreign calls in your native language
+([paper](proposals/ctypes.pdf)), by Jeremy Yallop (University of
+Cambridge, UK)
+
+#### 11:30-11:50 - Break
+
+#### 11:50-12:30 - OCaml News
+
+The State of OCaml (invited, [slides](slides/leroy.pdf)), Xavier Leroy
+(INRIA Paris-Rocquencourt, France
+
+The OCaml Platform v0.1 ([paper](proposals/platform.pdf),
+[slides](slides/madhavapeddy.pdf)), by Anil Madhavapeddy (University of
+Cambridge, UK), Amir Chaudhry (University of Cambridge, UK), Thomas
+Gazagnaire (OCamlPro SAS, France), David Sheets (University of
+Cambridge, UK), Philippe Wang (University of Cambridge, UK), Leo White
+(University of Cambridge, UK) and Jeremy Yallop (University of
+Cambridge, UK)
+
+#### 12:30-14:00 - Lunch
+
+#### 14:00-15:00 - Compilation
+
+Extensions points for OCaml (invited, [slides](slides/white.pdf)), by
+Leo White (University of Cambridge, UK)
+
+High-Performance GPGPU Programming with OCaml
+([paper](proposals/gpgpu.pdf), [slides](slides/bourgoin.pdf)), by
+Mathias Bourgoin (Université Pierre et Marie Curie, LIP6 UMR 7606,
+France), Emmmanuel Chailloux (Université Pierre et Marie Curie, LIP6 UMR
+7606, France) and Jean-Luc Lamotte (Université Pierre et Marie Curie,
+LIP6 UMR 7606, France)
+
+Improving OCaml high level optimisations
+([paper](proposals/optimizations.pdf), [slides](slides/chambart.pdf)),
+by Pierre Chambart (OCamlPro SAS, France)
+
+#### 15:00-15:20 - Break
+
+#### 15:20-16:20 - Types
+
+A new implementation of OCaml formats based on GADTs
+([paper](proposals/formats-as-gadts.pdf), [slides](slides/vaugon.pdf)),
+by Benoît Vaugon (ENSTA-ParisTech, France)
+
+Runtime types in OCaml ([paper](proposals/runtime-types.pdf),
+[slides](slides/henry.pdf)), by Grégoire Henry (INRIA
+Paris-Rocquencourt, France) and Jacques Garrigue (Nagoya University,
+Japan)
+
+On variance, injectivity, and abstraction
+([paper](proposals/injectivity.pdf), [slides](slides/garrigue.pdf)), by
+Jacques Garrigue (Nagoya University, Japan)
+
+#### 16:20-16:40 - Break
+
+#### 16:40-17:40 - Tools
+
+Ocamlot: OCaml Online Testing ([paper](proposals/ocamlot.pdf),
+[slides](slides/sheets.pdf)), by David Sheets (University of Cambridge,
+UK), Anil Madhavapeddy (University of Cambridge, UK), Amir Chaudhry
+(University of Cambridge, UK) and Thomas Gazagnaire (OCamlPro SAS,
+France)
+
+Merlin, an assistant for editing OCaml code
+([paper](proposals/merlin.pdf)), by Frédéric Bour (Université
+Paris-Diderot, France), Thomas Refis (Université Paris-Diderot, France)
+and Simon Castellan (Université Paris-Diderot, France)
+
+Profiling the Memory Usage of OCaml Applications without Changing their
+Behavior ([paper](proposals/profiling-memory.pdf),
+[slides](slides/bozman.pdf)), by Çagdas Bozman (OCamlPro SAS, INRIA
+Paris-Rocquencourt and ENSTA-Paristech, France), Michel Mauny
+(ENSTA-ParisTech, France), Fabrice Le Fessant (INRIA Paris-Rocquencourt
+and OCamlPro SAS, France) and Thomas Gazagnaire (OCamlPro SAS, France)
+
+Core bench: micro-benchmarking for OCaml
+([paper](proposals/core-bench.pdf), [slides](slides/james.pdf)), by
+Christopher Hardin (Jane Street Capital, USA) and James Roshan (Jane
+Street Capital, USA)
+
+#### 18:00 - Closing
diff --git a/data/v2/conferences/ocaml/2013/proposals/core-bench.pdf b/data/v2/conferences/ocaml/2013/proposals/core-bench.pdf
new file mode 100644
index 0000000000..dc30f97944
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/proposals/core-bench.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/proposals/ctypes.pdf b/data/v2/conferences/ocaml/2013/proposals/ctypes.pdf
new file mode 100644
index 0000000000..a823d1c91d
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/proposals/ctypes.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/proposals/formats-as-gadts.pdf b/data/v2/conferences/ocaml/2013/proposals/formats-as-gadts.pdf
new file mode 100644
index 0000000000..1b3ffb83a6
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/proposals/formats-as-gadts.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/proposals/frenetic.pdf b/data/v2/conferences/ocaml/2013/proposals/frenetic.pdf
new file mode 100644
index 0000000000..c82fb222f5
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/proposals/frenetic.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/proposals/goji.pdf b/data/v2/conferences/ocaml/2013/proposals/goji.pdf
new file mode 100644
index 0000000000..53bac26d71
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/proposals/goji.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/proposals/gpgpu.pdf b/data/v2/conferences/ocaml/2013/proposals/gpgpu.pdf
new file mode 100644
index 0000000000..cbc0bcc4dd
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/proposals/gpgpu.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/proposals/injectivity.pdf b/data/v2/conferences/ocaml/2013/proposals/injectivity.pdf
new file mode 100644
index 0000000000..9b9095732d
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/proposals/injectivity.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/proposals/merlin.pdf b/data/v2/conferences/ocaml/2013/proposals/merlin.pdf
new file mode 100644
index 0000000000..5d26738285
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/proposals/merlin.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/proposals/ocamlot.pdf b/data/v2/conferences/ocaml/2013/proposals/ocamlot.pdf
new file mode 100644
index 0000000000..00ba3e235e
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/proposals/ocamlot.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/proposals/optimizations.pdf b/data/v2/conferences/ocaml/2013/proposals/optimizations.pdf
new file mode 100644
index 0000000000..2735b77312
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/proposals/optimizations.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/proposals/platform.pdf b/data/v2/conferences/ocaml/2013/proposals/platform.pdf
new file mode 100644
index 0000000000..3e0b1b4b85
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/proposals/platform.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/proposals/profiling-memory.pdf b/data/v2/conferences/ocaml/2013/proposals/profiling-memory.pdf
new file mode 100644
index 0000000000..7a517290e5
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/proposals/profiling-memory.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/proposals/runtime-types.pdf b/data/v2/conferences/ocaml/2013/proposals/runtime-types.pdf
new file mode 100644
index 0000000000..b8ff3eb063
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/proposals/runtime-types.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/proposals/weather-related-data.pdf b/data/v2/conferences/ocaml/2013/proposals/weather-related-data.pdf
new file mode 100644
index 0000000000..acf78838b9
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/proposals/weather-related-data.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/proposals/wxocaml.pdf b/data/v2/conferences/ocaml/2013/proposals/wxocaml.pdf
new file mode 100644
index 0000000000..3dffc9ed9a
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/proposals/wxocaml.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/slides/bourgoin.pdf b/data/v2/conferences/ocaml/2013/slides/bourgoin.pdf
new file mode 100644
index 0000000000..fd4f7fe227
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/slides/bourgoin.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/slides/bozman.pdf b/data/v2/conferences/ocaml/2013/slides/bozman.pdf
new file mode 100644
index 0000000000..590f3045ea
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/slides/bozman.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/slides/canou.pdf b/data/v2/conferences/ocaml/2013/slides/canou.pdf
new file mode 100644
index 0000000000..8d81d93d46
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/slides/canou.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/slides/carty.pdf b/data/v2/conferences/ocaml/2013/slides/carty.pdf
new file mode 100644
index 0000000000..ee04687b97
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/slides/carty.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/slides/chambart.pdf b/data/v2/conferences/ocaml/2013/slides/chambart.pdf
new file mode 100644
index 0000000000..a171be3322
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/slides/chambart.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/slides/garrigue.pdf b/data/v2/conferences/ocaml/2013/slides/garrigue.pdf
new file mode 100644
index 0000000000..077b27b976
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/slides/garrigue.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/slides/guha.pdf b/data/v2/conferences/ocaml/2013/slides/guha.pdf
new file mode 100644
index 0000000000..5629bdbe0a
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/slides/guha.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/slides/henry.pdf b/data/v2/conferences/ocaml/2013/slides/henry.pdf
new file mode 100644
index 0000000000..d9b8a8d420
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/slides/henry.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/slides/james.pdf b/data/v2/conferences/ocaml/2013/slides/james.pdf
new file mode 100644
index 0000000000..8f191f9148
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/slides/james.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/slides/lefessant.pdf b/data/v2/conferences/ocaml/2013/slides/lefessant.pdf
new file mode 100644
index 0000000000..fac8c59c87
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/slides/lefessant.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/slides/leroy.pdf b/data/v2/conferences/ocaml/2013/slides/leroy.pdf
new file mode 100644
index 0000000000..681a7f3a8c
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/slides/leroy.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/slides/madhavapeddy.pdf b/data/v2/conferences/ocaml/2013/slides/madhavapeddy.pdf
new file mode 100644
index 0000000000..489f244f44
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/slides/madhavapeddy.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/slides/padioleau.pdf b/data/v2/conferences/ocaml/2013/slides/padioleau.pdf
new file mode 100644
index 0000000000..3ff7330334
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/slides/padioleau.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/slides/sheets.pdf b/data/v2/conferences/ocaml/2013/slides/sheets.pdf
new file mode 100644
index 0000000000..fb8b977de8
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/slides/sheets.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/slides/vaugon.pdf b/data/v2/conferences/ocaml/2013/slides/vaugon.pdf
new file mode 100644
index 0000000000..56dbd5e646
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/slides/vaugon.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/slides/white.pdf b/data/v2/conferences/ocaml/2013/slides/white.pdf
new file mode 100644
index 0000000000..20256f1b51
Binary files /dev/null and b/data/v2/conferences/ocaml/2013/slides/white.pdf differ
diff --git a/data/v2/conferences/ocaml/2013/talks/index.md b/data/v2/conferences/ocaml/2013/talks/index.md
new file mode 100644
index 0000000000..d32ad2d1a6
--- /dev/null
+++ b/data/v2/conferences/ocaml/2013/talks/index.md
@@ -0,0 +1,3 @@
+OCaml 2013: submissions
+
+
diff --git a/data/video-watch.yml b/data/video-watch.yml
index 61a6bade0c..df504264fc 100644
--- a/data/video-watch.yml
+++ b/data/video-watch.yml
@@ -9,6 +9,18 @@
author_uri: https://watch.ocaml.org/
source_link: https://watch.ocaml.org/
source_title: Watch OCaml
+- title: A 'Melange' of Tooling Coming Together - Antonio Monteiro - FUN OCaml 2024
+ description: "Antonio Monteiro's FUN OCaml 2024 talk recording!\r\n\r\n\r\nOverview
+ by Antonio:\r\n\r\nMelange is a new compiler for OCaml targeting JavaScript. Melange
+ codebases integrate with the OCaml Platform: install from OPAM, build with Dune,
+ preprocess with `ppxl..."
+ url: https://watch.ocaml.org/videos/watch/eff48eec-03be-479e-ad76-0c454e63b938
+ thumbnail: https://watch.ocaml.org/lazy-static/thumbnails/0ca24226-7678-488c-bf80-ce348eb158af.jpg
+ published: 2024-11-20T00:00:00.000Z
+ author_name: Unknown
+ author_uri: https://watch.ocaml.org/
+ source_link: https://watch.ocaml.org/
+ source_title: Watch OCaml
- title: A Case for Multi-Switch Constraints in OPAM
description: "A Case for Multi-Switch Constraints in OPAM - by Fabrice Le Fessant
(INRIA)\r\n\r\nPackage managers usually only deal with packages and their versions,
@@ -194,6 +206,19 @@
author_uri: https://watch.ocaml.org/
source_link: https://watch.ocaml.org/
source_title: Watch OCaml
+- title: Building Incremental and Reproducible Data Pipelines - Patrick Ferris -
+ FUN OCaml 2024
+ description: "Patrick Ferris's FUN OCaml 2024 talk recording!\r\n\r\nOverview by
+ Patrick:\r\n\r\nWe present the good and the bad of building a dataflow engine
+ in OCaml. The engine underpins a complex ecological analysis of avoided deforestation
+ projects in tropical moi..."
+ url: https://watch.ocaml.org/videos/watch/08223c1d-c6dc-4e73-b988-b58de2a407c6
+ thumbnail: https://watch.ocaml.org/lazy-static/thumbnails/e4cd54aa-a313-4f8d-be51-d4a9538672d0.jpg
+ published: 2024-11-20T00:00:00.000Z
+ author_name: Unknown
+ author_uri: https://watch.ocaml.org/
+ source_link: https://watch.ocaml.org/
+ source_title: Watch OCaml
- title: Casually talking about ReScript, OSS, and communities with Patrick Ecker
description: "Patrick (@ryyppy) is working with OSS in ReScript and part of the
core team\r\n\r\nIf you'd like to support the show for more content about OCaml,
@@ -438,6 +463,18 @@
author_uri: https://watch.ocaml.org/
source_link: https://watch.ocaml.org/
source_title: Watch OCaml
+- title: Easy GADTs by Repeating Yourself - Eduardo Rafael - FUN OCaml 2024
+ description: "Eduardo Rafael's FUN OCaml 2024 talk recording!\r\n\r\nOverview by
+ Eduardo:\r\n\r\n\r\nTraditionally GADT's are used for lightweight tasks as the
+ code complexity increases quite. I will be arguing that this is mostly a lack
+ of common \"design patterns\" and m..."
+ url: https://watch.ocaml.org/videos/watch/12959f82-f503-4e77-8940-208cf81a9c84
+ thumbnail: https://watch.ocaml.org/lazy-static/thumbnails/508a8b3a-88f5-4406-ab79-878c9ba46b66.jpg
+ published: 2024-11-20T00:00:00.000Z
+ author_name: Unknown
+ author_uri: https://watch.ocaml.org/
+ source_link: https://watch.ocaml.org/
+ source_title: Watch OCaml
- title: Effective Concurrency through Algebraic Effects
description: ""
url: https://watch.ocaml.org/videos/watch/e9f6c837-1435-4349-af0f-07d22d1c11ea
@@ -613,6 +650,18 @@
author_uri: https://watch.ocaml.org/
source_link: https://watch.ocaml.org/
source_title: Watch OCaml
+- title: How the Multicore Garbage Collector works - Sudha Parimala - FUN OCaml 2024
+ description: "Sudha's FUN OCaml 2024 talk recording!\r\n\r\nOverview by Sudha:\r\n\r\nIn
+ a first, OCaml 5.0 shipped with native support for parallelism and concurrency.
+ This was a multi-year effort by the Multicore team and the OCaml development team
+ that culminated in..."
+ url: https://watch.ocaml.org/videos/watch/316e573e-6622-46da-aa25-de81d4477ca7
+ thumbnail: https://watch.ocaml.org/lazy-static/thumbnails/c79ab9b0-8d48-43df-b979-abdae746811a.jpg
+ published: 2024-11-20T00:00:00.000Z
+ author_name: Unknown
+ author_uri: https://watch.ocaml.org/
+ source_link: https://watch.ocaml.org/
+ source_title: Watch OCaml
- title: Implementing an interval computation library for OCaml
description: "Implementing an interval computation library for OCaml, by Jean-Marc
Alliot, Charlie Vanaret, Jean-Baptiste Gotteland, Nicolas Durand and David Gianazza.\r\n\r\nIn
@@ -717,6 +766,18 @@
author_uri: https://watch.ocaml.org/
source_link: https://watch.ocaml.org/
source_title: Watch OCaml
+- title: Learning OCaml with Tiny Code Xmas - Michael Dales - FUN OCaml 2024
+ description: "Michael Dales's FUN OCaml 2024 talk recording!\r\n\r\nOverview by
+ Michael:\r\n\r\n\r\nEach year the demo community run Tiny Code Xmas - using fantasy
+ consoles like TIC-80 and PICO-8 to teach people to do retro graphics effects and
+ size coding. But I didn't ..."
+ url: https://watch.ocaml.org/videos/watch/55fff7e8-5aff-4150-a326-1c407e40817d
+ thumbnail: https://watch.ocaml.org/lazy-static/thumbnails/5cf07b59-eaf7-4604-98b2-bcb4f7530e1d.jpg
+ published: 2024-11-20T00:00:00.000Z
+ author_name: Unknown
+ author_uri: https://watch.ocaml.org/
+ source_link: https://watch.ocaml.org/
+ source_title: Watch OCaml
- title: 'Learning OCaml: An Online Learning Centre for OCaml'
description: "Learn OCaml: An Online Learning Center for OCaml, by Benjamin Canou,
Gr\xE9goire Henry, \xC7agdas Bozman and Fabrice Le Fessant.\r\n\r\nWe present
@@ -729,6 +790,18 @@
author_uri: https://watch.ocaml.org/
source_link: https://watch.ocaml.org/
source_title: Watch OCaml
+- title: Let Signals in OCaml - Rizo Isrof - FUN OCaml 2024
+ description: "Rizo Isrof's FunOCaml 2024 talk recording!\r\n\r\nOverview by Rizo:\r\n\r\nSignals
+ and fine-grained reactivity are two popular concepts in the JavaScript world,
+ thanks to their simplicity, ergonomics and excellent performance. What are signals?
+ What is th..."
+ url: https://watch.ocaml.org/videos/watch/31a13898-6c5a-4c82-aab9-6dcb0d805e64
+ thumbnail: https://watch.ocaml.org/lazy-static/thumbnails/2ccc22eb-8740-4f62-9710-1731b69c071a.jpg
+ published: 2024-11-20T00:00:00.000Z
+ author_name: Unknown
+ author_uri: https://watch.ocaml.org/
+ source_link: https://watch.ocaml.org/
+ source_title: Watch OCaml
- title: Leveraging Formal Specifications to Generate Fuzzing Suites
description: "When testing a library, developers typically first have to capture
the semantics they want to check. They then write the code implementing these
@@ -783,6 +856,19 @@
author_uri: https://watch.ocaml.org/
source_link: https://watch.ocaml.org/
source_title: Watch OCaml
+- title: Maybe OCaml Was the Friends We Made Along the Way - Dillon Mulroy - FUN OCaml
+ 2024
+ description: "Dillon Mulroy's FUN OCaml 2024 talk recording!\r\n\r\nOverview by
+ Dillon:\r\n\r\nIn my decade-long journey as a software engineer, the most transformative
+ experience came from an unexpected source: learning and using OCaml. I will share
+ how this functiona..."
+ url: https://watch.ocaml.org/videos/watch/cebc2faf-3df3-4179-8ea8-64a50e1d84ff
+ thumbnail: https://watch.ocaml.org/lazy-static/thumbnails/da273a66-26d6-4419-b199-87a2890d38ec.jpg
+ published: 2024-11-20T00:00:00.000Z
+ author_name: Unknown
+ author_uri: https://watch.ocaml.org/
+ source_link: https://watch.ocaml.org/
+ source_title: Watch OCaml
- title: 'Memo: an incremental computation library that powers Dune'
description: "Memo: an incremental computation library that powers Dune\r\n\r\nAndrey
Mokhov (Jane Street)\r\nArseniy Alekseyev (Jane Street)\r\n\r\nWe present Memo,
@@ -795,6 +881,19 @@
author_uri: https://watch.ocaml.org/
source_link: https://watch.ocaml.org/
source_title: Watch OCaml
+- title: MirageOS - Developing Operating Systems in OCaml - Hannes Mehnert - FUN OCaml
+ 2024
+ description: "Hannes Mehnert's FUN OCaml 2024 talk recording!\r\n\r\nOverview by
+ Hannes:\r\n\r\n\r\nOCaml is a great systems programming language. We use it since
+ more than a decade to develop MirageOS unikernels: run OCaml as a virtual machine,
+ no Linux kernel involved...."
+ url: https://watch.ocaml.org/videos/watch/3ad9fc58-ca49-4b2b-9278-fc980bcd7634
+ thumbnail: https://watch.ocaml.org/lazy-static/thumbnails/2f3c5db2-5139-4835-87b4-3c1652bbb28c.jpg
+ published: 2024-11-20T00:00:00.000Z
+ author_name: Unknown
+ author_uri: https://watch.ocaml.org/
+ source_link: https://watch.ocaml.org/
+ source_title: Watch OCaml
- title: Multicore OCaml
description: "Multicore OCaml, by Stephen Dolan, Leo White, Anil Madhavapeddy (University
of Cambridge).\r\n\r\nCurrently, threading is supported in OCaml only by means
@@ -840,6 +939,18 @@
author_uri: https://watch.ocaml.org/
source_link: https://watch.ocaml.org/
source_title: Watch OCaml
+- title: OCANNL, the `neural_nets_lib` - Lukasz Stafiniak - FUN OCaml 2024
+ description: "Lukasz Stafiniak's FUN OCaml 2024 talk recording!\r\n\r\nOverview
+ by Lukasz:\r\n\r\n\r\nUsing OCANNL, we will build a toy feed forward network,
+ we will train it, visualize its outputs. We will take a peek at the actual computation
+ generated at various level..."
+ url: https://watch.ocaml.org/videos/watch/f1af695d-21a5-45d1-ad05-ba4c87a106be
+ thumbnail: https://watch.ocaml.org/lazy-static/thumbnails/b944947f-5f5d-47f8-a9d0-25cb4d9dc3de.jpg
+ published: 2024-11-20T00:00:00.000Z
+ author_name: Unknown
+ author_uri: https://watch.ocaml.org/
+ source_link: https://watch.ocaml.org/
+ source_title: Watch OCaml
- title: OCaml Companion Tools
description: "OCaml Companion Tools, by Xavier Clerc.\r\n\r\nThe objective of this
talk is to present several tools that aim to ease the development of software
@@ -1028,6 +1139,18 @@
author_uri: https://watch.ocaml.org/
source_link: https://watch.ocaml.org/
source_title: Watch OCaml
+- title: Outreachy December 2024 Demo
+ description: "The OCaml community participated in the December 2024 round of [Outreachy
+ internships](https://www.outreachy.org). One intern worked on a tool for diffing
+ OCaml APIs.\r\n\r\nThis meeting was an opportunity for our intern to present their
+ work and for ..."
+ url: https://watch.ocaml.org/videos/watch/70ef58ff-ff7c-40d6-ab19-e886e4e98205
+ thumbnail: https://watch.ocaml.org/lazy-static/thumbnails/9d342118-1e60-4653-b6a4-fc500d0ea002.jpg
+ published: 2025-04-17T09:45:05.088Z
+ author_name: Unknown
+ author_uri: https://watch.ocaml.org/
+ source_link: https://watch.ocaml.org/
+ source_title: Watch OCaml
- title: Outreachy May 2024 Demo
description: The OCaml community participated in the May 2024 round of [Outreachy](https://www.outreachy.org/)
internships. Three interns worked on a range of projects including tools to diff
@@ -1424,6 +1547,18 @@
author_uri: https://watch.ocaml.org/
source_link: https://watch.ocaml.org/
source_title: Watch OCaml
+- title: The Future of Dune - Leandro Ostera - FUN OCaml 2024
+ description: "Leandro Ostera's FUN OCaml 2024 talk recording!\r\n\r\nOverview by
+ Leandro:\r\n\r\n\r\nIn this talk Leandro Ostera, the PM of OCaml Build System
+ team at Tarides shows the future of developer experience in dune. He will present
+ the new features that are bein..."
+ url: https://watch.ocaml.org/videos/watch/0cdb9fba-13d4-48fb-b06c-ea7a9583356d
+ thumbnail: https://watch.ocaml.org/lazy-static/thumbnails/5a4bfc62-e22b-4809-b7c9-925832cdc1c1.jpg
+ published: 2024-11-20T00:00:00.000Z
+ author_name: Unknown
+ author_uri: https://watch.ocaml.org/
+ source_link: https://watch.ocaml.org/
+ source_title: Watch OCaml
- title: The ImpFS filesystem
description: "This proposal describes a presentation to be given at the OCaml\u201920
workshop. The presentation will cover a new OCaml filesystem, ImpFS, and the related
@@ -1529,6 +1664,19 @@
author_uri: https://watch.ocaml.org/
source_link: https://watch.ocaml.org/
source_title: Watch OCaml
+- title: The Story Behind the Fastest Image Comparison Library - Dmitriy Kovalenko
+ - FUN OCaml 2024
+ description: "Dmitriy Kovalenko's FUN OCaml 2024 talk recording!\r\n\r\nOverview
+ by Dmitriy:\r\n\r\nI am the author of dmtrKovalenko/odiff which claims to be and
+ it is the fastest in the world (on my banchmarks lol) implementation of the pixel-by-pixel
+ image comparison..."
+ url: https://watch.ocaml.org/videos/watch/88a712b2-e800-4eee-bb16-e9710a9b5102
+ thumbnail: https://watch.ocaml.org/lazy-static/thumbnails/c3501e6c-9fa7-4dec-924f-3f91473d771e.jpg
+ published: 2024-11-20T00:00:00.000Z
+ author_name: Unknown
+ author_uri: https://watch.ocaml.org/
+ source_link: https://watch.ocaml.org/
+ source_title: Watch OCaml
- title: The final pieces of the OCaml documentation puzzle
description: 'Rendering OCaml document is widely known as a very difficult task:
The ever-evolving OCaml module system is extremely rich and can include complex
@@ -1593,6 +1741,19 @@
author_uri: https://watch.ocaml.org/
source_link: https://watch.ocaml.org/
source_title: Watch OCaml
+- title: Type engineering in OCaml, Illustrated on the OCaml Compiler - Florian Angeletti
+ - FUN OCaml 2024
+ description: "Florian Angeletti's FUN OCaml 2024 talk recording!\r\n\r\nOverview
+ by Florian:\r\n\r\n\r\nWith four different kinds of variants and four different
+ implementations of an object system, OCaml provides many options to design types
+ to fit domain problems. Howev..."
+ url: https://watch.ocaml.org/videos/watch/7a86d018-021f-4e9e-bb8c-7be05d7e378c
+ thumbnail: https://watch.ocaml.org/lazy-static/thumbnails/7cdb5bd4-5330-4ff0-8dce-17996c71e42c.jpg
+ published: 2024-11-20T00:00:00.000Z
+ author_name: Unknown
+ author_uri: https://watch.ocaml.org/
+ source_link: https://watch.ocaml.org/
+ source_title: Watch OCaml
- title: Types in Amber
description: Coda is a new cryptocurrency that uses zk-SNARKs to dramatically reduce
the size of data needed by nodes running its protocol. Nodes communicate in a
@@ -1605,6 +1766,18 @@
author_uri: https://watch.ocaml.org/
source_link: https://watch.ocaml.org/
source_title: Watch OCaml
+- title: Universal React in OCaml - David Sancho Moreno - FUN OCaml 2024
+ description: "David Sancho Moreno's FUN OCaml 2024 talk recording!\r\n\r\nOverview
+ by David:\r\n\r\n\r\nserver-reason-react implements react-dom/server and some
+ of React's internals in OCaml. Its purpose is to natively render HTML markup from
+ the server for a Reason Reac..."
+ url: https://watch.ocaml.org/videos/watch/f74571dc-c1e5-43d2-9d7d-e723a2165d02
+ thumbnail: https://watch.ocaml.org/lazy-static/thumbnails/39c48723-73cc-4011-bbe7-67e0392bfeed.jpg
+ published: 2024-11-20T00:00:00.000Z
+ author_name: Unknown
+ author_uri: https://watch.ocaml.org/
+ source_link: https://watch.ocaml.org/
+ source_title: Watch OCaml
- title: Using Preferences to Tame your Package Manager
description: ""
url: https://watch.ocaml.org/videos/watch/43536918-a6e5-4a53-a680-bed527319e31
@@ -1614,6 +1787,19 @@
author_uri: https://watch.ocaml.org/
source_link: https://watch.ocaml.org/
source_title: Watch OCaml
+- title: "Using odoc to Write Documentation - Paul-Elliot Angl\xE8s d'Auriac - FUN
+ OCaml 2024"
+ description: "Paul-Elliot Angl\xE8s d'Auriac's FUN OCaml 2024 talk recording!\r\n\r\nOverview
+ by Paul-Elliot:\r\n\r\nThis talk is a gentle introduction to the documenting part
+ of the OCaml ecosystem. We will see how to use `odoc` to build nice documentation
+ for your `dune..."
+ url: https://watch.ocaml.org/videos/watch/e844250f-a6c2-46aa-8d66-b9a436b6c8bc
+ thumbnail: https://watch.ocaml.org/lazy-static/thumbnails/aeba6eed-9e4f-44c4-a2f8-b7c6483fd609.jpg
+ published: 2024-11-20T00:00:00.000Z
+ author_name: Unknown
+ author_uri: https://watch.ocaml.org/
+ source_link: https://watch.ocaml.org/
+ source_title: Watch OCaml
- title: Verifying an Effect-Based Cooperative Concurrency Scheduler in Iris by Adrian
Dapprich
description: "Lightweight asynchronous programming (using futures, goroutines or
diff --git a/data/video-youtube.yml b/data/video-youtube.yml
index b084f2ea1d..2cdd4cafdb 100644
--- a/data/video-youtube.yml
+++ b/data/video-youtube.yml
@@ -1,3 +1,28 @@
+- title: Making OCaml Safe for Performance Engineering
+ url: https://www.youtube.com/watch/g3qd4zpm1LA?version=3
+ thumbnail: https://i4.ytimg.com/vi/g3qd4zpm1LA/hqdefault.jpg
+ description: "Jane Street is a trading firm that uses a variety of high-performance
+ systems built in OCaml to provide liquidity to financial markets worldwide. Over
+ the last couple of years, we have started developing major extensions to OCaml\u2019s
+ type system, with the primary goal of making OCaml a better language for writing
+ high-performance systems. In this talk, we will attempt to provide a developer's-eye
+ view of these changes. We\u2019ll cover two major directions of innovation: first,
+ the addition of modal types to OCaml, which opens up a variety of ambitious features,
+ like memory-safe stack-allocation; type-level tracking of effects, and data-race
+ freedom guarantees for multicore code. The second is the addition of a kind system
+ to OCaml, which provides more control over the representation of memory, in particular
+ allowing for structured data to be represented in a cache-and-prefetch-friendly
+ tabular form. Together, these features pull together some of the most important
+ features for writing high performance code in Rust, while maintaining the relative
+ simplicity of programming in OCaml. In all of this, we will focus less on the
+ type theory, and more on how these features are surfaced to users, the practical
+ problems that they help us solve, and the place in the design space of programming
+ languages that this leaves us in."
+ published: 2025-04-03T20:44:20+00:00
+ author_name: Jane Street
+ author_uri: https://www.youtube.com/channel/UCDsVC_ewpcEW_AQcO-H-RDQ
+ source_link: https://www.youtube.com/feeds/videos.xml?playlist_id=PLCiAikFFaMJoWyXnJ2BWpse5HuiYibNYs
+ source_title: Jane Street - Tech Talks
- title: Universal React in OCaml - David Sancho Moreno - FUN OCaml 2024
url: https://www.youtube.com/watch/Oy3lZl2kE-0?version=3
thumbnail: https://i4.ytimg.com/vi/Oy3lZl2kE-0/hqdefault.jpg
@@ -72,7 +97,10 @@
Twitter: https://x.com/FunOCaml
- Bluesky: https://bsky.app/profile/fun-ocaml.com'
+ Bluesky: https://bsky.app/profile/fun-ocaml.com
+
+
+ #ocaml'
published: 2024-11-20T09:24:00+00:00
author_name: FUN OCaml
author_uri: https://www.youtube.com/channel/UC3TI-fmhJ_g3_n9fHaXGZKA
@@ -109,7 +137,10 @@
Twitter: https://x.com/FunOCaml
- Bluesky: https://bsky.app/profile/fun-ocaml.com'
+ Bluesky: https://bsky.app/profile/fun-ocaml.com
+
+
+ #ocaml'
published: 2024-11-20T09:24:00+00:00
author_name: FUN OCaml
author_uri: https://www.youtube.com/channel/UC3TI-fmhJ_g3_n9fHaXGZKA
@@ -143,7 +174,10 @@
Twitter: https://x.com/FunOCaml
- Bluesky: https://bsky.app/profile/fun-ocaml.com'
+ Bluesky: https://bsky.app/profile/fun-ocaml.com
+
+
+ #ocaml'
published: 2024-11-20T09:24:00+00:00
author_name: FUN OCaml
author_uri: https://www.youtube.com/channel/UC3TI-fmhJ_g3_n9fHaXGZKA
@@ -174,7 +208,10 @@
Twitter: https://x.com/FunOCaml
- Bluesky: https://bsky.app/profile/fun-ocaml.com'
+ Bluesky: https://bsky.app/profile/fun-ocaml.com
+
+
+ #ocaml'
published: 2024-11-20T09:23:58+00:00
author_name: FUN OCaml
author_uri: https://www.youtube.com/channel/UC3TI-fmhJ_g3_n9fHaXGZKA
@@ -207,7 +244,10 @@
Twitter: https://x.com/FunOCaml
- Bluesky: https://bsky.app/profile/fun-ocaml.com'
+ Bluesky: https://bsky.app/profile/fun-ocaml.com
+
+
+ #ocaml'
published: 2024-11-20T09:23:58+00:00
author_name: FUN OCaml
author_uri: https://www.youtube.com/channel/UC3TI-fmhJ_g3_n9fHaXGZKA
@@ -236,7 +276,10 @@
Twitter: https://x.com/FunOCaml
- Bluesky: https://bsky.app/profile/fun-ocaml.com'
+ Bluesky: https://bsky.app/profile/fun-ocaml.com
+
+
+ #ocaml'
published: 2024-11-20T09:23:58+00:00
author_name: FUN OCaml
author_uri: https://www.youtube.com/channel/UC3TI-fmhJ_g3_n9fHaXGZKA
@@ -307,7 +350,10 @@
Twitter: https://x.com/FunOCaml
- Bluesky: https://bsky.app/profile/fun-ocaml.com'
+ Bluesky: https://bsky.app/profile/fun-ocaml.com
+
+
+ #ocaml'
published: 2024-11-20T09:23:58+00:00
author_name: FUN OCaml
author_uri: https://www.youtube.com/channel/UC3TI-fmhJ_g3_n9fHaXGZKA
diff --git a/dune b/dune
index 2a33239f6e..7997026488 100644
--- a/dune
+++ b/dune
@@ -3,22 +3,13 @@
(rule
(target main.css)
(deps
- %{project_root}/tool/tailwind/tailwindcss
(:config %{project_root}/tailwind.config.js)
(:input %{project_root}/src/ocamlorg_frontend/css/styles.css)
(source_tree %{project_root}/src/ocamlorg_frontend))
(action
(chdir
%{project_root}
- (run
- %{project_root}/tool/tailwind/tailwindcss
- -m
- -c
- %{config}
- -i
- %{input}
- -o
- %{target})))))
+ (run tailwindcss -m -c %{config} -i %{input} -o %{target})))))
(subdir
asset/
diff --git a/dune-project b/dune-project
index 5b6c970615..15fb213ca5 100644
--- a/dune-project
+++ b/dune-project
@@ -24,6 +24,9 @@
(using mdx 0.1)
+(package
+ (name tailwindcss) (allow_empty))
+
(package
(name ocamlorg)
(synopsis "Official OCaml website")
@@ -67,6 +70,7 @@
olinkcheck
; tools/ood-gen
ppx_deriving_yaml
+ ppx_import
ppx_stable
ezjsonm
lambdasoup
diff --git a/ocamlorg.opam b/ocamlorg.opam
index 6290d70761..837e74a55d 100644
--- a/ocamlorg.opam
+++ b/ocamlorg.opam
@@ -45,6 +45,7 @@ depends: [
"mdx" {with-test & >= "1.10.0"}
"olinkcheck"
"ppx_deriving_yaml"
+ "ppx_import"
"ppx_stable"
"ezjsonm"
"lambdasoup"
diff --git a/src/ocamlorg_data/data.ml b/src/ocamlorg_data/data.ml
index bcaae984b3..e45f35de70 100644
--- a/src/ocamlorg_data/data.ml
+++ b/src/ocamlorg_data/data.ml
@@ -20,7 +20,13 @@ end
module Changelog = struct
include Changelog
- let get_by_slug slug = List.find_opt (fun x -> String.equal slug x.slug) all
+ let get_by_slug slug =
+ List.find_opt
+ (fun x ->
+ match x with
+ | Release x -> String.equal slug x.slug
+ | Post x -> String.equal slug x.slug)
+ all
end
module Code_example = struct
@@ -152,9 +158,7 @@ module Paper = struct
let get_by_slug slug = List.find_opt (fun x -> String.equal slug x.slug) all
end
-module Planet = struct
- include Planet
-end
+module Planet = Planet
module Release = struct
include Release
@@ -238,3 +242,5 @@ module Conference = struct
let all = Conference.all
let get_by_slug slug = List.find_opt (fun x -> String.equal slug x.slug) all
end
+
+module V2 = V2
diff --git a/src/ocamlorg_data/data.mli b/src/ocamlorg_data/data.mli
index 655415ad3f..a3d8d2ee6f 100644
--- a/src/ocamlorg_data/data.mli
+++ b/src/ocamlorg_data/data.mli
@@ -211,3 +211,5 @@ module Conference : sig
val all : t list
val get_by_slug : string -> t option
end
+
+module V2 : module type of V2
diff --git a/src/ocamlorg_data/data_intf.ml b/src/ocamlorg_data/data_intf.ml
index 87b57a0412..cd23216aea 100644
--- a/src/ocamlorg_data/data_intf.ml
+++ b/src/ocamlorg_data/data_intf.ml
@@ -1,18 +1,5 @@
module Academic_institution = struct
- type location = { lat : float; long : float } [@@deriving of_yaml, show]
-
- let pp_ptime fmt t =
- Format.pp_print_string fmt "Ptime.of_rfc3339 \"";
- Ptime.pp_rfc3339 () fmt t;
- Format.pp_print_string fmt
- "\" |> function Ok (t, _, _) -> t | Error _ -> failwith \"RFC 3339\""
-
- let pp_print_option pp fmt = function
- | None -> Format.pp_print_string fmt "None"
- | Some x ->
- Format.pp_print_string fmt "Some (";
- pp fmt x;
- Format.pp_print_string fmt ")"
+ type location = { lat : float; long : float }
type course = {
name : string;
@@ -22,12 +9,11 @@ module Academic_institution = struct
enrollment : string option;
year : int option;
description : string;
- last_check : Ptime.t option; [@printer pp_print_option pp_ptime]
+ last_check : Ptime.t option;
lecture_notes : bool;
exercises : bool;
video_recordings : bool;
}
- [@@deriving show]
type t = {
name : string;
@@ -44,7 +30,6 @@ module Academic_institution = struct
body_md : string;
body_html : string;
}
- [@@deriving show]
end
module Academic_testimonial = struct
@@ -55,7 +40,6 @@ module Academic_testimonial = struct
publication : string;
year : string;
}
- [@@deriving of_yaml, show]
end
module Blog = struct
@@ -67,38 +51,23 @@ module Blog = struct
only_ocaml : bool;
disabled : bool;
}
- [@@deriving show]
-
- module Post = struct
- type t = {
- title : string;
- url : string;
- slug : string;
- source : source;
- description : string option;
- authors : string list;
- date : string;
- preview_image : string option;
- body_html : string;
- }
- [@@deriving show]
- end
+
+ type post = {
+ title : string;
+ url : string;
+ slug : string;
+ source : source;
+ description : string option;
+ authors : string list;
+ date : string;
+ preview_image : string option;
+ body_html : string;
+ }
end
module Book = struct
- type difficulty = Beginner | Intermediate | Advanced [@@deriving show]
-
- let difficulty_of_string = function
- | "beginner" -> Ok Beginner
- | "intermediate" -> Ok Intermediate
- | "advanced" -> Ok Advanced
- | s -> Error (`Msg ("Unknown difficulty type: " ^ s))
-
- let difficulty_of_yaml = function
- | `String s -> difficulty_of_string s
- | _ -> Error (`Msg "Expected a string for difficulty type")
-
- type link = { description : string; uri : string } [@@deriving of_yaml, show]
+ type difficulty = Beginner | Intermediate | Advanced
+ type link = { description : string; uri : string }
type t = {
title : string;
@@ -116,11 +85,10 @@ module Book = struct
body_md : string;
body_html : string;
}
- [@@deriving show]
end
module Changelog = struct
- type t = {
+ type release = {
title : string;
slug : string;
date : string;
@@ -129,12 +97,24 @@ module Changelog = struct
body_html : string;
body : string;
authors : string list;
+ contributors : string list;
+ }
+
+ type post = {
+ title : string;
+ slug : string;
+ date : string;
+ tags : string list;
+ body_html : string;
+ body : string;
+ authors : string list;
}
- [@@deriving of_yaml, show]
+
+ type t = Release of release | Post of post
end
module Code_examples = struct
- type t = { title : string; body : string } [@@deriving show]
+ type t = { title : string; body : string }
end
module Cookbook = struct
@@ -143,7 +123,6 @@ module Cookbook = struct
slug : string;
subcategories : category list;
}
- [@@deriving show]
type task = {
title : string;
@@ -151,17 +130,14 @@ module Cookbook = struct
category_path : string list;
description : string option;
}
- [@@deriving show]
type code_block_with_explanation = { code : string; explanation : string }
- [@@deriving show]
type package = {
name : string;
tested_version : string;
used_libraries : string list;
}
- [@@deriving of_yaml, show]
type t = {
slug : string;
@@ -172,26 +148,11 @@ module Cookbook = struct
code_plaintext : string;
discussion_html : string;
}
- [@@deriving show]
end
module Event = struct
type event_type = Meetup | Conference | Seminar | Hackathon | Retreat
- [@@deriving show]
-
- let event_type_of_string = function
- | "meetup" -> Ok Meetup
- | "conference" -> Ok Conference
- | "seminar" -> Ok Seminar
- | "hackathon" -> Ok Hackathon
- | "retreat" -> Ok Retreat
- | s -> Error (`Msg ("Unknown event type: " ^ s))
-
- let event_type_of_yaml = function
- | `String s -> event_type_of_string s
- | _ -> Error (`Msg "Expected a string for difficulty type")
-
- type location = { lat : float; long : float } [@@deriving of_yaml, show]
+ type location = { lat : float; long : float }
type recurring_event = {
title : string;
@@ -202,10 +163,8 @@ module Event = struct
location : location option;
event_type : event_type;
}
- [@@deriving of_yaml, show]
type utc_datetime = { yyyy_mm_dd : string; utc_hh_mm : string option }
- [@@deriving of_yaml, show]
type t = {
title : string;
@@ -223,21 +182,10 @@ module Event = struct
recurring_event : recurring_event option;
event_type : event_type;
}
- [@@deriving show]
end
module Exercise = struct
- type difficulty = Beginner | Intermediate | Advanced [@@deriving show]
-
- let of_string = function
- | "beginner" -> Ok Beginner
- | "intermediate" -> Ok Intermediate
- | "advanced" -> Ok Advanced
- | s -> Error (`Msg ("Unknown difficulty type: " ^ s))
-
- let difficulty_of_yaml = function
- | `String s -> of_string s
- | _ -> Error (`Msg "Expected a string for difficulty type")
+ type difficulty = Beginner | Intermediate | Advanced
type t = {
title : string;
@@ -249,42 +197,12 @@ module Exercise = struct
solution : string;
tutorials : string list;
}
- [@@deriving show]
end
module Governance = struct
- module Member = struct
- type t = { name : string; github : string; role : string }
- [@@deriving of_yaml, show]
-
- let compare a b = String.compare a.github b.github
- end
-
- type contact_kind = GitHub | Email | Discord | Chat [@@deriving show]
-
- let contact_kind_of_yaml = function
- | `String "github" -> Ok GitHub
- | `String "email" -> Ok Email
- | `String "discord" -> Ok Discord
- | `String "chat" -> Ok Chat
- | x -> (
- match Yaml.to_string x with
- | Ok str ->
- Error
- (`Msg
- ("\"" ^ str
- ^ "\" is not a valid contact_kind! valid options are: github, \
- email, discord, chat"))
- | Error _ -> Error (`Msg "Invalid Yaml value"))
-
- let contact_kind_to_yaml = function
- | GitHub -> `String "github"
- | Email -> `String "email"
- | Discord -> `String "discord"
- | Chat -> `String "chat"
-
+ type member = { name : string; github : string; role : string }
+ type contact_kind = GitHub | Email | Discord | Chat
type contact = { name : string; link : string; kind : contact_kind }
- [@@deriving of_yaml, show]
type dev_meeting = {
date : string;
@@ -293,18 +211,16 @@ module Governance = struct
calendar : string option;
notes : string;
}
- [@@deriving of_yaml, show]
type team = {
id : string;
name : string;
description : string;
contacts : contact list;
- dev_meeting : dev_meeting option; [@default None] [@key "dev-meeting"]
- members : Member.t list; [@default []]
- subteams : team list; [@default []]
+ dev_meeting : dev_meeting option;
+ members : member list;
+ subteams : team list;
}
- [@@deriving of_yaml, show]
end
module Industrial_user = struct
@@ -320,15 +236,11 @@ module Industrial_user = struct
body_md : string;
body_html : string;
}
- [@@deriving show]
end
module Is_ocaml_yet = struct
type external_package = { url : string; synopsis : string }
- [@@deriving of_yaml, show]
-
type package = { name : string; extern : external_package option }
- [@@deriving of_yaml, show]
type category = {
name : string;
@@ -337,7 +249,6 @@ module Is_ocaml_yet = struct
packages : package list;
slug : string;
}
- [@@deriving show]
type t = {
id : string;
@@ -346,7 +257,6 @@ module Is_ocaml_yet = struct
categories : category list;
body_html : string;
}
- [@@deriving show]
end
module Job = struct
@@ -358,7 +268,6 @@ module Job = struct
company : string;
company_logo : string;
}
- [@@deriving of_yaml, show]
end
module Testimonial = struct
@@ -369,7 +278,6 @@ module Testimonial = struct
role : string;
logo : string;
}
- [@@deriving of_yaml, show]
end
module News = struct
@@ -382,7 +290,6 @@ module News = struct
body_html : string;
authors : string list;
}
- [@@deriving show]
end
module Opam_user = struct
@@ -392,7 +299,6 @@ module Opam_user = struct
github_username : string option;
avatar : string option;
}
- [@@deriving of_yaml, show]
end
module Outreachy = struct
@@ -405,9 +311,8 @@ module Outreachy = struct
mentors : string list;
video : string option;
}
- [@@deriving of_yaml, show]
- type t = { name : string; projects : project list } [@@deriving of_yaml, show]
+ type t = { name : string; projects : project list }
end
module Page = struct
@@ -420,11 +325,10 @@ module Page = struct
body_md : string;
body_html : string;
}
- [@@deriving show]
end
module Paper = struct
- type link = { description : string; uri : string } [@@deriving of_yaml, show]
+ type link = { description : string; uri : string }
type t = {
title : string;
@@ -437,19 +341,10 @@ module Paper = struct
links : link list;
featured : bool;
}
- [@@deriving show]
end
module Release = struct
- type kind = [ `Compiler ] [@@deriving show]
-
- let kind_of_string = function
- | "compiler" -> Ok `Compiler
- | s -> Error (`Msg ("Unknown release type: " ^ s))
-
- let kind_of_yaml = function
- | `String s -> kind_of_string s
- | _ -> Error (`Msg "Expected a string for release type")
+ type kind = [ `Compiler ]
type t = {
kind : kind;
@@ -464,7 +359,6 @@ module Release = struct
body_md : string;
body_html : string;
}
- [@@deriving show]
end
module Resource = struct
@@ -476,7 +370,6 @@ module Resource = struct
source_url : string option;
featured : bool;
}
- [@@deriving of_yaml, show]
end
module Success_story = struct
@@ -494,23 +387,10 @@ module Success_story = struct
body_md : string;
body_html : string;
}
- [@@deriving show]
end
module Tool = struct
type lifecycle = [ `Incubate | `Active | `Sustain | `Deprecate ]
- [@@deriving show]
-
- let lifecycle_of_string = function
- | "incubate" -> Ok `Incubate
- | "active" -> Ok `Active
- | "sustain" -> Ok `Sustain
- | "deprecate" -> Ok `Deprecate
- | s -> Error (`Msg ("Unknown lifecycle type: " ^ s))
-
- let lifecycle_of_yaml = function
- | `String s -> lifecycle_of_string s
- | _ -> Error (`Msg "Expected a string for lifecycle type")
type t = {
name : string;
@@ -521,15 +401,11 @@ module Tool = struct
description : string;
lifecycle : lifecycle;
}
- [@@deriving show]
end
module Tool_page = struct
type toc = { title : string; href : string; children : toc list }
- [@@deriving of_yaml, show]
-
type contribute_link = { url : string; description : string }
- [@@deriving of_yaml, show]
type t = {
title : string;
@@ -542,42 +418,23 @@ module Tool_page = struct
toc : toc list;
body_html : string;
}
- [@@deriving show]
end
module Tutorial = struct
- module Section = struct
- type t = GetStarted | Language | Platform | Guides [@@deriving show]
-
- let of_string = function
- | "getting-started" -> Ok GetStarted
- | "language" -> Ok Language
- | "platform" -> Ok Platform
- | "guides" -> Ok Guides
- | s -> Error (`Msg ("Unknown section: " ^ s))
- end
-
+ type section = GetStarted | Language | Platform | Guides
type toc = { title : string; href : string; children : toc list }
- [@@deriving show]
-
type contribute_link = { url : string; description : string }
- [@@deriving of_yaml, show]
-
type banner = { image : string; url : string; alt : string }
- [@@deriving of_yaml, show]
type external_tutorial = {
tag : string;
banner : banner;
contribute_link : contribute_link;
}
- [@@deriving of_yaml, show]
-
- type recommended_next_tutorials = string list [@@deriving of_yaml, show]
- type prerequisite_tutorials = string list [@@deriving of_yaml, show]
+ type recommended_next_tutorials = string list
+ type prerequisite_tutorials = string list
type search_document_section = { title : string; id : string }
- [@@deriving show]
type search_document = {
title : string;
@@ -586,7 +443,6 @@ module Tutorial = struct
content : string;
slug : string;
}
- [@@deriving show]
type t = {
title : string;
@@ -594,7 +450,7 @@ module Tutorial = struct
fpath : string;
slug : string;
description : string;
- section : Section.t;
+ section : section;
category : string;
external_tutorial : external_tutorial option;
body_md : string;
@@ -603,7 +459,6 @@ module Tutorial = struct
recommended_next_tutorials : recommended_next_tutorials;
prerequisite_tutorials : prerequisite_tutorials;
}
- [@@deriving show]
end
module Video = struct
@@ -611,30 +466,18 @@ module Video = struct
title : string;
url : string;
thumbnail : string;
- description : string; [@default ""]
+ description : string;
published : string;
author_name : string;
author_uri : string;
source_link : string;
source_title : string;
}
- [@@deriving yaml, show]
end
module Conference = struct
- type role = [ `Co_chair | `Chair ] [@@deriving show]
-
- let role_of_string = function
- | "chair" -> Ok `Chair
- | "co-chair" -> Ok `Co_chair
- | s -> Error (`Msg ("Unknown role type: " ^ s))
-
- let role_of_yaml = function
- | `String s -> role_of_string s
- | _ -> Error (`Msg "Expected a string for role type")
-
+ type role = [ `Co_chair | `Chair ]
type important_date = { date : string; info : string }
- [@@deriving of_yaml, show]
type committee_member = {
name : string;
@@ -642,18 +485,17 @@ module Conference = struct
affiliation : string option;
picture : string option;
}
- [@@deriving of_yaml, show]
type presentation = {
title : string;
authors : string list;
link : string option;
- video : string option;
+ watch_ocamlorg_video : string option;
+ youtube_video : string option;
slides : string option;
poster : bool;
additional_links : string list;
}
- [@@deriving of_yaml, show]
type t = {
title : string;
@@ -667,15 +509,10 @@ module Conference = struct
body_md : string;
body_html : string;
}
- [@@deriving of_yaml, show]
end
(* Depends on Video and Blog modules to define the different kinds of entries of
the OCaml Planet *)
module Planet = struct
- type entry = BlogPost of Blog.Post.t | Video of Video.t [@@deriving show]
-
- let date_of_post = function
- | BlogPost { date; _ } -> date
- | Video { published; _ } -> published
+ type entry = BlogPost of Blog.post | Video of Video.t
end
diff --git a/src/ocamlorg_data/dune b/src/ocamlorg_data/dune
index ac90548237..befc1ea06d 100644
--- a/src/ocamlorg_data/dune
+++ b/src/ocamlorg_data/dune
@@ -340,3 +340,22 @@
(with-stdout-to
%{target}
(run %{ood_gen} conferences)))))
+
+(rule
+ (target v2.ml)
+ (deps
+ (:v2
+ (source_tree %{workspace_root}/data/v2)))
+ (action
+ (chdir
+ %{workspace_root}/data/v2
+ (with-stdout-to
+ %{target}
+ (progn
+ (echo "let assets = [\n")
+ (pipe-stdout
+ (run find . -type f)
+ (run cut -b 2-)
+ (run sed "s/^/ \"/")
+ (run sed "s/$/\";/"))
+ (echo "]\n"))))))
diff --git a/src/ocamlorg_frontend/components/icons.eml b/src/ocamlorg_frontend/components/icons.eml
index f1adcb17a7..6fe38a63a7 100644
--- a/src/ocamlorg_frontend/components/icons.eml
+++ b/src/ocamlorg_frontend/components/icons.eml
@@ -51,7 +51,12 @@ let calendar class_ =
-let changelog class_ =
+let changelog_release class_ =
+
+
+let changelog_post class_ =
diff --git a/src/ocamlorg_frontend/components/navmap.eml b/src/ocamlorg_frontend/components/navmap.eml
index cf56383fa8..20c4a0457e 100644
--- a/src/ocamlorg_frontend/components/navmap.eml
+++ b/src/ocamlorg_frontend/components/navmap.eml
@@ -28,11 +28,12 @@ let kind_title = function
| Class_type -> "Class type"
| _ -> "?"
-let title_style = "flex-1 flex-nowrap py-1 md:py-0.5 pr-1 text-title dark:text-dark-title"
+let title_style = "flex-1 flex-nowrap py-1 md:py-0.5 pr-1 text-title dark:text-dark-title truncate"
-let htmx_attributes = "hx-boost=\"true\" hx-ext=\"multi-swap\" hx-swap=\"multi:#htmx-head,#htmx-sidebar,#htmx-content,#htmx-right-sidebar,#htmx-breadcrumbs\" hx-push-url=\"true\""
+let htmx_attributes = "hx-boost=\"true\" hx-ext=\"multi-swap\" hx-swap=\"multi:#htmx-head,#htmx-content,#htmx-right-sidebar,#htmx-breadcrumbs\" hx-push-url=\"true\""
let icon_style = function
+ | Page -> "navmap-tag page-tag"
| Library -> "navmap-tag library-tag"
| Module -> "navmap-tag module-tag"
| Module_type -> "navmap-tag module-type-tag"
@@ -75,20 +76,20 @@ let rec nested_render ~path (item : toc) =
let active_style = if item.title = fragment then (if List.length path = 0 then "bg-gray-200 dark:bg-dark-tertiary_bt_hover font-medium dark:font-semibold" else "border-transparent bg-gray-100 dark:bg-dark-tertiary_bt_hover font-medium dark:font-semibold") else "border-transparent" in
}">
- ">
+ ">
<% (match item.href with | None -> %>
">
<%s! item.title %>
-
+
<% | Some href -> %>
class="<%s title_style %> overflow-hidden truncate text-title dark:text-dark-title transition-colors hover:text-primary">
<%s! item.title %>
-
+
<% ); %>
diff --git a/src/ocamlorg_frontend/components/package_breadcrumbs.eml b/src/ocamlorg_frontend/components/package_breadcrumbs.eml
index 8c43612933..7a9b0751a9 100644
--- a/src/ocamlorg_frontend/components/package_breadcrumbs.eml
+++ b/src/ocamlorg_frontend/components/package_breadcrumbs.eml
@@ -1,30 +1,33 @@
-type library_path_item =
- | Module of { name: string; href: string; }
- | ModuleType of { name: string; href: string; }
- | Parameter of { name: string; href: string; number: int; }
- | Class of { name: string; href: string; }
- | ClassType of { name: string; href: string; }
+type breadcrumb = {
+ name: string;
+ href: string option;
+}
-type docs_path =
- | Index
- | Page of string
- | Library of string * library_path_item list
+type path_item =
+ | Module of breadcrumb
+ | ModuleType of breadcrumb
+ | Parameter of breadcrumb * int
+ | Class of breadcrumb
+ | ClassType of breadcrumb
+ | Page of breadcrumb
-let kind_tag (m : library_path_item) = match m with
+let kind_tag (m : path_item) = match m with
| Module _ ->
| ModuleType _ ->
- | Parameter { number; _ } ->
+ | Parameter (_, number) ->
" class="breadcrumbs-tag parameter-tag"><%s "Parameter #" ^ (Int.to_string number) %>
| Class _ ->
| ClassType _ ->
+ | Page _ ->
+
type path =
| Overview of string option
- | Documentation of (docs_path)
+ | Documentation of path_item list
let render_package_and_version
~path
@@ -57,54 +60,49 @@ let render_package_and_version
-type breadcrumb = {
- name: string;
- href: string;
-}
+let path_item_to_breadcrumb = function
+ | Module x | ModuleType x | Class x | ClassType x | Parameter (x, _) | Page x
+ ->
+ x
-let library_path_item_to_breadcrumb = function
- | Module x -> { name = x.name; href = x.href }
- | ModuleType x -> { name = x.name; href = x.href }
- | Class x -> { name = x.name; href = x.href }
- | ClassType x -> { name = x.name; href = x.href }
- | Parameter x -> { name = x.name; href = x.href }
+let is_page : path_item -> bool = function Page _ -> true | _ -> false
-let render_library_path_breadcrumbs
-~library_name
-~(path: library_path_item list) =
- let render_breadcrumb i b =
- if i < List.length path - 1 then
- <%s b.name %>
- else
- <%s b.name %>
+let render_path_breadcrumbs
+~(path: path_item list) =
+ let pages, modules = List.partition is_page path in
+ let render_breadcrumb max i b =
+ match b.href with
+ | None when i < max ->
+ <%s b.name %>
+ | None ->
+ <%s b.name %>
+ | Some href when i < max ->
+ <%s b.name %>
+ | Some href ->
+ <%s b.name %>
in
-
- <%s library_name %> lib
-
-
- <%s! String.concat "." (path |> List.map library_path_item_to_breadcrumb |> List.mapi render_breadcrumb); %>
- <%s! kind_tag (List.hd (List.rev path)) %>
-
+ let li content =
+
+ <%s! content %>
+
+ in
+ let page_items = List.fold_left (fun (i, acc) item ->
+ (i+1, acc ^ li (render_breadcrumb (List.length pages - 1) i (path_item_to_breadcrumb item)))) (0,"") pages |> snd in
+ let ms =
+ <%s! String.concat "." (List.mapi (render_breadcrumb (List.length modules - 1)) (List.map path_item_to_breadcrumb modules)) %>
+ in
+ let last = kind_tag (List.hd (List.rev path)) in
+ if List.length modules > 0 then
+ page_items ^ li (ms ^ last)
+ else
+ page_items
let render_docs_path_breadcrumbs
-~(path: docs_path)
-(package: Package.package)
+~(path: path_item list)
=
- let version = Package.url_version package in
@@ -122,8 +120,7 @@ let render_overview_breadcrumbs
let render
~(path: path)
-(package: Package.package)
=
match path with
| Overview page -> render_overview_breadcrumbs page
- | Documentation (docs_path) -> render_docs_path_breadcrumbs ~path:docs_path package
+ | Documentation (docs_path) -> render_docs_path_breadcrumbs ~path:docs_path
diff --git a/src/ocamlorg_frontend/layouts/package_layout.eml b/src/ocamlorg_frontend/layouts/package_layout.eml
index 4f95ededfe..a8bf7b568b 100644
--- a/src/ocamlorg_frontend/layouts/package_layout.eml
+++ b/src/ocamlorg_frontend/layouts/package_layout.eml
@@ -199,7 +199,7 @@ Layout.base
let list_item = document.createElement("li");
let a = document.createElement("a");
- let href = "/" + entry.url;
+ let href = entry.url;
a.href = href;
a.id = "search-result-"+entry.id;
a.classList.add("search-entry", kind.innerText.slice(0,3));
diff --git a/src/ocamlorg_frontend/pages/academic_institutions.eml b/src/ocamlorg_frontend/pages/academic_institutions.eml
index 33621e889e..2342df9221 100644
--- a/src/ocamlorg_frontend/pages/academic_institutions.eml
+++ b/src/ocamlorg_frontend/pages/academic_institutions.eml
@@ -29,7 +29,7 @@ Layout.render
30+ Academic Entries
- To list an academic institution, check out the Contributing Guide on GitHub.
+ To list an academic institution, check out the Contributing Guide on GitHub.
- Read the latest releases and updates from the OCaml ecosystem.
+ Read the latest releases and updates from the OCaml compiler, OCaml infrastructure and the OCaml Platform Tools.
@@ -55,40 +117,7 @@ News_layout.single_column_layout
- <% news |> List.iter (fun (item : Data.Changelog.t) -> %>
-
-
-
-
- <%s! Icons.changelog "w-6 h-6" %>
-
-
- <%s item.title %>
-
-
- <% (match item.tags with [] -> () | _ -> %>
-
- <% item.tags |> List.iter (fun tag -> %>
- "><%s tag %>
- <% ); %>
-
- <% ); %>
-
-
-
-
- <%s! item.body_html %>
- <% (match item.changelog_html with | None -> () | Some changelog -> %>
-
- See full changelog
- <%s! changelog %>
-
- <% ); %>
-
-
-
- <% ); %>
+ <%s! news |> List.map (fun (item : Data.Changelog.t) -> render_changelog_entry ?current_tag item ) |> String.concat "" %>
<%s! Pagination.render pagination_info %>
diff --git a/src/ocamlorg_frontend/pages/changelog_entry.eml b/src/ocamlorg_frontend/pages/changelog_entry.eml
index b197a2d1f8..cb1b1cc55e 100644
--- a/src/ocamlorg_frontend/pages/changelog_entry.eml
+++ b/src/ocamlorg_frontend/pages/changelog_entry.eml
@@ -1,52 +1,99 @@
-let render (change : Data.Changelog.t) =
-Layout.render
-~title:(Printf.sprintf "%s • OCaml Changelog" change.title)
-~description:change.title @@
-
-
-
+let render (entry : Data.Changelog.t) = match entry with
+| Release release ->
+ Layout.render
+ ~title:(Printf.sprintf "%s • OCaml Changelog" release.title)
+ ~description:release.title @@
+
+
+
-
-
- <%s change.title %>
-
- <% (match change.tags with [] -> () | _ -> %>
-
- <% change.tags |> List.iter (fun tag -> %>
- -
-
- <%s tag %>
-
-
+
+
+ <%s release.title %>
+
+ <% (match release.tags with [] -> () | _ -> %>
+
+ <% release.tags |> List.iter (fun tag -> %>
+ -
+
+ <%s tag %>
+
+
+ <% ); %>
+
<% ); %>
-
- <% ); %>
-
+
-
-
-
- <%s! change.body_html %>
- <% (match change.changelog_html with | None -> () | Some changelog -> %>
-
- See full changelog
- <%s! changelog %>
-
- <% ); %>
-
-
+
+
+
+ <%s! release.body_html %>
+ <% (match release.changelog_html with | None -> () | Some changelog -> %>
+
+ See full changelog
+ <%s! changelog %>
+
+ <% ); %>
+
+
+
-
\ No newline at end of file
+| Post post ->
+ Layout.render
+ ~title:(Printf.sprintf "%s • OCaml Changelog" post.title)
+ ~description:post.title @@
+
+
+
+
+
+
+
+ <%s post.title %>
+
+ <% (match post.tags with [] -> () | _ -> %>
+
+ <% post.tags |> List.iter (fun tag -> %>
+ -
+
+ <%s tag %>
+
+
+ <% ); %>
+
+ <% ); %>
+
+
+
+
+
+ <%s! post.body_html %>
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ocamlorg_frontend/pages/community.eml b/src/ocamlorg_frontend/pages/community.eml
index c86cd0a1b7..99f945bca1 100644
--- a/src/ocamlorg_frontend/pages/community.eml
+++ b/src/ocamlorg_frontend/pages/community.eml
@@ -67,7 +67,7 @@ Community_layout.single_column_layout
Video
<%s! social_list_item ~text:("Community videos such as previous OCaml Conference presentation - ") ~href:("https://watch.ocaml.org/videos/recently-added") ~left_icon:(Icons.peertube "w-10 h-10") ~title:("Watch") "" %>
<%s! social_list_item ~text:("Live Streams about OCaml - ") ~href:("https://twitch.tv/search?term=ocaml") ~left_icon:(Icons.twitch "w-10 h-10") ~title:("Twitch") "" %>
- <%s! social_list_item ~text:("OCaml on YouTube - ") ~href:("https://www.youtube.com/results?search_query=OCaml") ~left_icon:(Icons.youtube "w-10 h-10") ~title:("YouTube") "" %>
+ <%s! social_list_item ~text:("OCaml on YouTube - ") ~href:("https://www.youtube.com/@OCamlLang") ~left_icon:(Icons.youtube "w-10 h-10") ~title:("YouTube") "" %>
Microblogging
@@ -81,7 +81,7 @@ Community_layout.single_column_layout
Mailing Lists
- <%s! social_list_item ~text:("Share experience, exchange ideas and code, and report on applications - ") ~href:("https://inbox.vuxu.org/caml-list") ~left_icon:(Icons.email "w-10 h-10") ~title:("Caml User's Mailing List") "" %>
+ <%s! social_list_item ~text:("Share experience, exchange ideas and code, and report on applications - ") ~href:("https://inbox.ci.dev/caml-list") ~left_icon:(Icons.email "w-10 h-10") ~title:("Caml User's Mailing List") "" %>
diff --git a/src/ocamlorg_frontend/pages/conference.eml b/src/ocamlorg_frontend/pages/conference.eml
index 0cd4cb0c9b..e2a17ead7e 100644
--- a/src/ocamlorg_frontend/pages/conference.eml
+++ b/src/ocamlorg_frontend/pages/conference.eml
@@ -89,7 +89,10 @@ Community_layout.single_column_layout
<% (match presentation.link with None -> () | Some link -> %>
<%s! resource_link ~link (Icons.link "h-6 w-6") "Details" %>
<% ); %>
- <% (match presentation.video with None -> () | Some link -> %>
+ <% (match presentation.youtube_video with None -> () | Some link -> %>
+ <%s! resource_link ~link (Icons.youtube "h-6 w-6") "View on YouTube" %>
+ <% ); %>
+ <% (match presentation.watch_ocamlorg_video with None -> () | Some link -> %>
<%s! resource_link ~link (Icons.video "h-6 w-6") "View Video" %>
<% ); %>
<% (match presentation.slides with None -> () | Some link -> %>
@@ -106,6 +109,7 @@ Community_layout.single_column_layout
+<% if conference.program_committee <> [] then ( %>
Conference Details
@@ -132,22 +136,57 @@ Community_layout.single_column_layout
-<% let videos = List.filter (fun (p : Data.Conference.presentation) -> Option.is_some p.video)
+<% ); %>
+
+<% if conference.organising_committee <> [] then ( %>
+
+
+ Conference Details
+ Organisers
+
+ <%
+ let committee_with_pictures = List.filter (fun (x : Data.Conference.committee_member) -> Option.is_some x.picture) conference.organising_committee in
+ let committee_without_pictures = List.filter (fun (x : Data.Conference.committee_member) -> Option.is_none x.picture) conference.organising_committee in
+ let names = List.map (fun (x : Data.Conference.committee_member) -> match x.affiliation with None -> (x.name, "") | Some affiliation -> (x.name, affiliation)) committee_without_pictures in
+ committee_with_pictures |> List.iter (fun (member : Data.Conference.committee_member) -> %>
+
+
+ <%s member.name %>
+ <% (match member.affiliation with None -> () | Some affiliation -> %>
+ <%s affiliation %>
+ <% ); %>
+
+ <% ); %>
+
+
+ <% names |> List.iter (fun (name, affiliation) -> %>
+ •<%s name %>
+ <% (match affiliation with "" -> () | _ -> %>
+ (<%s affiliation %>)
+ <% ); %>
+
+ <% ); %>
+
+
+
+<% ); %>
+
+<% let videos = List.filter (fun (p : Data.Conference.presentation) -> Option.is_some p.watch_ocamlorg_video)
conference.presentations in (match List.length videos with | 0 -> () | _ -> %>
Some Videos
- <% let videos = List.filteri (fun i _ -> i < 6) videos in videos |> List.iter (fun (video : Data.Conference.presentation) -> %>
+ <% let videos = List.filteri (fun i _ -> i < 6) videos in videos |> List.iter (fun (presentation : Data.Conference.presentation) -> %>
- <%s video.title %>
+ <%s presentation.title %>
<% ); %>
diff --git a/src/ocamlorg_frontend/pages/governance.eml b/src/ocamlorg_frontend/pages/governance.eml
index a318ef51cb..7245c96dcc 100644
--- a/src/ocamlorg_frontend/pages/governance.eml
+++ b/src/ocamlorg_frontend/pages/governance.eml
@@ -1,4 +1,4 @@
-module MemberSet = Set.Make (Data.Governance.Member)
+module MemberSet = Set.Make (struct type t = Data.Governance.member let compare a b = let open Data.Governance in String.compare a.github b.github end)
let count_members (team : Data.Governance.team) =
MemberSet.cardinal (MemberSet.of_list (team.members @ List.concat (List.map (fun (team : Data.Governance.team) -> team.members) team.subteams)))
@@ -75,4 +75,4 @@ and what makes it unique." @@
" alt="camel head illustration">
-
\ No newline at end of file
+
diff --git a/src/ocamlorg_frontend/pages/governance_team.eml b/src/ocamlorg_frontend/pages/governance_team.eml
index 6c1d38b681..ba1cf1f160 100644
--- a/src/ocamlorg_frontend/pages/governance_team.eml
+++ b/src/ocamlorg_frontend/pages/governance_team.eml
@@ -4,7 +4,7 @@ let contact_icon (kind : Data.Governance.contact_kind) = match kind with
| Discord -> Icons.discord
| Chat -> Icons.chat
-let render_team_member (member : Data.Governance.Member.t) =
+let render_team_member (member : Data.Governance.member) =
@@ -58,7 +58,7 @@ let render_subteam (team : Data.Governance.team) =
<% ); %>
- <%s! team.members |> List.map (fun (member : Data.Governance.Member.t) -> render_team_member member ) |> String.concat "\n" %>
+ <%s! team.members |> List.map (fun (member : Data.Governance.member) -> render_team_member member ) |> String.concat "\n" %>
@@ -107,7 +107,7 @@ Layout.render
People
- <%s! t.members |> List.map (fun (member : Data.Governance.Member.t) -> render_team_member member ) |> String.concat "\n" %>
+ <%s! t.members |> List.map (fun (member : Data.Governance.member) -> render_team_member member ) |> String.concat "\n" %>
<% ); %>
diff --git a/src/ocamlorg_frontend/pages/home.eml b/src/ocamlorg_frontend/pages/home.eml
index f802e43606..5ec47294cc 100644
--- a/src/ocamlorg_frontend/pages/home.eml
+++ b/src/ocamlorg_frontend/pages/home.eml
@@ -12,6 +12,48 @@ let package_card ~href ~img_path ~name description =
+let render_small_changelog_entry (item : Data.Changelog.t) = match item with
+ | Release item ->
+
+
+
+
+
+ <%s! Icons.changelog_release "w-6 h-6" %>
+
+ <%s item.title %>
+
+
+
+ <%s! String.sub item.body 0 (min (String.length item.body - 1) 100) %>...
+
+
+ See full changelog
+
+
+
+
+ | Post item ->
+
+
+
+
+
+ <%s! Icons.changelog_post "w-6 h-6" %>
+
+ <%s item.title %>
+
+
+
+ <%s! String.sub item.body 0 (min (String.length item.body - 1) 100) %>...
+
+
+ See full changelog
+
+
+
+
+
let render
~(latest_release: Data.Release.t)
~(lts_release: Data.Release.t)
@@ -295,28 +337,9 @@ Layout.render
Compiler and Platform Tools
- <% let changelogs_length = List.length changelogs in changelogs |>
- List.iteri (fun index (item : Data.Changelog.t) -> %>
- ">
-
-
-
-
- <%s! Icons.changelog "w-6 h-6" %>
-
- <%s item.title %>
-
-
-
- <%s! String.sub item.body 0 (min (String.length item.body - 1) 100) %>...
-
-
- See full changelog
-
-
-
-
- <% ); %>
+ <%s! changelogs
+ |> List.map (fun (item : Data.Changelog.t) -> render_small_changelog_entry item )
+ |> String.concat "" %>
See Full Changelog
<%s! Icons.chevron_right "h-5 w-5" %>
diff --git a/src/ocamlorg_frontend/pages/industrial_businesses.eml b/src/ocamlorg_frontend/pages/industrial_businesses.eml
index b25a268326..cb5a6d914d 100644
--- a/src/ocamlorg_frontend/pages/industrial_businesses.eml
+++ b/src/ocamlorg_frontend/pages/industrial_businesses.eml
@@ -23,7 +23,7 @@ Layout.render
Other Industry Users of OCaml
With its strong security features and high performance, several companies rely on OCaml to keep their data operating both safely and efficiently. On this page, you can get an overview of the companies in the community and learn more about how they use OCaml.
- If you want to contribute to add a new industrial user, check out the Contributing Guide on GitHub.
+ If you want to contribute to add a new industrial user, check out the Contributing Guide on GitHub.
<% businesses |> List.iter (fun (item : Data.Industrial_user.t) -> let logo = match item.logo with | Some x -> Ocamlorg_static.Media.url x | None -> "" in %>
diff --git a/src/ocamlorg_frontend/pages/industrial_users.eml b/src/ocamlorg_frontend/pages/industrial_users.eml
index bef846dfd5..72958d5498 100644
--- a/src/ocamlorg_frontend/pages/industrial_users.eml
+++ b/src/ocamlorg_frontend/pages/industrial_users.eml
@@ -53,7 +53,7 @@ let job_card (job: Data.Job.t) =
-let render ~(users : Data.Industrial_user.t list) ~success_stories ~(top_story: Data.Success_story.t) ~(testimonials: Data.Testimonial.t list) ~jobs_with_count =
+let render ~number_of_users ~(users : Data.Industrial_user.t list) ~success_stories ~(top_story: Data.Success_story.t) ~(testimonials: Data.Testimonial.t list) ~jobs_with_count =
Layout.render
~use_swiper:true
~title:"OCaml in Industry"
@@ -209,7 +209,7 @@ the community and learn more about how they use OCaml."
diff --git a/src/ocamlorg_frontend/pages/is_ocaml_yet.eml b/src/ocamlorg_frontend/pages/is_ocaml_yet.eml
index 57f2aa7060..1ba261882a 100644
--- a/src/ocamlorg_frontend/pages/is_ocaml_yet.eml
+++ b/src/ocamlorg_frontend/pages/is_ocaml_yet.eml
@@ -27,36 +27,11 @@ Learn_layout.three_column_layout
<%s! meta.body_html %>
-
- <% meta.categories |> List.iter (fun (category : Data.Is_ocaml_yet.category) -> %>
- -
- <%s category.status %> <%s category.name %>
-
- <% ); %>
-
-
- -
- 🟢 : stable, tested and mature
-
- -
- 🟡 : getting there, stable but still maturing
-
- -
- 🟠 : not yet stable, but progressing
-
- -
- 🔴 : unstable/incomplete, needs work
-
- -
- 🆘 : barely there, needs serious work
-
-
Details
<% meta.categories |> List.iter (fun (category : Data.Is_ocaml_yet.category) -> %>
- <%s category.status %>
<%s category.name %>
<%s! category.description %>
diff --git a/src/ocamlorg_frontend/pages/learn.eml b/src/ocamlorg_frontend/pages/learn.eml
index 7802d1aec0..feec9c9966 100644
--- a/src/ocamlorg_frontend/pages/learn.eml
+++ b/src/ocamlorg_frontend/pages/learn.eml
@@ -70,8 +70,8 @@ Learn_layout.single_column_layout
]
~see_more:{href = Url.getting_started; title = "Get Started"}
%>
- <%s! Learn_components.tutorial_block ~icon:Learn_components.beginner_section_icon ~title:"LANGUAGE" ~heading:"The OCaml Language"
- ~description:"An in-depth explanation of language features and data structures from the Standard Library"
+ <%s! Learn_components.tutorial_block ~icon:Learn_components.beginner_section_icon ~title:"LANGUAGE" ~heading:"The OCaml Language"
+ ~description:"An in-depth explanation of language features and data structures from the Standard Library"
~tutorial_links:[
{href = Url.tutorial "values-and-functions"; title = "Values and Functions"};
{href = Url.tutorial "basic-data-types"; title = "Data Types and Pattern Matching"};
@@ -129,8 +129,8 @@ Learn_layout.single_column_layout
- <%s! Learn_components.tutorial_block ~icon:Learn_components.intermediate_section_icon ~title:"GUIDES" ~heading:"Practical-Minded Tutorials and Guides"
- ~description:"How to solve real-world problems in OCaml"
+ <%s! Learn_components.tutorial_block ~icon:Learn_components.intermediate_section_icon ~title:"GUIDES" ~heading:"Practical-Minded Tutorials and Guides"
+ ~description:"How to solve real-world problems in OCaml"
~tutorial_links:[
{href = Url.tutorial "formatting-text"; title = "Formatting and Wrapping Text"};
{href = Url.tutorial "debugging"; title = "Debugging"};
@@ -139,13 +139,13 @@ Learn_layout.single_column_layout
]
~see_more:{href = Url.learn_guides; title = "See More Guides"}
%>
- <%s! Learn_components.tutorial_block ~icon:Learn_components.intermediate_section_icon ~title:"PLATFORM" ~heading:"The OCaml Platform"
- ~description:"Learn to leverage the tooling around OCaml and create your own projects and libraries"
+ <%s! Learn_components.tutorial_block ~icon:Learn_components.intermediate_section_icon ~title:"PLATFORM" ~heading:"The OCaml Platform"
+ ~description:"Learn to leverage the tooling around OCaml and create your own projects and libraries"
~tutorial_links:[
{href = Url.tutorial "bootstrapping-a-dune-project"; title = "Bootstrapping a Project"};
{href = Url.tutorial "managing-dependencies"; title = "Managing Dependencies"};
{href = Url.tutorial "install-a-specific-ocaml-compiler-version"; title = "Install a Specific Compiler Version"};
- {href = Url.tutorial "configuring-your-editor"; title = "Configuring Your Editor"}
+ {href = Url.tutorial "set-up-editor"; title = "Configuring Your Editor"}
]
~see_more:{href = Url.learn_platform; title = "Platform Tools Documentation"}
%>
diff --git a/src/ocamlorg_frontend/pages/package_documentation.eml b/src/ocamlorg_frontend/pages/package_documentation.eml
index 9632889a5b..9c2bd0f5d1 100644
--- a/src/ocamlorg_frontend/pages/package_documentation.eml
+++ b/src/ocamlorg_frontend/pages/package_documentation.eml
@@ -1,49 +1,48 @@
let sidebar
~str_path
-~toc
-~maptoc
+~local_toc
+~global_toc
(package : Package.package)
=
- <% if (toc != []) then ( %>
+ <% if (local_toc != []) then ( %>
- <%s! Toc.render toc %>
+ <%s! Toc.render local_toc %>
<% ); %>
- <%s! Navmap.render ~package ~path:str_path maptoc %>
+ <%s! Navmap.render ~package ~path:str_path global_toc %>
let right_sidebar
-~toc
+~local_toc
=
- <%s! Toc.render toc %>
+ <%s! Toc.render local_toc %>
let render
~(path: Package_breadcrumbs.path)
~page
-~toc
-~maptoc
+~local_toc
+~global_toc
~content
(package : Package.package)
=
let str_path =
match path with
| Overview _ -> []
- | Documentation (docs_path) -> (match docs_path with
- | Package_breadcrumbs.Index -> []
- | Library (s, p) -> s :: List.map (function
- | Package_breadcrumbs.Module { name ; _ } -> name
- | ModuleType { name ; _ } -> name
- | Parameter { name ; _ } -> name
- | Class { name ; _ } -> name
- | ClassType { name ; _ } -> name
- ) p
- | Page s -> [""; s])
+ | Documentation (docs_path) ->
+ List.map (function
+ | Package_breadcrumbs.Module { name ; _ }
+ | ModuleType { name ; _ }
+ | Parameter ({ name ; _ }, _)
+ | Class { name ; _ }
+ | ClassType { name ; _ }
+ | Page { name; _ } -> name
+ ) docs_path |> List.tl
in
Package_layout.render
~title:(Printf.sprintf "%s %s · OCaml Package" package.name (Package.render_version package))
@@ -54,10 +53,10 @@ Package_layout.render
~documentation_status:Success
~canonical:(Url.Package.documentation package.name ~version:(Package.specific_version package) ~page:(Url.Package.documentation ?version:(Some (Package.specific_version package)) package.name))
~styles:["css/main.css"; "css/doc.css"]
-~left_sidebar_html:(sidebar ~str_path ~toc ~maptoc package)
-~right_sidebar_html:(right_sidebar ~toc) @@
+~left_sidebar_html:(sidebar ~str_path ~local_toc ~global_toc package)
+~right_sidebar_html:(right_sidebar ~local_toc) @@
- <%s! Package_breadcrumbs.render ~path package %>
+ <%s! Package_breadcrumbs.render ~path %>
<%s! content %>
diff --git a/src/ocamlorg_frontend/pages/package_overview.eml b/src/ocamlorg_frontend/pages/package_overview.eml
index cdf8af70e2..67a86e2c02 100644
--- a/src/ocamlorg_frontend/pages/package_overview.eml
+++ b/src/ocamlorg_frontend/pages/package_overview.eml
@@ -68,10 +68,10 @@ in
<%s! side_box_link ~icon_html:(Icons.package_homepage "h-4 w-4 mr-2 inline-block") ~href:homepage ~title:(Utils.host_of_uri homepage) %>
<% ); %>
<% (match sidebar_data.readme_filename with Some readme_filename -> %>
- <%s! side_box_link ~icon_html:(Icons.changelog "h-4 w-4 mr-2 inline-block") ~href:(Url.Package.file package.name ?version ~filepath:(readme_filename ^ ".html")) ~title:"Readme" %>
+ <%s! side_box_link ~icon_html:(Icons.document "h-4 w-4 mr-2 inline-block") ~href:(Url.Package.file package.name ?version ~filepath:(readme_filename ^ ".html")) ~title:"Readme" %>
<% | _ -> ()); %>
<% (match sidebar_data.changes_filename with Some changes_filename -> %>
- <%s! side_box_link ~icon_html:(Icons.changelog "h-4 w-4 mr-2 inline-block") ~href:(Url.Package.file package.name ?version ~filepath:(changes_filename ^ ".html")) ~title:"Changelog" %>
+ <%s! side_box_link ~icon_html:(Icons.document "h-4 w-4 mr-2 inline-block") ~href:(Url.Package.file package.name ?version ~filepath:(changes_filename ^ ".html")) ~title:"Changelog" %>
<% | _ -> ()); %>
<% (match sidebar_data.license_filename with Some license_filename -> %>
<%s! side_box_link ~icon_html:(Icons.license "h-4 w-4 mr-2 inline-block") ~href:(Url.Package.file package.name ?version ~filepath:(license_filename ^ ".html")) ~title:( package.license ^ " License") %>
diff --git a/src/ocamlorg_frontend/pages/tools_platform.eml b/src/ocamlorg_frontend/pages/tools_platform.eml
index 010f4a6a1a..2a63a4ef9b 100644
--- a/src/ocamlorg_frontend/pages/tools_platform.eml
+++ b/src/ocamlorg_frontend/pages/tools_platform.eml
@@ -67,7 +67,7 @@ Tools_layout.three_column_layout
href="<%s Url.installing_ocaml %>">"Installing OCaml".
The Tools of the OCaml Platform
- The individual OCaml Platform Tools go through a lifecycle from incubation to deprecation.
+ The individual OCaml Platform Tools go through a lifecycle from incubation to deprecation.
Here is a list of all Platform Tools sorted by their lifecycle state.
diff --git a/src/ocamlorg_frontend/pages/tutorial.eml b/src/ocamlorg_frontend/pages/tutorial.eml
index 958d43b408..7584407573 100644
--- a/src/ocamlorg_frontend/pages/tutorial.eml
+++ b/src/ocamlorg_frontend/pages/tutorial.eml
@@ -18,7 +18,7 @@ let left_sidebar ~tutorials ~current_tutorial ~section =
<%s! Learn_layout.render_sidebar ~tutorials ~current_tutorial ~section %>
-let of_tutorial_section (s: Data.Tutorial.Section.t) =
+let of_tutorial_section (s: Data.Tutorial.section) =
match s with
| GetStarted -> Learn_layout.GetStarted
| Language -> Learn_layout.Language
diff --git a/src/ocamlorg_package/lib/config.ml b/src/ocamlorg_package/lib/config.ml
index 0896651660..dc7425a82d 100644
--- a/src/ocamlorg_package/lib/config.ml
+++ b/src/ocamlorg_package/lib/config.ml
@@ -5,7 +5,7 @@ let opam_polling =
let documentation_url =
Sys.getenv_opt "OCAMLORG_DOC_URL"
- |> Option.value ~default:"https://docs-data.ocaml.org/live/"
+ |> Option.value ~default:"https://sage.ci.dev/current/"
let package_caches_ttl =
env_with_default "OCAMLORG_PACKAGE_CACHES_TTL" "3600" |> float_of_string
diff --git a/src/ocamlorg_package/lib/documentation_status.ml b/src/ocamlorg_package/lib/documentation_status.ml
index b9b4303bf4..754c67ab73 100644
--- a/src/ocamlorg_package/lib/documentation_status.ml
+++ b/src/ocamlorg_package/lib/documentation_status.ml
@@ -1,34 +1,27 @@
-type otherdocs = {
- readme : string option;
- license : string option;
- changes : string option;
-}
+type redirection = { old_path : string; new_path : string } [@@deriving yojson]
-type t = { failed : bool; otherdocs : otherdocs }
+type t = {
+ name : string;
+ version : string;
+ failed : bool;
+ files : string list;
+ redirections : redirection list;
+}
+[@@deriving yojson]
-let first_opt = function x :: _ -> Some x | [] -> None
+let has_file (v : t) (options : string list) : string option =
+ let children = v.files in
+ try
+ List.find_map
+ (fun x ->
+ let fname = Fpath.(v x |> rem_ext |> filename) in
+ if List.mem fname options then Some fname else None)
+ children
+ with Not_found -> None
-let strip_prefix (p : string option) =
- let v : string list option = Option.map (String.split_on_char '/') p in
- match v with
- | None -> None
- | Some (_ :: _ :: _ :: _ :: _ :: xs) -> Some (String.concat "/" xs)
- | _ -> None
+let license (v : t) = has_file v [ "LICENSE"; "LICENCE" ]
+let readme (v : t) = has_file v [ "README"; "Readme"; "readme" ]
-let of_yojson (v : Yojson.Safe.t) : t =
- let status = Voodoo_serialize.Status.of_yojson v in
- {
- failed = status.failed;
- otherdocs =
- {
- readme =
- status.otherdocs.readme |> first_opt |> Option.map Fpath.to_string
- |> strip_prefix;
- license =
- status.otherdocs.license |> first_opt |> Option.map Fpath.to_string
- |> strip_prefix;
- changes =
- status.otherdocs.changes |> first_opt |> Option.map Fpath.to_string
- |> strip_prefix;
- };
- }
+let changelog (v : t) =
+ has_file v
+ [ "CHANGELOG"; "Changelog"; "changelog"; "CHANGES"; "Changes"; "changes" ]
diff --git a/src/ocamlorg_package/lib/documentation_status.mli b/src/ocamlorg_package/lib/documentation_status.mli
index 90d1b0ad61..351558f577 100644
--- a/src/ocamlorg_package/lib/documentation_status.mli
+++ b/src/ocamlorg_package/lib/documentation_status.mli
@@ -2,13 +2,17 @@
ocaml-docs-ci. If the documentation generation is successful, this file
should exist and contain info about the other pages present in the package. *)
-type otherdocs = {
- readme : string option;
- license : string option;
- changes : string option;
-}
+type redirection = { old_path : string; new_path : string } [@@deriving yojson]
-type t = { failed : bool; otherdocs : otherdocs }
+type t = {
+ name : string;
+ version : string;
+ failed : bool;
+ files : string list;
+ redirections : redirection list;
+}
+[@@deriving yojson]
-val of_yojson : Yojson.Safe.t -> t
-(** Parse the status from the JSON format of `status.json` *)
+val readme : t -> string option
+val license : t -> string option
+val changelog : t -> string option
diff --git a/src/ocamlorg_package/lib/dune b/src/ocamlorg_package/lib/dune
index 141953c717..33299d609c 100644
--- a/src/ocamlorg_package/lib/dune
+++ b/src/ocamlorg_package/lib/dune
@@ -1,5 +1,6 @@
(library
(name ocamlorg_package)
+ (preprocess (pps ppx_deriving_yojson))
(libraries
opam-format
bos
diff --git a/src/ocamlorg_package/lib/ocamlorg_package.ml b/src/ocamlorg_package/lib/ocamlorg_package.ml
index 086e485114..fc564d8c81 100644
--- a/src/ocamlorg_package/lib/ocamlorg_package.ml
+++ b/src/ocamlorg_package/lib/ocamlorg_package.ml
@@ -10,6 +10,7 @@ end
module Version = OpamPackage.Version
module Info = Info
module Statistics = Packages_stats
+module Sidebar = Sidebar
type t = { name : Name.t; version : Version.t; info : Info.t }
@@ -256,7 +257,11 @@ module Documentation = struct
Parameter (int_of_string i)
else raise (Invalid_argument ("kind not recognized: " ^ s))
- type breadcrumb = { name : string; href : string; kind : breadcrumb_kind }
+ type breadcrumb = {
+ name : string;
+ href : string option;
+ kind : breadcrumb_kind;
+ }
type t = {
uses_katex : bool;
@@ -280,13 +285,17 @@ module Documentation = struct
[
("name", `String name); ("href", `String href); ("kind", `String kind);
] ->
- { name; href; kind = breadcrumb_kind_from_string kind }
+ { name; href = Some href; kind = breadcrumb_kind_from_string kind }
+ | `Assoc [ ("name", `String name); ("href", `Null); ("kind", `String kind) ]
+ ->
+ { name; href = None; kind = breadcrumb_kind_from_string kind }
| _ -> raise (Invalid_argument "malformed breadcrumb field")
let doc_from_string s =
match Yojson.Safe.from_string s with
| `Assoc
[
+ ("header", `String header);
("type", `String _page_type);
("uses_katex", `Bool uses_katex);
("breadcrumbs", `List json_breadcrumbs);
@@ -295,16 +304,12 @@ module Documentation = struct
("preamble", `String preamble);
("content", `String content);
] ->
- let breadcrumbs =
- match List.map breadcrumb_from_json json_breadcrumbs with
- | _ :: _ :: _ :: _ :: breadcrumbs -> breadcrumbs
- | _ -> failwith "Not enough breadcrumbs"
- in
+ let breadcrumbs = List.map breadcrumb_from_json json_breadcrumbs in
{
uses_katex;
breadcrumbs;
toc = List.map toc_of_json json_toc;
- content = preamble ^ content;
+ content = header ^ preamble ^ content;
}
| _ -> raise (Invalid_argument "malformed .html.json file")
end
@@ -345,20 +350,56 @@ let http_get url =
Logs.err (fun m -> m "%s" (Printexc.to_string e));
Lwt.return (Error (`Msg (Printexc.to_string e))))
-let module_map ~kind t =
+module Sidebar_cache : sig
+ val add :
+ Name.t ->
+ Version.t ->
+ [ `Package | `Universe of string ] ->
+ Sidebar.t ->
+ unit
+
+ val get :
+ Name.t ->
+ Version.t ->
+ [ `Package | `Universe of string ] ->
+ Sidebar.t option
+end = struct
+ let cache = Hashtbl.create 100
+
+ let add name version kind sidebar =
+ let name = Name.to_string name in
+ let version = Version.to_string version in
+ Hashtbl.add cache (name, version, kind) sidebar
+
+ let get name version kind =
+ let name = Name.to_string name in
+ let version = Version.to_string version in
+ Hashtbl.find_opt cache (name, version, kind)
+end
+
+let sidebar ~kind t =
let package_url =
package_url ~kind (Name.to_string t.name) (Version.to_string t.version)
in
let open Lwt.Syntax in
- let url = package_url ^ "package.json" in
- let+ content = http_get url in
- match content with
- | Ok v ->
- let json = Yojson.Safe.from_string v in
- Package_info.of_yojson json
- | Error _ ->
- Logs.info (fun m -> m "Failed to fetch module map at %s" url);
- { Package_info.libraries = String.Map.empty }
+ match Sidebar_cache.get t.name t.version kind with
+ | Some sidebar -> Lwt.return sidebar
+ | None -> (
+ let url = package_url ^ "doc/sidebar.json" in
+ let+ content = http_get url in
+ match content with
+ | Ok v -> (
+ let json = Yojson.Safe.from_string v in
+ match Sidebar.of_yojson json with
+ | Ok x ->
+ Sidebar_cache.add t.name t.version kind x;
+ x
+ | Error msg ->
+ Logs.info (fun m -> m "Failed to parse sidebar at %s: %s" url msg);
+ [])
+ | Error _ ->
+ Logs.info (fun m -> m "Failed to fetch sidebar at %s" url);
+ [])
let odoc_page ~url =
let open Lwt.Syntax in
@@ -395,7 +436,7 @@ let search_index ~kind t =
let package_url =
package_url ~kind (Name.to_string t.name) (Version.to_string t.version)
in
- let url = package_url ^ "index.js" in
+ let url = package_url ^ "doc/index.js" in
let open Lwt.Syntax in
let* content = http_get url in
@@ -448,9 +489,13 @@ let documentation_status ~kind state t : Documentation_status.t option Lwt.t =
let+ content = http_get (package_url ^ "status.json") in
let status =
match content with
- | Ok s ->
- Some (s |> Yojson.Safe.from_string |> Documentation_status.of_yojson)
- | _ -> None
+ | Ok s -> (
+ match
+ s |> Yojson.Safe.from_string |> Documentation_status.of_yojson
+ with
+ | Ok status -> Some status
+ | Error _TODO -> None)
+ | Error _TODO -> None
in
let status_entry =
{ documentation_status = status; time = Unix.gettimeofday () }
diff --git a/src/ocamlorg_package/lib/ocamlorg_package.mli b/src/ocamlorg_package/lib/ocamlorg_package.mli
index 70a3a34100..4665b766c1 100644
--- a/src/ocamlorg_package/lib/ocamlorg_package.mli
+++ b/src/ocamlorg_package/lib/ocamlorg_package.mli
@@ -81,7 +81,7 @@ module Documentation : sig
| ClassType
| File
- type breadcrumb = { name : string; href : string; kind : breadcrumb_kind }
+ type breadcrumb = { name : string; href : string option; kind : breadcrumb_kind }
type t = {
uses_katex : bool;
@@ -93,6 +93,8 @@ end
module Package_info = Package_info
+module Sidebar = Sidebar
+
type state
type t
@@ -111,15 +113,7 @@ val info : t -> Info.t
val create : name:Name.t -> version:Version.t -> Info.t -> t
(** This is added to enable demo test package to use Package.t with abstraction *)
-module Documentation_status : sig
- type otherdocs = {
- readme : string option;
- license : string option;
- changes : string option;
- }
-
- type t = { failed : bool; otherdocs : otherdocs }
-end
+module Documentation_status = Documentation_status
val documentation_status :
kind:[< `Package | `Universe of string ] ->
@@ -128,9 +122,9 @@ val documentation_status :
Documentation_status.t option Lwt.t
(** Get the build status of the documentation of a package *)
-val module_map :
- kind:[< `Package | `Universe of string ] -> t -> Package_info.t Lwt.t
-(** Get the module map of a package *)
+val sidebar :
+ kind:[ `Package | `Universe of string ] -> t -> Sidebar.t Lwt.t
+(** Get the sidebar of a package *)
val documentation_page :
kind:[< `Package | `Universe of string ] ->
diff --git a/src/ocamlorg_package/lib/sidebar.ml b/src/ocamlorg_package/lib/sidebar.ml
new file mode 100644
index 0000000000..dd133c4797
--- /dev/null
+++ b/src/ocamlorg_package/lib/sidebar.ml
@@ -0,0 +1,12 @@
+(** Odoc types for sidebar's global table of content *)
+
+type 'a node = { node : 'a; children : 'a node list }
+
+and sidebar_node = {
+ url : string option;
+ kind : string option;
+ content : string;
+}
+
+and tree = sidebar_node node
+and t = tree list [@@deriving of_yojson]
diff --git a/src/ocamlorg_web/lib/config.ml b/src/ocamlorg_web/lib/config.ml
index d8ba90020d..afd8cbe0b2 100644
--- a/src/ocamlorg_web/lib/config.ml
+++ b/src/ocamlorg_web/lib/config.ml
@@ -6,4 +6,6 @@ let to_bool s =
let http_port = env_with_default "OCAMLORG_HTTP_PORT" "8080" |> int_of_string
let manual_path =
- env_with_default "OCAMLORG_MANUAL_PATH" "http-compiler-manuals"
+ env_with_default "OCAMLORG_MANUAL_PATH" "html-compiler-manuals"
+
+let v2_path = env_with_default "OCAMLORG_V2_PATH" "data/v2"
diff --git a/src/ocamlorg_web/lib/handler.ml b/src/ocamlorg_web/lib/handler.ml
index 180586268e..f98103579c 100644
--- a/src/ocamlorg_web/lib/handler.ml
+++ b/src/ocamlorg_web/lib/handler.ml
@@ -277,9 +277,13 @@ let learn_documents_search req =
let changelog req =
let current_tag = Dream.query req "t" in
+ let tags_from_change change =
+ match change with Data.Changelog.Release r -> r.tags | Post p -> p.tags
+ in
let tags =
Data.Changelog.all
- |> List.concat_map (fun (change : Data.Changelog.t) -> change.tags)
+ |> List.concat_map (fun (change : Data.Changelog.t) ->
+ tags_from_change change)
|> List.sort_uniq String.compare
in
let changes =
@@ -288,7 +292,7 @@ let changelog req =
| Some tag ->
List.filter
(fun (change : Data.Changelog.t) ->
- List.exists (( = ) tag) change.tags)
+ List.exists (( = ) tag) (tags_from_change change))
Data.Changelog.all
in
@@ -328,6 +332,7 @@ let industrial_users _req =
in
let top_story = List.hd (sort_by_priority_desc Data.Success_story.all) in
let users = Data.Industrial_user.featured |> Ocamlorg.Import.List.take 6 in
+ let number_of_users = List.length Data.Industrial_user.all in
let success_stories =
match sort_by_priority_desc Data.Success_story.all with
| [] -> []
@@ -340,8 +345,8 @@ let industrial_users _req =
let jobs_with_count = (jobs, List.length Data.Job.all) in
Dream.html
- (Ocamlorg_frontend.industrial_users ~users ~success_stories ~top_story
- ~testimonials ~jobs_with_count)
+ (Ocamlorg_frontend.industrial_users ~number_of_users ~users ~success_stories
+ ~top_story ~testimonials ~jobs_with_count)
let industrial_businesses _req =
let businesses = Data.Industrial_user.all in
@@ -886,29 +891,18 @@ module Package_helper = struct
let package_sidebar_data ~kind t package =
let open Lwt.Syntax in
- let* package_documentation_status =
- Ocamlorg_package.documentation_status ~kind t package
- in
+ let* doc_status = Ocamlorg_package.documentation_status ~kind t package in
let readme_filename =
- Option.fold ~none:None
- ~some:(fun (s : Ocamlorg_package.Documentation_status.t) ->
- s.otherdocs.readme)
- package_documentation_status
+ Option.bind doc_status Ocamlorg_package.Documentation_status.readme
in
let changes_filename =
- Option.fold ~none:None
- ~some:(fun (s : Ocamlorg_package.Documentation_status.t) ->
- s.otherdocs.changes)
- package_documentation_status
+ Option.bind doc_status Ocamlorg_package.Documentation_status.changelog
in
let license_filename =
- Option.fold ~none:None
- ~some:(fun (s : Ocamlorg_package.Documentation_status.t) ->
- s.otherdocs.license)
- package_documentation_status
+ Option.bind doc_status Ocamlorg_package.Documentation_status.license
in
let documentation_status =
- match package_documentation_status with
+ match doc_status with
| Some { failed = false; _ } -> Ocamlorg_frontend.Package.Success
| Some { failed = true; _ } -> Failure
| None -> Unknown
@@ -1235,11 +1229,6 @@ let package_documentation t kind req =
in
let path = (Dream.path [@ocaml.warning "-3"]) req |> String.concat "/" in
let hash = match kind with `Package -> None | `Universe u -> Some u in
- let root =
- Url.Package.documentation ?hash ~page:""
- ?version:(Ocamlorg_frontend.Package.url_version frontend_package)
- (Ocamlorg_package.Name.to_string name)
- in
let* docs = Ocamlorg_package.documentation_page ~kind package path in
match docs with
| None ->
@@ -1247,7 +1236,7 @@ let package_documentation t kind req =
Dream.html ~code:404
(Ocamlorg_frontend.package_documentation_not_found ~page:path
~search_index_digest:None
- ~path:(Ocamlorg_frontend.Package_breadcrumbs.Documentation Index)
+ ~path:(Ocamlorg_frontend.Package_breadcrumbs.Documentation [])
frontend_package)
in
if version_from_url = "latest" then
@@ -1264,95 +1253,63 @@ let package_documentation t kind req =
(Ocamlorg_package.Name.to_string name))
else response_404_page
| Some doc ->
- let module Package_info = Ocamlorg_package.Package_info in
- let rec toc_of_module ~root
- (module' : Ocamlorg_package.Package_info.Module.t) :
- Ocamlorg_frontend.Navmap.toc =
- let title = Package_info.Module.name module' in
- let kind = Package_info.Module.kind module' in
- let href = Some (root ^ Package_info.Module.path module') in
- let children =
- module' |> Package_info.Module.submodules |> String.Map.bindings
- |> List.map (fun (_, module') -> toc_of_module ~root module')
- in
- let kind =
- match (kind : Package_info.Kind.t) with
- | Page -> Ocamlorg_frontend.Navmap.Page
- | Module -> Module
- | LeafPage -> Leaf_page
- | ModuleType -> Module_type
- | Parameter _ -> Parameter
- | Class -> Class
- | ClassType -> Class_type
- | File -> File
- in
- Ocamlorg_frontend.Navmap.{ title; href; kind; children }
+ let map_url = Option.map (fun url -> "/" ^ url) in
+ let rec navmap_of_sidebar (sidebar : Ocamlorg_package.Sidebar.tree) =
+ Ocamlorg_frontend.Navmap.
+ {
+ title = sidebar.node.content;
+ kind =
+ (match sidebar.node.kind with
+ | Some "module" -> Module
+ | (Some ("page" | "leaf-page") | None)
+ when String.starts_with ~prefix:"Library " sidebar.node.content
+ ->
+ Library
+ | Some ("page" | "leaf-page") -> Page
+ | Some "module-type" -> Module_type
+ | Some "parameter" -> Parameter
+ | Some "class" -> Class
+ | Some "class-type" -> Class_type
+ | Some "file" -> File
+ | None -> Page
+ | _ -> File);
+ href = map_url sidebar.node.url;
+ children = List.map navmap_of_sidebar sidebar.children;
+ }
in
- let toc_of_map ~root (map : Ocamlorg_package.Package_info.t) :
- Ocamlorg_frontend.Navmap.t =
- let libraries = map.libraries in
- String.Map.bindings libraries
- |> List.map (fun (_, (library : Package_info.library)) ->
- let title = library.name in
- let href = None in
- let children =
- String.Map.bindings library.modules
- |> List.map (fun (_, module') -> toc_of_module ~root module')
- in
- Ocamlorg_frontend.Navmap.
- { title; href; kind = Library; children })
- in
- let* module_map = Ocamlorg_package.module_map ~kind package in
+ let* sidebar = Ocamlorg_package.sidebar ~kind package in
let* search_index_digest =
Package_helper.search_index_digest ~kind t package
in
- let toc = Package_helper.frontend_toc doc.toc in
- let (maptoc : Ocamlorg_frontend.Navmap.toc list) =
- toc_of_map ~root module_map
+ let local_toc = Package_helper.frontend_toc doc.toc in
+ let (global_toc : Ocamlorg_frontend.Navmap.toc list) =
+ List.map navmap_of_sidebar sidebar
in
let (breadcrumb_path : Ocamlorg_frontend.Package_breadcrumbs.path) =
let breadcrumbs = doc.breadcrumbs in
- if breadcrumbs != [] then
- let first_path_item = List.hd breadcrumbs in
- let doc_breadcrumb_to_library_path_item
- (p : Ocamlorg_package.Documentation.breadcrumb) =
- match p.kind with
- | Module ->
- Ocamlorg_frontend.Package_breadcrumbs.Module
- { name = p.name; href = p.href }
- | ModuleType -> ModuleType { name = p.name; href = p.href }
- | Parameter i ->
- Parameter { name = p.name; href = p.href; number = i }
- | Class -> Class { name = p.name; href = p.href }
- | ClassType -> ClassType { name = p.name; href = p.href }
- | Page | LeafPage | File ->
- failwith "library paths do not contain Page, LeafPage or File"
+ let doc_breadcrumb_to_library_path_item
+ (p : Ocamlorg_package.Documentation.breadcrumb) =
+ let b =
+ {
+ Ocamlorg_frontend.Package_breadcrumbs.name = p.name;
+ href = p.href;
+ }
in
+ match p.kind with
+ | Module -> Ocamlorg_frontend.Package_breadcrumbs.Module b
+ | ModuleType -> ModuleType b
+ | Parameter i -> Parameter (b, i)
+ | Class -> Class b
+ | ClassType -> ClassType b
+ | Page | LeafPage | File -> Page b
+ in
- match first_path_item.kind with
- | Page | LeafPage | File ->
- Ocamlorg_frontend.Package_breadcrumbs.Documentation
- (Page first_path_item.name)
- | Module | ModuleType | Parameter _ | Class | ClassType ->
- let library =
- List.find_opt
- (fun (toc : Ocamlorg_frontend.Navmap.toc) ->
- List.exists
- (fun (t : Ocamlorg_frontend.Navmap.toc) ->
- t.title = first_path_item.name)
- toc.children)
- maptoc
- in
-
- Ocamlorg_frontend.Package_breadcrumbs.Documentation
- (Library
- ( (match library with Some l -> l.title | None -> "unknown"),
- List.map doc_breadcrumb_to_library_path_item breadcrumbs ))
- else Ocamlorg_frontend.Package_breadcrumbs.Documentation Index
+ Ocamlorg_frontend.Package_breadcrumbs.Documentation
+ (List.map doc_breadcrumb_to_library_path_item breadcrumbs)
in
Dream.html
(Ocamlorg_frontend.package_documentation ~page:(Some path)
- ~search_index_digest ~path:breadcrumb_path ~toc ~maptoc
+ ~search_index_digest ~path:breadcrumb_path ~local_toc ~global_toc
~content:doc.content frontend_package)
let package_file t kind req =
@@ -1414,3 +1371,10 @@ let sitemap _request =
Dream.flush stream)
let logos _req = Dream.html (Ocamlorg_frontend.logos ())
+
+let v2_asset req =
+ let>? path =
+ try Some (Config.v2_path ^ Dream.target req) with _ -> None
+ in
+ let>? () = if Sys.file_exists path then Some () else None in
+ Filename.(Dream.from_filesystem (dirname path) (basename path) req)
diff --git a/src/ocamlorg_web/lib/redirection.ml b/src/ocamlorg_web/lib/redirection.ml
index e24294c4de..3e454be805 100644
--- a/src/ocamlorg_web/lib/redirection.ml
+++ b/src/ocamlorg_web/lib/redirection.ml
@@ -1,43 +1,28 @@
open Ocamlorg
-let fwd_v2 target = (target, Url.v2 ^ target)
-
-(* For assets previously hosted on V2, we redirect the requests to
- v2.ocaml.org. *)
let v2_assets =
- [
- "/meetings/ocaml/2013/proposals/core-bench.pdf";
- "/meetings/ocaml/2013/proposals/ctypes.pdf";
- "/meetings/ocaml/2013/proposals/formats-as-gadts.pdf";
- "/meetings/ocaml/2013/proposals/frenetic.pdf";
- "/meetings/ocaml/2013/proposals/goji.pdf";
- "/meetings/ocaml/2013/proposals/gpgpu.pdf";
- "/meetings/ocaml/2013/proposals/injectivity.pdf";
- "/meetings/ocaml/2013/proposals/merlin.pdf";
- "/meetings/ocaml/2013/proposals/ocamlot.pdf";
- "/meetings/ocaml/2013/proposals/optimizations.pdf";
- "/meetings/ocaml/2013/proposals/platform.pdf";
- "/meetings/ocaml/2013/proposals/profiling-memory.pdf";
- "/meetings/ocaml/2013/proposals/runtime-types.pdf";
- "/meetings/ocaml/2013/proposals/weather-related-data.pdf";
- "/meetings/ocaml/2013/proposals/wxocaml.pdf";
- "/meetings/ocaml/2013/slides/bourgoin.pdf";
- "/meetings/ocaml/2013/slides/bozman.pdf";
- "/meetings/ocaml/2013/slides/canou.pdf";
- "/meetings/ocaml/2013/slides/carty.pdf";
- "/meetings/ocaml/2013/slides/chambart.pdf";
- "/meetings/ocaml/2013/slides/garrigue.pdf";
- "/meetings/ocaml/2013/slides/guha.pdf";
- "/meetings/ocaml/2013/slides/henry.pdf";
- "/meetings/ocaml/2013/slides/james.pdf";
- "/meetings/ocaml/2013/slides/lefessant.pdf";
- "/meetings/ocaml/2013/slides/leroy.pdf";
- "/meetings/ocaml/2013/slides/madhavapeddy.pdf";
- "/meetings/ocaml/2013/slides/padioleau.pdf";
- "/meetings/ocaml/2013/slides/sheets.pdf";
- "/meetings/ocaml/2013/slides/vaugon.pdf";
- "/meetings/ocaml/2013/slides/white.pdf";
- ]
+ let confs =
+ [ "/conference/"; "/meetings/"; "/meeting/"; "/workshops/"; "/workshop/" ]
+ in
+ let redirects confs target source =
+ let f s = (s ^ source, target) in
+ List.map f confs
+ in
+ let f path =
+ let open String in
+ if starts_with ~prefix:"/conferences/" path && ends_with ~suffix:".pdf" path
+ then redirects confs path (sub path 13 (length path - 13))
+ else []
+ in
+ let g conf =
+ let year = String.sub conf.Data.Conference.date 0 4 in
+ [ ""; "/index.html" ]
+ |> List.concat_map (fun s ->
+ redirects ("/conferences/" :: confs)
+ ("/conferences/" ^ conf.slug)
+ ("ocaml/" ^ year ^ s))
+ in
+ List.concat_map f Data.V2.assets @ List.concat_map g Data.Conference.all
let from_v2 =
[
@@ -228,91 +213,30 @@ let from_v2 =
("/learn/tutorials/streams.html", Url.tutorial "sequences");
("/learn/tutorials/up_and_running.html", Url.tutorial "up-and-running");
(Url.tutorial "first-hour", Url.tutorial "tour-of-ocaml");
- ("/meetings/index.fr.html", Url.community);
- ("/meetings/index.html", Url.community);
- ("/meetings", Url.community);
- ( "/meetings/ocaml/2008/index.html",
- Url.conference "ocaml-users-and-developers-conference-2008" );
- ( "/meetings/ocaml/2008",
- Url.conference "ocaml-users-and-developers-conference-2008" );
- ( "/meetings/ocaml/2008/index.html",
- Url.conference "ocaml-users-and-developers-conference-2008" );
- ( "/meetings/ocaml/2008",
- Url.conference "ocaml-users-and-developers-conference-2008" );
- ( "/meetings/ocaml/2009/index.html",
- Url.conference "ocaml-users-and-developers-conference-2009" );
- ( "/meetings/ocaml/2009",
- Url.conference "ocaml-users-and-developers-conference-2009" );
- ( "/meetings/ocaml/2010/index.html",
- Url.conference "ocaml-users-and-developers-conference-2010" );
- ( "/meetings/ocaml/2010",
- Url.conference "ocaml-users-and-developers-conference-2010" );
- ( "/meetings/ocaml/2011/index.html",
- Url.conference "ocaml-users-and-developers-conference-2011" );
- ( "/meetings/ocaml/2011",
- Url.conference "ocaml-users-and-developers-conference-2011" );
- ( "/meetings/ocaml/2012/index.html",
- Url.conference "ocaml-users-and-developers-conference-2012" );
+ ("/meetings/index.fr.html", Url.conferences);
+ ("/meetings/index.html", Url.conferences);
+ ("/meetings", Url.conferences);
( "/meetings/ocaml/2013/call.html",
Url.conference "ocaml-users-and-developers-conference-2013" );
- ( "/meetings/ocaml/2013",
- Url.conference "ocaml-users-and-developers-conference-2013" );
- ( "/meetings/ocaml/2013/index.html",
- Url.conference "ocaml-users-and-developers-conference-2013" );
( "/meetings/ocaml/2013/program.html",
Url.conference "ocaml-users-and-developers-conference-2013" );
- ( "/meetings/ocaml/2013",
- Url.conference "ocaml-users-and-developers-conference-2013" );
( "/meetings/ocaml/2013/talks/index.html",
Url.conference "ocaml-users-and-developers-conference-2013" );
( "/meetings/ocaml/2014/cfp.html",
Url.conference "ocaml-users-and-developers-conference-2014" );
- ( "/meetings/ocaml/2014",
- Url.conference "ocaml-users-and-developers-conference-2014" );
- ( "/meetings/ocaml/2014/index.html",
- Url.conference "ocaml-users-and-developers-conference-2014" );
( "/meetings/ocaml/2014/ocaml2014_10.html",
Url.conference "ocaml-users-and-developers-conference-2014" );
( "/meetings/ocaml/2014/program.html",
Url.conference "ocaml-users-and-developers-conference-2014" );
( "/meetings/ocaml/2015/cfp.html",
Url.conference "ocaml-users-and-developers-conference-2015" );
- ( "/meetings/ocaml/2015",
- Url.conference "ocaml-users-and-developers-conference-2015" );
- ( "/meetings/ocaml/2015/index.html",
- Url.conference "ocaml-users-and-developers-conference-2015" );
- ( "/meetings/ocaml/2015",
- Url.conference "ocaml-users-and-developers-conference-2015" );
( "/meetings/ocaml/2015/program.html",
Url.conference "ocaml-users-and-developers-conference-2015" );
- ( "/meetings/ocaml/2015",
- Url.conference "ocaml-users-and-developers-conference-2015" );
( "/meetings/ocaml/2015/program.txt",
Url.conference "ocaml-users-and-developers-conference-2015" );
- ( "/meetings/ocaml/2015",
- Url.conference "ocaml-users-and-developers-conference-2015" );
- ( "/meetings/ocaml/2016/index.html",
- Url.conference "ocaml-users-and-developers-conference-2016" );
- ( "/meetings/ocaml/2016",
- Url.conference "ocaml-users-and-developers-conference-2016" );
- ( "/meetings/ocaml/2017/index.html",
- Url.conference "ocaml-users-and-developers-conference-2017" );
- ( "/meetings/ocaml/2017",
- Url.conference "ocaml-users-and-developers-conference-2017" );
- ( "/meetings/ocaml/2018/index.html",
- Url.conference "ocaml-users-and-developers-conference-2018" );
- ( "/meetings/ocaml/2018",
- Url.conference "ocaml-users-and-developers-conference-2018" );
- ( "/meetings/ocaml/2019/index.html",
- Url.conference "ocaml-users-and-developers-conference-2019" );
- ( "/meetings/ocaml/2019",
- Url.conference "ocaml-users-and-developers-conference-2019" );
- ( "/meetings/ocaml/2020/index.html",
- Url.conference "ocaml-users-and-developers-conference-2020" );
- ( "/meetings/ocaml/2020",
- Url.conference "ocaml-users-and-developers-conference-2020" );
- ("/meetings/ocaml/index.html", Url.community);
- ("/meetings/ocaml", Url.community);
+ ("/meetings/ocaml/index.html", Url.conferences);
+ ("/meetings/ocaml", Url.conferences);
+ ("/workshops", Url.conferences);
("/ocamllabs/index.html", Url.index);
("/ocamllabs", Url.index);
("/platform/index.html", Url.learn_platform);
@@ -327,6 +251,7 @@ let from_v2 =
("/docs/platform-principles", Url.tool_page "platform-principles");
("/docs/platform-users", Url.tool_page "platform-users");
("/docs/platform-roadmap", Url.tool_page "platform-roadmap");
+ ("/docs/configuring-your-editor", Url.tutorial "set-up-editor");
]
let make ?(permanent = false) t =
@@ -340,30 +265,6 @@ let make ?(permanent = false) t =
Some (Dream.get origin (fun req -> Dream.redirect ~status req new_)))
t)
-let ocaml_workshops =
- List.map
- (fun (slug : string) ->
- make ~permanent:true [ ("/workshop/" ^ slug, Url.conference slug) ])
- [
- "ocaml-users-and-developers-conference-2008";
- "ocaml-users-and-developers-conference-2009";
- "ocaml-users-and-developers-conference-2010";
- "ocaml-users-and-developers-conference-2011";
- "ocaml-users-and-developers-conference-2012";
- "ocaml-users-and-developers-conference-2013";
- "ocaml-users-and-developers-conference-2014";
- "ocaml-users-and-developers-conference-2015";
- "ocaml-users-and-developers-conference-2016";
- "ocaml-users-and-developers-conference-2017";
- "ocaml-users-and-developers-conference-2018";
- "ocaml-users-and-developers-conference-2019";
- "ocaml-users-and-developers-conference-2020";
- "ocaml-users-and-developers-conference-2020";
- "ocaml-users-and-developers-conference-2021";
- "ocaml-users-and-developers-conference-2022";
- "ocaml-users-and-developers-conference-2023";
- ]
-
let package req =
let package = Dream.param req "name" in
Dream.redirect req (Url.Package.overview package)
@@ -374,22 +275,19 @@ let package_docs req =
let t =
Dream.scope "" []
- ([
- make ~permanent:true [ ("feed.xml", "planet.xml") ];
- make from_v2;
- make (List.map fwd_v2 v2_assets);
- make [ ("/blog", "/ocaml-planet") ];
- make ~permanent:true [ ("/opportunities", "/jobs") ];
- make ~permanent:true
- [ ("/carbon-footprint", "/policies/carbon-footprint") ];
- make ~permanent:true [ ("/privacy-policy", "/policies/privacy-policy") ];
- make ~permanent:true
- [ ("/code-of-conduct", "/policies/code-of-conduct") ];
- make ~permanent:true [ ("/opportunities", "/jobs") ];
- (* make ~permanent:false [ (Url.conferences, Url.community ^
- "#conferences") ]; *)
- Dream.get "/p/:name" package;
- Dream.get "/u/:hash/p/:name" package;
- Dream.get "/p/:name/doc" package_docs;
- ]
- @ ocaml_workshops)
+ [
+ make ~permanent:true [ ("feed.xml", "planet.xml") ];
+ make ~permanent:true from_v2;
+ make ~permanent:true v2_assets;
+ make ~permanent:true [ ("/blog", "/ocaml-planet") ];
+ make ~permanent:true [ ("/opportunities", "/jobs") ];
+ make ~permanent:true
+ [ ("/carbon-footprint", "/policies/carbon-footprint") ];
+ make ~permanent:true [ ("/privacy-policy", "/policies/privacy-policy") ];
+ make ~permanent:true [ ("/code-of-conduct", "/policies/code-of-conduct") ];
+ (* make ~permanent:false [ (Url.conferences, Url.community ^
+ "#conferences") ]; *)
+ Dream.get "/p/:name" package;
+ Dream.get "/u/:hash/p/:name" package;
+ Dream.get "/p/:name/doc" package_docs;
+ ]
diff --git a/src/ocamlorg_web/lib/router.ml b/src/ocamlorg_web/lib/router.ml
index d144c42ebb..80dd71a8ec 100644
--- a/src/ocamlorg_web/lib/router.ml
+++ b/src/ocamlorg_web/lib/router.ml
@@ -127,16 +127,32 @@ let graphql_route t =
Dream.get "/graphiql" (Dream.graphiql "/graphql");
]
+let ( let+ ) x f = Lwt.map f x
+
+let middleware_text_utf8 handler request =
+ let+ response = handler request in
+ let ( let& ) opt some = Option.fold ~none:response ~some opt in
+ let headers = Dream.all_headers response in
+ let& content_type = List.assoc_opt "Content-Type" headers in
+ let& _ =
+ if String.starts_with ~prefix:"text/plain" content_type then Some ()
+ else None
+ in
+ Dream.drop_header response "Content-Type";
+ Dream.add_header response "Content-Type" (content_type ^ "; charset=utf-8");
+ response
+
let router t =
Dream.router
[
Redirection.t;
+ Dream.get "/conferences/ocaml/**" Handler.v2_asset;
page_routes t;
package_route t;
graphql_route t;
sitemap_routes;
Dream.scope ""
- [ Dream_encoding.compress ]
+ [ Dream_encoding.compress; middleware_text_utf8 ]
[ Dream.get "/manual/**" (Dream.static Config.manual_path) ];
Dream.scope ""
[ Dream_encoding.compress ]
diff --git a/src/ocamlorg_web/lib/sitemap.ml b/src/ocamlorg_web/lib/sitemap.ml
index e91489ad45..1a81ab32ff 100644
--- a/src/ocamlorg_web/lib/sitemap.ml
+++ b/src/ocamlorg_web/lib/sitemap.ml
@@ -43,7 +43,12 @@ let urlables =
List.to_seq
[
Urlable (urls, to_url);
- Urlable (Changelog.all, fun r -> to_url @@ Url.changelog_entry r.slug);
+ Urlable
+ ( Changelog.all,
+ fun r ->
+ match r with
+ | Release r -> to_url @@ Url.changelog_entry r.slug
+ | Post r -> to_url @@ Url.changelog_entry r.slug );
Urlable (Governance.teams, fun r -> to_url @@ Url.governance_team r.id);
Urlable (Is_ocaml_yet.all, fun r -> to_url @@ Url.is_ocaml_yet r.id);
Urlable (News.all, fun r -> to_url @@ Url.news_post r.slug);
diff --git a/test/load-test/k6/.gitignore b/test/load-test/k6/.gitignore
new file mode 100644
index 0000000000..df9d16b899
--- /dev/null
+++ b/test/load-test/k6/.gitignore
@@ -0,0 +1 @@
+_results/
diff --git a/test/load-test/k6/README.md b/test/load-test/k6/README.md
new file mode 100644
index 0000000000..e9bcbfe493
--- /dev/null
+++ b/test/load-test/k6/README.md
@@ -0,0 +1,45 @@
+# k6 Load Tests
+
+This directory contains a [k6](https://grafana.com/docs/k6/latest/) script for
+load testing ocaml.org endpoints.
+
+- The k6 test script is defined in [./script.js](./script.js).
+- The runner script [./run.sh](./run.sh) will run `./script.js` with the k6
+ docker image.
+
+
+## Running load tests
+
+### Prerequisites
+
+- docker
+- k6parser
+- xdg-open (MacOS users may need to adjust the runner script)
+
+### Usage
+
+``` sh
+./run.sh script.js
+```
+
+will run a load test against all defined endpoints using 10 virtual users, and
+running for 30 seconds.
+
+You can specify different values for the concurrent users and duration using the
+`--vus` and `--duration` flags, respectively. E.g., to test with 100 virtual
+users for one minute:
+
+``` sh
+./run.sh --vus 100 --duration 1m script.js
+```
+
+## Reviewing the results
+
+k6 will print out the a summary of results when the test is finished. See
+ for
+documentation on how to interpret the results.
+
+- Detailed results are written to a gzipped JOSN file
+ `_results/{day}T{time}-results.gz`.
+- A browser will be opened with a visualization plotting general responsiveness,
+ loaded with the file `_results/{day}T{time}-results.report`.
diff --git a/test/load-test/k6/run.sh b/test/load-test/k6/run.sh
new file mode 100755
index 0000000000..12ef3cd4bc
--- /dev/null
+++ b/test/load-test/k6/run.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env sh
+
+# Ignore posix compliance errors in shellcheck
+#shellcheck disable=SC3000-SC3061
+
+set -eu
+set -o pipefail
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+
+RESULTS_DIR=_results
+USER_ID=$(id -u) # ensure we can write to the mounted dir
+TIME=$(date +%FT%T)
+RESULTS="${RESULTS_DIR}/${TIME}-results.gz"
+GRAPH="${RESULTS_DIR}/${TIME}-report.html"
+
+
+mkdir -p "$RESULTS_DIR"
+
+docker run --rm \
+ -u "$USER_ID" \
+ -v "$SCRIPT_DIR":/home/k6 grafana/k6 \
+ --out json="$RESULTS" \
+ "$@"
+
+k6parser "$RESULTS" --output "$GRAPH"
+
+xdg-open "$GRAPH"
diff --git a/test/load-test/k6/script.js b/test/load-test/k6/script.js
new file mode 100644
index 0000000000..3bc97945f5
--- /dev/null
+++ b/test/load-test/k6/script.js
@@ -0,0 +1,88 @@
+import http from "k6/http";
+import { sleep } from "k6";
+
+export const options = {
+ // A number specifying the number of VUs to run concurrently.
+ vus: 10,
+ // A string specifying the total duration of the test run.
+ duration: "30s",
+
+ // The following section contains configuration options for execution of this
+ // test script in Grafana Cloud.
+ //
+ // See https://grafana.com/docs/grafana-cloud/k6/get-started/run-cloud-tests-from-the-cli/
+ // to learn about authoring and running k6 test scripts in Grafana k6 Cloud.
+ //
+ // cloud: {
+ // // The ID of the project to which the test is assigned in the k6 Cloud UI.
+ // // By default tests are executed in default project.
+ // projectID: "",
+ // // The name of the test in the k6 Cloud UI.
+ // // Test runs with the same name will be grouped.
+ // name: "script.js"
+ // },
+
+ // Uncomment this section to enable the use of Browser API in your tests.
+ //
+ // See https://grafana.com/docs/k6/latest/using-k6-browser/running-browser-tests/ to learn more
+ // about using Browser API in your test scripts.
+ //
+ // scenarios: {
+ // // The scenario name appears in the result summary, tags, and so on.
+ // // You can give the scenario any name, as long as each name in the script is unique.
+ // ui: {
+ // // Executor is a mandatory parameter for browser-based tests.
+ // // Shared iterations in this case tells k6 to reuse VUs to execute iterations.
+ // //
+ // // See https://grafana.com/docs/k6/latest/using-k6/scenarios/executors/ for other executor types.
+ // executor: 'shared-iterations',
+ // options: {
+ // browser: {
+ // // This is a mandatory parameter that instructs k6 to launch and
+ // // connect to a chromium-based browser, and use it to run UI-based
+ // // tests.
+ // type: 'chromium',
+ // },
+ // },
+ // },
+ // }
+};
+
+// The function that defines VU logic.
+//
+// See https://grafana.com/docs/k6/latest/examples/get-started-with-k6/ to learn more
+// about authoring k6 scripts.
+
+const base_url = "http://localhost:8080";
+
+function endpoint(u) {
+ return base_url + u;
+}
+
+export default function () {
+ // Landing page
+ http.get(endpoint("/"));
+
+ // Core doc page
+ http.get(endpoint("/p/core/latest/doc/index.html"));
+
+ // Top level pages
+ http.get(endpoint("/install"));
+ http.get(endpoint("/docs/tour-of-ocaml"));
+ http.get(endpoint("/docs"));
+ http.get(endpoint("/platform"));
+ http.get(endpoint("/packages"));
+ http.get(endpoint("/community"));
+ http.get(endpoint("/changelog"));
+ http.get(endpoint("/play"));
+ http.get(endpoint("/industrial-users"));
+ http.get(endpoint("/academic-users"));
+
+ // some package searches
+ // Grouping the urls, see https://grafana.com/docs/k6/latest/using-k6/http-requests/#url-grouping
+ const package_search_tag = { tags: { name: "PacakageSearch" } };
+ ["http", "server", "cli", "core", "eio", "graph"].forEach((q) => {
+ http.get(endpoint(`/packages/autocomplete?q=${q}`), package_search_tag);
+ http.get(endpoint(`/packages/search?q=${q}`), package_search_tag);
+ });
+}
diff --git a/test/load-test/locust/.gitignore b/test/load-test/locust/.gitignore
new file mode 100644
index 0000000000..c18dd8d83c
--- /dev/null
+++ b/test/load-test/locust/.gitignore
@@ -0,0 +1 @@
+__pycache__/
diff --git a/test/load-test/locust/README.md b/test/load-test/locust/README.md
new file mode 100644
index 0000000000..6dc71d2580
--- /dev/null
+++ b/test/load-test/locust/README.md
@@ -0,0 +1,40 @@
+# Locust Load Tests
+
+This directory contains a [locust](https://locust.io/) script for load testing
+ocaml.org endpoints.
+
+## Running simple load tests
+
+1. Start the test framework running with
+
+ ``` sh
+ ./main.sh
+ ```
+
+2. Navigate to http://0.0.0.0:8089
+
+3. Configure
+ - the max number of users to simulate
+ - the number of new users to add to the simulation every second
+ - the host (e.g., `http://localhost:8080`)
+
+## Running load tests with multiple cores
+
+``` sh
+./main.sh n
+```
+
+where `n` is the number of processes to run concurrently.
+
+## Reviewing the load tests
+
+- Click "Stop" when you are finished running your test.
+- Review the various tabs, or click "Download data" for options to download the
+ test results.
+- You can also review prometheus metrics about the staging and prod servers at
+ https://status.ocaml.ci.dev/d/be358r0z9ai9sf/ocaml-org
+
+## Adding new routines
+
+Tests are defined as "tasks" (sequences of site traversal) in
+[./locustfile.py](./locustfile.py).
diff --git a/test/load-test/locust/locustfile.py b/test/load-test/locust/locustfile.py
new file mode 100644
index 0000000000..7829c68508
--- /dev/null
+++ b/test/load-test/locust/locustfile.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python3
+#
+import random
+from locust import FastHttpUser, task, between
+
+# obtained via https://random-word-api.herokuapp.com/word?number=200
+words = ["sacraria","seedcake","philtered","leadiest","cloverleafs","snaffle","lyrisms","ankhs","bedtimes","carrier","restabling","playlets","occupation","overreport","printers","scintigraphic","spa","corsages","enwound","gossipmonger","hydragog","waterlily","avulsions","sonneteerings","spilt","hemocytes","tamandus","rais","minaret","coliseums","ultramicrotome","attribute","phosphite","scincoids","scooting","frightfulnesses","carbamides","sculpture","irresponsive","overtasks","expertise","weet","consociations","tulles","hared","pigginesses","oversimple","theologs","adverb","inamoratas","teeny","rapacities","assonant","metestrus","cyanohydrin","smiting","polychete","merest","tautological","phyllopod","petahertz","plainspokenness","pavior","penitently","omikrons","cigarlike","foetal","diebacks","downlight","kinship","warmish","titleholders","suppositions","resuscitations","tiffing","outsung","homed","alternated","cranks","piaster","allotters","nonirradiated","protohistories","finned","decouple","shahs","foeman","perfidious","soarers","thoroughpins","gastrulating","thrivers","convention","roughened","uncircumcised","clabbering","leadscrew","panfuls","nilgais","evolver","overvoting","furrower","ichthyosaurs","internalizes","borschts","regrouping","lordlier","roguish","microseismicity","besmiling","mattoids","cholerically","fibrosarcomas","farinha","curricles","triradiate","beringed","electrolysis","kashmirs","dirdums","ignorami","otalgic","lusciously","blotty","pizzaz","educe","pendant","disposable","autolyzes","outjutting","interfused","operagoers","fustian","theretofore","dean","unsullied","goitrogenic","ultrasafe","potboil","geochemistries","outdesigned","ephedras","woodlore","illuminatingly","guardrooms","sheldrakes","leachable","theistically","reconception","beachboys","recriminates","almuds","changeabilities","flareups","machinate","verbalizations","dendrologist","unkept","copulatives","restyles","parceled","caecilians","mortgager","thunderstones","labarums","wiliness","hydroplane","already","unlatch","swineherds","alternate","whodunit","hoodiest","sainted","detract","inspiring","fantastically","macaws","adsorbs","thickets","blogs","greenfields","ariettes","camphor","hornpipe","uninventive","boatyard","boomiest","lollingly","congresspersons","painter","radiocarbons","impiously","unfeigned","matchlocks","screwballs","stickies","muddlers","resentful","meats"]
+
+class OcamlOrgUsere(FastHttpUser):
+ wait_time = between(1, 5) # range of seconds a user waits between clicks
+
+ @task
+ def landing(self):
+ self.client.get("/")
+ self.client.get("/install")
+ self.client.get("/p/core/latest/doc/index.html")
+ self.client.get("/docs/tour-of-ocaml")
+
+ @task
+ def top_level_pages(self):
+ self.client.get("/docs")
+ self.client.get("/platform")
+ self.client.get("/packages")
+ self.client.get("/community")
+ self.client.get("/changelog")
+ self.client.get("/play")
+ self.client.get("/industrial-users")
+ self.client.get("/academic-users")
+
+ @task
+ def package_searches(self):
+ query = random.choice(words)
+ self.client.get(f"/packages/autocomplete?q={query}")
+ self.client.get(f"/packages/search?q={query}")
diff --git a/test/load-test/locust/main.sh b/test/load-test/locust/main.sh
new file mode 100755
index 0000000000..4ea0b3dd4c
--- /dev/null
+++ b/test/load-test/locust/main.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env sh
+
+# Ignore posix compliance errors in shellcheck
+#shellcheck disable=SC3000-SC3061
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+
+# see https://docs.locust.io/en/stable/running-distributed.html#distributed-load-generation
+n_procs="$1"
+
+docker run -p 8089:8089 -v "$SCRIPT_DIR":/mnt/locust locustio/locust \
+ --locustfile /mnt/locust/locustfile.py \
+ ${n_procs:+"--processes=${n_procs}"} # Build the --processes flag if n_procs is not nil
diff --git a/tool/ood-gen/lib/academic_institution.ml b/tool/ood-gen/lib/academic_institution.ml
index 7bdfdfee9b..5eeae369cd 100644
--- a/tool/ood-gen/lib/academic_institution.ml
+++ b/tool/ood-gen/lib/academic_institution.ml
@@ -1,5 +1,3 @@
-open Data_intf.Academic_institution
-
type course_metadata = {
name : string;
acronym : string option;
@@ -15,6 +13,18 @@ type course_metadata = {
}
[@@deriving of_yaml]
+module Ptime = struct
+ include Ptime
+
+ let pp fmt t =
+ Format.pp_print_string fmt "(Ptime.of_rfc3339 \"";
+ Ptime.pp_rfc3339 () fmt t;
+ Format.pp_print_string fmt
+ "\" |> function Ok (t, _, _) -> t | Error _ -> failwith \"RFC 3339\")"
+end
+
+type course = [%import: Data_intf.Academic_institution.course] [@@deriving show]
+
let course_metadata_to_course ~modify_last_check (c : course_metadata) : course
=
Data_intf.Academic_institution.
@@ -47,6 +57,9 @@ let course_of_yaml yaml =
Ok (course_metadata_to_course ~modify_last_check metadata)
with Failure msg -> Error (`Msg msg)
+type location = [%import: Data_intf.Academic_institution.location]
+[@@deriving of_yaml, show]
+
type metadata = {
name : string;
description : string;
@@ -59,9 +72,15 @@ type metadata = {
image : string option;
alternate_logo : string option;
}
-[@@deriving of_yaml, stable_record ~version:t ~add:[ body_md; body_html; slug ]]
+[@@deriving
+ of_yaml,
+ stable_record ~version:Data_intf.Academic_institution.t
+ ~add:[ body_md; body_html; slug ]]
+
+type t = [%import: Data_intf.Academic_institution.t] [@@deriving show]
-let of_metadata m = metadata_to_t m ~slug:(Utils.slugify m.name)
+let of_metadata m =
+ metadata_to_Data_intf_Academic_institution_t m ~slug:(Utils.slugify m.name)
let decode (fpath, (head, body_md)) =
let metadata =
diff --git a/tool/ood-gen/lib/academic_testimonial.ml b/tool/ood-gen/lib/academic_testimonial.ml
index 467b5c8ceb..f401968721 100644
--- a/tool/ood-gen/lib/academic_testimonial.ml
+++ b/tool/ood-gen/lib/academic_testimonial.ml
@@ -1,4 +1,4 @@
-open Data_intf.Academic_testimonial
+type t = [%import: Data_intf.Academic_testimonial.t] [@@deriving of_yaml, show]
let all () = Utils.yaml_sequence_file of_yaml "academic-testimonials.yml"
@@ -7,5 +7,4 @@ let template () =
include Data_intf.Academic_testimonial
let all = %a
|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
- (all ())
+ (Fmt.Dump.list pp) (all ())
diff --git a/tool/ood-gen/lib/blog.ml b/tool/ood-gen/lib/blog.ml
index 69a57c765c..17f0703d9a 100644
--- a/tool/ood-gen/lib/blog.ml
+++ b/tool/ood-gen/lib/blog.ml
@@ -1,9 +1,10 @@
open Ocamlorg.Import
-open Data_intf.Blog
(* external RSS feeds that we aggregate - they will all be scraped by the
scrape.yml workflow *)
+type source = [%import: Data_intf.Blog.source] [@@deriving show]
+
module Source = struct
type t = {
id : string;
@@ -16,7 +17,7 @@ module Source = struct
type sources = t list [@@deriving yaml]
- let all () : source list =
+ let all () : Data_intf.Blog.source list =
let file = "planet-sources.yml" in
let result =
let ( let* ) = Result.bind in
@@ -28,7 +29,7 @@ module Source = struct
(sources
|> List.map (fun { id; name; url; only_ocaml; disabled } ->
{
- id;
+ Data_intf.Blog.id;
name;
url;
description = "";
@@ -41,6 +42,8 @@ module Source = struct
Exn.Decode_error (file ^ ": " ^ msg))
end
+type post = [%import: Data_intf.Blog.post] [@@deriving show]
+
module Post = struct
type source_on_external_post = { name : string; url : string }
[@@deriving yaml]
@@ -58,7 +61,7 @@ module Post = struct
let all_sources = Source.all ()
- let of_metadata ~source ~body_html m : Post.t =
+ let of_metadata ~source ~body_html m : Data_intf.Blog.post =
{
title = m.title;
source =
@@ -106,7 +109,9 @@ module Post = struct
match Str.split (Str.regexp_string "/") fpath with
| _ :: second :: _ -> (
match
- List.find_opt (fun (s : source) -> s.id = second) all_sources
+ List.find_opt
+ (fun (s : Data_intf.Blog.source) -> s.id = second)
+ all_sources
with
| Some source -> Ok source
| None -> Error (`Msg ("No source found for: " ^ fpath)))
@@ -120,9 +125,10 @@ module Post = struct
|> Result.map_error (Utils.where fpath)
|> Result.map (of_metadata ~source ~body_html)
- let all () : Post.t list =
+ let all () : Data_intf.Blog.post list =
Utils.map_md_files decode "planet/*/*.md"
- |> List.sort (fun (a : Post.t) (b : Post.t) -> String.compare b.date a.date)
+ |> List.sort (fun (a : Data_intf.Blog.post) (b : Data_intf.Blog.post) ->
+ String.compare b.date a.date)
end
module Scraper = struct
@@ -136,7 +142,7 @@ module Scraper = struct
let scrape_post ~source (post : River.post) =
let title = River.title post in
let slug = Utils.slugify title in
- let source_path = "data/planet/" ^ source.id in
+ let source_path = "data/planet/" ^ source.Data_intf.Blog.id in
let output_file = source_path ^ "/" ^ slug ^ ".md" in
if not (Sys.file_exists output_file) then
let url = River.link post in
@@ -144,8 +150,8 @@ module Scraper = struct
match (url, date) with
| None, _ ->
print_endline
- (Printf.sprintf "skipping %s/%s: item does not have a url" source.id
- slug)
+ (Printf.sprintf "skipping %s/%s: item does not have a url"
+ source.Data_intf.Blog.id slug)
| _, None ->
print_endline
(Printf.sprintf "skipping %s/%s: item does not have a date"
@@ -193,6 +199,7 @@ module Scraper = struct
let scrape () =
let sources = Source.all () in
sources
- |> List.filter (fun ({ disabled; _ } : source) -> not disabled)
+ |> List.filter (fun ({ disabled; _ } : Data_intf.Blog.source) ->
+ not disabled)
|> List.iter scrape_source
end
diff --git a/tool/ood-gen/lib/book.ml b/tool/ood-gen/lib/book.ml
index 15184d4e1f..955ac589e5 100644
--- a/tool/ood-gen/lib/book.ml
+++ b/tool/ood-gen/lib/book.ml
@@ -1,4 +1,17 @@
-open Data_intf.Book
+type difficulty = [%import: Data_intf.Book.difficulty] [@@deriving show]
+
+let difficulty_of_string = function
+ | "beginner" -> Ok Beginner
+ | "intermediate" -> Ok Intermediate
+ | "advanced" -> Ok Advanced
+ | s -> Error (`Msg ("Unknown difficulty type: " ^ s))
+
+let difficulty_of_yaml = function
+ | `String s -> difficulty_of_string s
+ | _ -> Error (`Msg "Expected a string for difficulty type")
+
+type link = [%import: Data_intf.Book.link] [@@deriving of_yaml, show]
+type t = [%import: Data_intf.Book.t] [@@deriving show]
type metadata = {
title : string;
@@ -38,6 +51,5 @@ let template () =
Format.asprintf {|
include Data_intf.Book
let all = %a
-|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
+|} (Fmt.Dump.list pp)
(all ())
diff --git a/tool/ood-gen/lib/changelog.ml b/tool/ood-gen/lib/changelog.ml
index 8ae1409d8f..d354ef8420 100644
--- a/tool/ood-gen/lib/changelog.ml
+++ b/tool/ood-gen/lib/changelog.ml
@@ -1,19 +1,45 @@
-open Data_intf.Changelog
-
-type metadata = {
- title : string;
- tags : string list;
- authors : string list option;
- description : string option;
- changelog : string option;
-}
-[@@deriving
- of_yaml,
- stable_record ~version:t ~remove:[ changelog; description ]
- ~modify:[ authors ]
- ~add:[ slug; changelog_html; body_html; body; date ]]
-
-let of_metadata m = metadata_to_t m ~modify_authors:(Option.value ~default:[])
+type release = [%import: Data_intf.Changelog.release] [@@deriving of_yaml, show]
+type post = [%import: Data_intf.Changelog.post] [@@deriving of_yaml, show]
+type t = [%import: Data_intf.Changelog.t] [@@deriving of_yaml, show]
+
+(* The OCaml.org Changelog has two categories:
+
+ - posts - release announcements
+
+ POSTS =====
+
+ These OCaml.org Changelog entries are posted automatically to various OCaml
+ social media accounts.
+
+ The main accounts will automatically receive posts that (a) describe how the
+ user-facing surface of the tools as a whole changes and (b) empower the
+ reader to make better use of the OCaml Platform tools.
+
+ These entries are represented by the RSS feed category "posts".
+
+ Example content: - celebrating major releases or milestones - how to use new
+ features - which new workflows are possible now (sometimes involving releases
+ from multiple tools) - announcing that workarounds for bugs that are now
+ fixed can be retired
+
+ Generally, these will be written together with a release announcement, or
+ after, and reference zero or more earlier relevant release announcements.
+
+ RELEASE ANNOUNCEMENTS =====================
+
+ We also collect and broadcast release announcements for all the GitHub
+ repositories that directly support the OCaml Platform Tools.
+
+ Release announcements will not go out through the main social media accounts,
+ but through dedicated accounts sharing releases. From these, the main OCaml
+ accounts can repost and comment on major releases to give more details or
+ highlight particular milestones.
+
+ These entries are represented by the RSS feed category "releases".
+
+ Generally, software engineers of the OCaml Platform will add these by opening
+ a pull request to mirror a release announcement that likely already happened
+ on discuss.ocaml.org. *)
let re_date_slug =
let open Re in
@@ -42,53 +68,156 @@ let parse_date_from_slug s =
let day = int 3 in
Some (Printf.sprintf "%04d-%02d-%02d" year month day)
-let decode (fname, (head, body)) =
- let slug = Filename.basename (Filename.remove_extension fname) in
- let metadata =
- metadata_of_yaml head |> Result.map_error (Utils.where fname)
- in
- let body_html =
- Cmarkit_html.of_doc ~safe:false
- (Hilite.Md.transform
- (Cmarkit.Doc.of_string ~strict:true (String.trim body)))
- in
-
- Result.map
- (fun metadata ->
- let changelog_html =
- match metadata.changelog with
- | None -> None
- | Some changelog ->
- Some
- (Cmarkit.Doc.of_string ~strict:true (String.trim changelog)
- |> Hilite.Md.transform
- |> Cmarkit_html.of_doc ~safe:false)
- in
- let date =
- match parse_date_from_slug slug with
- | Some x -> x
- | None ->
- failwith
- "date is not present in metadata and could not be parsed from \
- slug"
- in
- of_metadata ~slug ~changelog_html ~body ~body_html ~date metadata)
- metadata
+module Releases = struct
+ type release_metadata = {
+ title : string;
+ tags : string list;
+ authors : string list option;
+ contributors : string list option;
+ description : string option;
+ changelog : string option;
+ }
+ [@@deriving
+ of_yaml,
+ stable_record ~version:release ~remove:[ changelog; description ]
+ ~modify:[ authors; contributors ]
+ ~add:[ slug; changelog_html; body_html; body; date ]]
+
+ let of_release_metadata m =
+ release_metadata_to_release m ~modify_authors:(Option.value ~default:[])
+ ~modify_contributors:(Option.value ~default:[])
+
+ let decode (fname, (head, body)) =
+ let slug = Filename.basename (Filename.remove_extension fname) in
+ let metadata =
+ release_metadata_of_yaml head |> Result.map_error (Utils.where fname)
+ in
+ let body_html =
+ Cmarkit_html.of_doc ~safe:false
+ (Hilite.Md.transform
+ (Cmarkit.Doc.of_string ~strict:true (String.trim body)))
+ in
+
+ Result.map
+ (fun metadata ->
+ let changelog_html =
+ match metadata.changelog with
+ | None -> None
+ | Some changelog ->
+ Some
+ (Cmarkit.Doc.of_string ~strict:true (String.trim changelog)
+ |> Hilite.Md.transform
+ |> Cmarkit_html.of_doc ~safe:false)
+ in
+ let date =
+ match parse_date_from_slug slug with
+ | Some x -> x
+ | None ->
+ failwith
+ "date is not present in metadata and could not be parsed from \
+ slug"
+ in
+ of_release_metadata ~slug ~changelog_html ~body ~body_html ~date
+ metadata)
+ metadata
+
+ let all () =
+ Utils.map_md_files decode "changelog/releases/*/*.md"
+ |> List.sort (fun (a : release) b -> String.compare b.slug a.slug)
+end
+
+module Posts = struct
+ type post_metadata = {
+ title : string;
+ tags : string list;
+ authors : string list option;
+ }
+ [@@deriving
+ of_yaml,
+ stable_record ~version:post ~modify:[ authors ]
+ ~add:[ slug; body_html; body; date ]]
+
+ let of_post_metadata m =
+ post_metadata_to_post m ~modify_authors:(Option.value ~default:[])
+
+ let decode (fname, (head, body)) =
+ let slug = Filename.basename (Filename.remove_extension fname) in
+ let metadata =
+ post_metadata_of_yaml head |> Result.map_error (Utils.where fname)
+ in
+ let body_html =
+ Cmarkit_html.of_doc ~safe:false
+ (Hilite.Md.transform
+ (Cmarkit.Doc.of_string ~strict:true (String.trim body)))
+ in
+
+ Result.map
+ (fun metadata ->
+ let date =
+ match parse_date_from_slug slug with
+ | Some x -> x
+ | None ->
+ failwith
+ "date is not present in metadata and could not be parsed from \
+ slug"
+ in
+ of_post_metadata ~slug ~body ~body_html ~date metadata)
+ metadata
+
+ let all () =
+ Utils.map_md_files decode "changelog/posts/*/*.md"
+ |> List.sort (fun (a : post) b -> String.compare b.slug a.slug)
+end
let all () =
- Utils.map_md_files decode "changelog/*/*.md"
- |> List.sort (fun a b -> String.compare b.slug a.slug)
+ let slug_of_t r = match r with Release r -> r.slug | Post p -> p.slug in
+ let releases = Releases.all () in
+ let posts = Posts.all () in
+ List.map (fun x -> Release x) releases @ List.map (fun x -> Post x) posts
+ |> List.sort (fun (a : t) b -> String.compare (slug_of_t b) (slug_of_t a))
module ChangelogFeed = struct
- let create_entry (log : t) =
- let content = Syndic.Atom.Html (None, log.body_html) in
- let id = Uri.of_string ("https://ocaml.org/changelog/" ^ log.slug) in
- let authors = (Syndic.Atom.author "Ocaml.org", []) in
- let updated = Syndic.Date.of_rfc3339 (log.date ^ "T00:00:00-00:00") in
- Syndic.Atom.entry ~content ~id ~authors ~title:(Syndic.Atom.Text log.title)
- ~updated
- ~links:[ Syndic.Atom.link id ]
- ()
+ let to_author name = Syndic.Atom.{ name; uri = None; email = None }
+
+ let create_entry (entry : t) =
+ match entry with
+ | Release release ->
+ let content = Syndic.Atom.Html (None, release.body_html) in
+ let id =
+ Uri.of_string ("https://ocaml.org/changelog/release/" ^ release.slug)
+ in
+ let authors =
+ (Syndic.Atom.author "OCaml.org", List.map to_author release.authors)
+ in
+ let updated =
+ Syndic.Date.of_rfc3339 (release.date ^ "T00:00:00-00:00")
+ in
+ let categories =
+ [
+ Syndic.Atom.
+ { term = "releases"; scheme = None; label = Some "Releases" };
+ ]
+ in
+ Syndic.Atom.entry ~content ~id ~authors
+ ~title:(Syndic.Atom.Text release.title) ~updated
+ ~links:[ Syndic.Atom.link id ]
+ ~categories ()
+ | Post post ->
+ let content = Syndic.Atom.Html (None, post.body_html) in
+ let id = Uri.of_string ("https://ocaml.org/changelog/" ^ post.slug) in
+ let authors =
+ (Syndic.Atom.author "OCaml.org", List.map to_author post.authors)
+ in
+ let updated = Syndic.Date.of_rfc3339 (post.date ^ "T00:00:00-00:00") in
+ let categories =
+ [
+ Syndic.Atom.{ term = "posts"; scheme = None; label = Some "Posts" };
+ ]
+ in
+ Syndic.Atom.entry ~content ~id ~authors
+ ~title:(Syndic.Atom.Text post.title) ~updated
+ ~links:[ Syndic.Atom.link id ]
+ ~categories ()
let create_feed () =
let open Rss in
@@ -103,5 +232,4 @@ let template () =
include Data_intf.Changelog
let all = %a
|ocaml}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
- (all ())
+ (Fmt.Dump.list pp) (all ())
diff --git a/tool/ood-gen/lib/code_example.ml b/tool/ood-gen/lib/code_example.ml
index d9c596ec96..448a92b27e 100644
--- a/tool/ood-gen/lib/code_example.ml
+++ b/tool/ood-gen/lib/code_example.ml
@@ -1,4 +1,4 @@
-open Data_intf.Code_examples
+type t = [%import: Data_intf.Code_examples.t] [@@deriving show]
let all () =
Utils.read_from_dir "code_examples/*.ml"
@@ -11,5 +11,4 @@ let template () =
include Data_intf.Code_examples
let all = %a
|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
- (all ())
+ (Fmt.Dump.list pp) (all ())
diff --git a/tool/ood-gen/lib/conference.ml b/tool/ood-gen/lib/conference.ml
index d62730e5a7..fa073bd18b 100644
--- a/tool/ood-gen/lib/conference.ml
+++ b/tool/ood-gen/lib/conference.ml
@@ -1,10 +1,31 @@
-open Data_intf.Conference
+type role = [%import: Data_intf.Conference.role] [@@deriving show]
+
+let role_of_string = function
+ | "chair" -> Ok `Chair
+ | "co-chair" -> Ok `Co_chair
+ | s -> Error (`Msg ("Unknown role type: " ^ s))
+
+let role_of_yaml = function
+ | `String s -> role_of_string s
+ | _ -> Error (`Msg "Expected a string for role type")
+
+type important_date = [%import: Data_intf.Conference.important_date]
+[@@deriving of_yaml, show]
+
+type committee_member = [%import: Data_intf.Conference.committee_member]
+[@@deriving of_yaml, show]
+
+type presentation = [%import: Data_intf.Conference.presentation]
+[@@deriving of_yaml, show]
+
+type t = [%import: Data_intf.Conference.t] [@@deriving of_yaml, show]
type presentation_metadata = {
title : string;
authors : string list;
link : string option;
- video : string option;
+ watch_ocamlorg_video : string option;
+ youtube_video : string option;
slides : string option;
poster : bool option;
additional_links : string list option;
@@ -55,5 +76,4 @@ let template () =
include Data_intf.Conference
let all = %a
|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
- (all ())
+ (Fmt.Dump.list pp) (all ())
diff --git a/tool/ood-gen/lib/cookbook.ml b/tool/ood-gen/lib/cookbook.ml
index 136c1e1220..8ae1e565f7 100644
--- a/tool/ood-gen/lib/cookbook.ml
+++ b/tool/ood-gen/lib/cookbook.ml
@@ -1,4 +1,12 @@
-open Data_intf.Cookbook
+type category = [%import: Data_intf.Cookbook.category] [@@deriving show]
+type task = [%import: Data_intf.Cookbook.task] [@@deriving show]
+
+type code_block_with_explanation =
+ [%import: Data_intf.Cookbook.code_block_with_explanation]
+[@@deriving show]
+
+type package = [%import: Data_intf.Cookbook.package] [@@deriving of_yaml, show]
+type t = [%import: Data_intf.Cookbook.t] [@@deriving show]
type task_metadata = {
title : string;
@@ -128,9 +136,5 @@ let top_categories = %a
let tasks = %a
let all = %a
|ocaml}
- (Fmt.brackets (Fmt.list pp_category ~sep:Fmt.semi))
- top_categories
- (Fmt.brackets (Fmt.list pp_task ~sep:Fmt.semi))
- tasks
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
- (all ())
+ (Fmt.Dump.list pp_category)
+ top_categories (Fmt.Dump.list pp_task) tasks (Fmt.Dump.list pp) (all ())
diff --git a/tool/ood-gen/lib/dune b/tool/ood-gen/lib/dune
index c605afbf2c..4703eda561 100644
--- a/tool/ood-gen/lib/dune
+++ b/tool/ood-gen/lib/dune
@@ -22,4 +22,4 @@
re
xmlm)
(preprocess
- (pps ppx_deriving_yaml ppx_stable ppx_deriving.show)))
+ (staged_pps ppx_import ppx_deriving_yaml ppx_stable ppx_deriving.show)))
diff --git a/tool/ood-gen/lib/event.ml b/tool/ood-gen/lib/event.ml
index ff18de0829..a70c2f0904 100644
--- a/tool/ood-gen/lib/event.ml
+++ b/tool/ood-gen/lib/event.ml
@@ -1,4 +1,26 @@
-open Data_intf.Event
+type event_type = [%import: Data_intf.Event.event_type] [@@deriving show]
+
+let event_type_of_string = function
+ | "meetup" -> Ok Meetup
+ | "conference" -> Ok Conference
+ | "seminar" -> Ok Seminar
+ | "hackathon" -> Ok Hackathon
+ | "retreat" -> Ok Retreat
+ | s -> Error (`Msg ("Unknown event type: " ^ s))
+
+let event_type_of_yaml = function
+ | `String s -> event_type_of_string s
+ | _ -> Error (`Msg "Expected a string for difficulty type")
+
+type location = [%import: Data_intf.Event.location] [@@deriving of_yaml, show]
+
+type recurring_event = [%import: Data_intf.Event.recurring_event]
+[@@deriving of_yaml, show]
+
+type utc_datetime = [%import: Data_intf.Event.utc_datetime]
+[@@deriving of_yaml, show]
+
+type t = [%import: Data_intf.Event.t] [@@deriving show]
let recurring_event_all () : recurring_event list =
Utils.yaml_sequence_file recurring_event_of_yaml "events/recurring.yml"
@@ -128,7 +150,5 @@ include Data_intf.Event
let recurring_event_all = %a
let all = %a
|}
- (Fmt.brackets (Fmt.list pp_recurring_event ~sep:Fmt.semi))
- (recurring_event_all ())
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
- (all ())
+ (Fmt.Dump.list pp_recurring_event)
+ (recurring_event_all ()) (Fmt.Dump.list pp) (all ())
diff --git a/tool/ood-gen/lib/exercise.ml b/tool/ood-gen/lib/exercise.ml
index 035910d7a5..9bc8c7398e 100644
--- a/tool/ood-gen/lib/exercise.ml
+++ b/tool/ood-gen/lib/exercise.ml
@@ -1,4 +1,16 @@
-open Data_intf.Exercise
+type difficulty = [%import: Data_intf.Exercise.difficulty] [@@deriving show]
+
+let of_string = function
+ | "beginner" -> Ok Beginner
+ | "intermediate" -> Ok Intermediate
+ | "advanced" -> Ok Advanced
+ | s -> Error (`Msg ("Unknown difficulty type: " ^ s))
+
+let difficulty_of_yaml = function
+ | `String s -> of_string s
+ | _ -> Error (`Msg "Expected a string for difficulty type")
+
+type t = [%import: Data_intf.Exercise.t] [@@deriving show]
type metadata = {
title : string;
@@ -62,5 +74,4 @@ let template () =
include Data_intf.Exercise
let all = %a
|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
- (all ())
+ (Fmt.Dump.list pp) (all ())
diff --git a/tool/ood-gen/lib/governance.ml b/tool/ood-gen/lib/governance.ml
index bfe9043f93..024d7a4b1e 100644
--- a/tool/ood-gen/lib/governance.ml
+++ b/tool/ood-gen/lib/governance.ml
@@ -1,5 +1,53 @@
open Ocamlorg.Import
-open Data_intf.Governance
+
+type member = [%import: Data_intf.Governance.member] [@@deriving of_yaml, show]
+
+type contact_kind = [%import: Data_intf.Governance.contact_kind]
+[@@deriving show]
+
+let contact_kind_of_yaml = function
+ | `String "github" -> Ok GitHub
+ | `String "email" -> Ok Email
+ | `String "discord" -> Ok Discord
+ | `String "chat" -> Ok Chat
+ | x -> (
+ match Yaml.to_string x with
+ | Ok str ->
+ Error
+ (`Msg
+ ("\"" ^ str
+ ^ "\" is not a valid contact_kind! valid options are: github, \
+ email, discord, chat"))
+ | Error _ -> Error (`Msg "Invalid Yaml value"))
+
+let contact_kind_to_yaml = function
+ | GitHub -> `String "github"
+ | Email -> `String "email"
+ | Discord -> `String "discord"
+ | Chat -> `String "chat"
+
+type contact = [%import: Data_intf.Governance.contact]
+[@@deriving of_yaml, show]
+
+type dev_meeting = [%import: Data_intf.Governance.dev_meeting]
+[@@deriving of_yaml, show]
+
+type team_metadata = {
+ id : string;
+ name : string;
+ description : string;
+ contacts : contact list;
+ dev_meeting : dev_meeting option; [@default None] [@key "dev-meeting"]
+ members : member list; [@default []]
+ subteams : team_metadata list; [@default []]
+}
+[@@deriving of_yaml, stable_record ~version:Data_intf.Governance.team]
+
+let team_of_yaml yml =
+ yml |> team_metadata_of_yaml
+ |> Result.map team_metadata_to_Data_intf_Governance_team
+
+type team = [%import: Data_intf.Governance.team] [@@deriving show]
type metadata = {
teams : team list;
@@ -27,7 +75,4 @@ let teams = %a
let working_groups = %a
|}
- (Fmt.brackets (Fmt.list pp_team ~sep:Fmt.semi))
- t.teams
- (Fmt.brackets (Fmt.list pp_team ~sep:Fmt.semi))
- t.working_groups
+ (Fmt.Dump.list pp_team) t.teams (Fmt.Dump.list pp_team) t.working_groups
diff --git a/tool/ood-gen/lib/industrial_user.ml b/tool/ood-gen/lib/industrial_user.ml
index c915c4e06f..cc1bf82ad2 100644
--- a/tool/ood-gen/lib/industrial_user.ml
+++ b/tool/ood-gen/lib/industrial_user.ml
@@ -1,4 +1,4 @@
-open Data_intf.Industrial_user
+type t = [%import: Data_intf.Industrial_user.t] [@@deriving show]
type metadata = {
name : string;
@@ -29,5 +29,4 @@ let template () =
include Data_intf.Industrial_user
let all = %a
|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
- (all ())
+ (Fmt.Dump.list pp) (all ())
diff --git a/tool/ood-gen/lib/is_ocaml_yet.ml b/tool/ood-gen/lib/is_ocaml_yet.ml
index 5e16d47fa6..7dde90b32e 100644
--- a/tool/ood-gen/lib/is_ocaml_yet.ml
+++ b/tool/ood-gen/lib/is_ocaml_yet.ml
@@ -1,4 +1,11 @@
-open Data_intf.Is_ocaml_yet
+type external_package = [%import: Data_intf.Is_ocaml_yet.external_package]
+[@@deriving of_yaml, show]
+
+type package = [%import: Data_intf.Is_ocaml_yet.package]
+[@@deriving of_yaml, show]
+
+type category = [%import: Data_intf.Is_ocaml_yet.category] [@@deriving show]
+type t = [%import: Data_intf.Is_ocaml_yet.t] [@@deriving show]
type category_meta = {
name : string;
@@ -50,5 +57,4 @@ let template () =
include Data_intf.Is_ocaml_yet
let all = %a
|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
- (all ())
+ (Fmt.Dump.list pp) (all ())
diff --git a/tool/ood-gen/lib/job.ml b/tool/ood-gen/lib/job.ml
index 4884aba285..8f354e3a0d 100644
--- a/tool/ood-gen/lib/job.ml
+++ b/tool/ood-gen/lib/job.ml
@@ -1,4 +1,4 @@
-open Data_intf.Job
+type t = [%import: Data_intf.Job.t] [@@deriving of_yaml, show]
let all () =
let job_date j = Option.value ~default:"1970-01-01" j.publication_date in
@@ -37,6 +37,5 @@ let template () =
Format.asprintf {|
include Data_intf.Job
let all = %a
-|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
+|} (Fmt.Dump.list pp)
(all ())
diff --git a/tool/ood-gen/lib/news.ml b/tool/ood-gen/lib/news.ml
index 068de981a3..568994c5f2 100644
--- a/tool/ood-gen/lib/news.ml
+++ b/tool/ood-gen/lib/news.ml
@@ -1,4 +1,4 @@
-open Data_intf.News
+type t = [%import: Data_intf.News.t] [@@deriving show]
type metadata = {
title : string;
@@ -31,8 +31,7 @@ let template () =
Format.asprintf {|
include Data_intf.News
let all = %a
-|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
+|} (Fmt.Dump.list pp)
(all ())
module RssFeed = struct
diff --git a/tool/ood-gen/lib/opam_user.ml b/tool/ood-gen/lib/opam_user.ml
index febb61516e..6496744319 100644
--- a/tool/ood-gen/lib/opam_user.ml
+++ b/tool/ood-gen/lib/opam_user.ml
@@ -1,4 +1,4 @@
-open Data_intf.Opam_user
+type t = [%import: Data_intf.Opam_user.t] [@@deriving of_yaml, show]
let all () = Utils.yaml_sequence_file of_yaml "opam-users.yml"
@@ -7,5 +7,4 @@ let template () =
include Data_intf.Opam_user
let all = %a
|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
- (all ())
+ (Fmt.Dump.list pp) (all ())
diff --git a/tool/ood-gen/lib/outreachy.ml b/tool/ood-gen/lib/outreachy.ml
index 7973e3820b..4ae6a78f67 100644
--- a/tool/ood-gen/lib/outreachy.ml
+++ b/tool/ood-gen/lib/outreachy.ml
@@ -1,4 +1,5 @@
-open Data_intf.Outreachy
+type project = [%import: Data_intf.Outreachy.project] [@@deriving of_yaml, show]
+type t = [%import: Data_intf.Outreachy.t] [@@deriving of_yaml, show]
let modify_project (p : project) =
{
@@ -18,5 +19,4 @@ let template () =
include Data_intf.Outreachy
let all = %a
|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
- (all ())
+ (Fmt.Dump.list pp) (all ())
diff --git a/tool/ood-gen/lib/page.ml b/tool/ood-gen/lib/page.ml
index 91cb821b42..90ba9af8e9 100644
--- a/tool/ood-gen/lib/page.ml
+++ b/tool/ood-gen/lib/page.ml
@@ -1,4 +1,4 @@
-open Data_intf.Page
+type t = [%import: Data_intf.Page.t] [@@deriving show]
type metadata = {
title : string;
@@ -26,6 +26,5 @@ let template () =
Format.asprintf {|
include Data_intf.Page
let all = %a
-|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
+|} (Fmt.Dump.list pp)
(all ())
diff --git a/tool/ood-gen/lib/paper.ml b/tool/ood-gen/lib/paper.ml
index 1c83017069..112e8ae4f8 100644
--- a/tool/ood-gen/lib/paper.ml
+++ b/tool/ood-gen/lib/paper.ml
@@ -1,4 +1,5 @@
-open Data_intf.Paper
+type link = [%import: Data_intf.Paper.link] [@@deriving of_yaml, show]
+type t = [%import: Data_intf.Paper.t] [@@deriving show]
type metadata = {
title : string;
@@ -24,6 +25,5 @@ let template () =
Format.asprintf {|
include Data_intf.Paper
let all = %a
-|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
+|} (Fmt.Dump.list pp)
(all ())
diff --git a/tool/ood-gen/lib/planet.ml b/tool/ood-gen/lib/planet.ml
index 859535701a..25f3060b6a 100644
--- a/tool/ood-gen/lib/planet.ml
+++ b/tool/ood-gen/lib/planet.ml
@@ -1,9 +1,15 @@
open Ocamlorg.Import
-open Data_intf.Planet
+
+type entry = BlogPost of Blog.post | Video of Vid.t
+[@@deriving show { with_path = false }]
+
+let date_of_post = function
+ | BlogPost { date; _ } -> date
+ | Video { published; _ } -> published
let all () =
let external_posts =
- Blog.Post.all () |> List.map (fun (p : Data_intf.Blog.Post.t) -> BlogPost p)
+ Blog.Post.all () |> List.map (fun (p : Blog.post) -> BlogPost p)
in
let videos =
Video.all () |> List.map (fun (v : Data_intf.Video.t) -> Video v)
@@ -17,8 +23,7 @@ let template () =
include Data_intf.Planet
let all = %a
|ocaml}
- (Fmt.brackets (Fmt.list pp_entry ~sep:Fmt.semi))
- (all ())
+ (Fmt.Dump.list pp_entry) (all ())
module GlobalFeed = struct
let feed_authors (source : Data_intf.Blog.source) authors =
@@ -99,7 +104,7 @@ module GlobalFeed = struct
~content:(Syndic.Atom.Html (None, content))
())
- let entry_of_post (post : Data_intf.Blog.Post.t) =
+ let entry_of_post (post : Blog.post) =
let content = Syndic.Atom.Html (None, post.body_html) in
let url = Uri.of_string post.source.url in
let source : Syndic.Atom.source =
diff --git a/tool/ood-gen/lib/release.ml b/tool/ood-gen/lib/release.ml
index 649e17f7f9..f6da54fc82 100644
--- a/tool/ood-gen/lib/release.ml
+++ b/tool/ood-gen/lib/release.ml
@@ -1,4 +1,14 @@
-open Data_intf.Release
+type kind = [%import: Data_intf.Release.kind] [@@deriving show]
+
+let kind_of_string = function
+ | "compiler" -> Ok `Compiler
+ | s -> Error (`Msg ("Unknown release type: " ^ s))
+
+let kind_of_yaml = function
+ | `String s -> kind_of_string s
+ | _ -> Error (`Msg "Expected a string for release type")
+
+type t = [%import: Data_intf.Release.t] [@@deriving show]
type metadata = {
kind : kind;
@@ -76,5 +86,4 @@ let all = %a
let latest = %a
let lts = %a
|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
- all pp latest pp lts
+ (Fmt.Dump.list pp) all pp latest pp lts
diff --git a/tool/ood-gen/lib/resource.ml b/tool/ood-gen/lib/resource.ml
index 6ee65ced48..55d1010258 100644
--- a/tool/ood-gen/lib/resource.ml
+++ b/tool/ood-gen/lib/resource.ml
@@ -1,4 +1,4 @@
-open Data_intf.Resource
+type t = [%import: Data_intf.Resource.t] [@@deriving of_yaml, show]
let all () = Utils.yaml_sequence_file of_yaml "resources.yml"
@@ -10,7 +10,5 @@ include Data_intf.Resource
let all = %a
let featured = %a
|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
- all_resources
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
+ (Fmt.Dump.list pp) all_resources (Fmt.Dump.list pp)
(all_resources |> List.filter (fun (r : t) -> r.featured = true))
diff --git a/tool/ood-gen/lib/rss.ml b/tool/ood-gen/lib/rss.ml
index 80783f545c..10faedb128 100644
--- a/tool/ood-gen/lib/rss.ml
+++ b/tool/ood-gen/lib/rss.ml
@@ -8,7 +8,7 @@ let create_entries ~create_entry ?days u =
Option.fold ~none:Option.some ~some days
in
let entries =
- List.tl u |> List.map create_entry |> List.sort Syndic.Atom.descending
+ u |> List.map create_entry |> List.sort Syndic.Atom.descending
in
match List.filter_map is_fresh entries with
| [] -> [ List.hd entries ]
diff --git a/tool/ood-gen/lib/success_story.ml b/tool/ood-gen/lib/success_story.ml
index 5e4c4be252..ff5179d69a 100644
--- a/tool/ood-gen/lib/success_story.ml
+++ b/tool/ood-gen/lib/success_story.ml
@@ -1,4 +1,4 @@
-open Data_intf.Success_story
+type t = [%import: Data_intf.Success_story.t] [@@deriving show]
type metadata = {
title : string;
@@ -31,5 +31,4 @@ let template () =
include Data_intf.Success_story
let all = %a
|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
- (all ())
+ (Fmt.Dump.list pp) (all ())
diff --git a/tool/ood-gen/lib/testimonial.ml b/tool/ood-gen/lib/testimonial.ml
index a9ded3dfdf..c7c1236c46 100644
--- a/tool/ood-gen/lib/testimonial.ml
+++ b/tool/ood-gen/lib/testimonial.ml
@@ -1,4 +1,4 @@
-open Data_intf.Testimonial
+type t = [%import: Data_intf.Testimonial.t] [@@deriving of_yaml, show]
let all () = Utils.yaml_sequence_file of_yaml "testimonials.yml"
@@ -7,5 +7,4 @@ let template () =
include Data_intf.Testimonial
let all = %a
|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
- (all ())
+ (Fmt.Dump.list pp) (all ())
diff --git a/tool/ood-gen/lib/tool.ml b/tool/ood-gen/lib/tool.ml
index a61398783e..aa95bbb338 100644
--- a/tool/ood-gen/lib/tool.ml
+++ b/tool/ood-gen/lib/tool.ml
@@ -1,4 +1,17 @@
-open Data_intf.Tool
+type lifecycle = [%import: Data_intf.Tool.lifecycle] [@@deriving show]
+
+let lifecycle_of_string = function
+ | "incubate" -> Ok `Incubate
+ | "active" -> Ok `Active
+ | "sustain" -> Ok `Sustain
+ | "deprecate" -> Ok `Deprecate
+ | s -> Error (`Msg ("Unknown lifecycle type: " ^ s))
+
+let lifecycle_of_yaml = function
+ | `String s -> lifecycle_of_string s
+ | _ -> Error (`Msg "Expected a string for lifecycle type")
+
+type t = [%import: Data_intf.Tool.t] [@@deriving show]
type metadata = {
name : string;
@@ -22,6 +35,5 @@ let template () =
Format.asprintf {|
include Data_intf.Tool
let all = %a
-|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
+|} (Fmt.Dump.list pp)
(all ())
diff --git a/tool/ood-gen/lib/tool_page.ml b/tool/ood-gen/lib/tool_page.ml
index d4b0d8c065..2354c7947c 100644
--- a/tool/ood-gen/lib/tool_page.ml
+++ b/tool/ood-gen/lib/tool_page.ml
@@ -1,5 +1,11 @@
open Ocamlorg.Import
-open Data_intf.Tool_page
+
+type toc = [%import: Data_intf.Tool_page.toc] [@@deriving of_yaml, show]
+
+type contribute_link = [%import: Data_intf.Tool_page.contribute_link]
+[@@deriving of_yaml, show]
+
+type t = [%import: Data_intf.Tool_page.t] [@@deriving show]
type metadata = {
id : string;
@@ -45,5 +51,4 @@ let template () =
include Data_intf.Tool_page
let all = %a
|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
- (all ())
+ (Fmt.Dump.list pp) (all ())
diff --git a/tool/ood-gen/lib/tutorial.ml b/tool/ood-gen/lib/tutorial.ml
index 5affa06596..9ae7239273 100644
--- a/tool/ood-gen/lib/tutorial.ml
+++ b/tool/ood-gen/lib/tutorial.ml
@@ -1,5 +1,40 @@
open Ocamlorg.Import
-open Data_intf.Tutorial
+
+type section = [%import: Data_intf.Tutorial.section] [@@deriving show]
+
+let section_of_string = function
+ | "getting-started" -> Ok GetStarted
+ | "language" -> Ok Language
+ | "platform" -> Ok Platform
+ | "guides" -> Ok Guides
+ | s -> Error (`Msg ("Unknown section: " ^ s))
+
+type toc = [%import: Data_intf.Tutorial.toc] [@@deriving show]
+
+type contribute_link = [%import: Data_intf.Tutorial.contribute_link]
+[@@deriving of_yaml, show]
+
+type banner = [%import: Data_intf.Tutorial.banner] [@@deriving of_yaml, show]
+
+type external_tutorial = [%import: Data_intf.Tutorial.external_tutorial]
+[@@deriving of_yaml, show]
+
+type recommended_next_tutorials =
+ [%import: Data_intf.Tutorial.recommended_next_tutorials]
+[@@deriving of_yaml, show]
+
+type prerequisite_tutorials =
+ [%import: Data_intf.Tutorial.prerequisite_tutorials]
+[@@deriving of_yaml, show]
+
+type search_document_section =
+ [%import: Data_intf.Tutorial.search_document_section]
+[@@deriving of_yaml, show]
+
+type search_document = [%import: Data_intf.Tutorial.search_document]
+[@@deriving show]
+
+type t = [%import: Data_intf.Tutorial.t] [@@deriving show]
type metadata = {
id : string;
@@ -55,7 +90,7 @@ let decode (fpath, (head, body_md)) =
in
let section =
List.nth (String.split_on_char '/' fpath) 1
- |> Section.of_string
+ |> section_of_string
|> Result.get_ok ~error:(fun (`Msg msg) ->
Exn.Decode_error (fpath ^ ":" ^ msg))
in
@@ -118,7 +153,6 @@ include Data_intf.Tutorial
let all = %a
let all_search_documents = %a
|}
- (Fmt.brackets (Fmt.list pp ~sep:Fmt.semi))
- (all ())
- (Fmt.brackets (Fmt.list pp_search_document ~sep:Fmt.semi))
+ (Fmt.Dump.list pp) (all ())
+ (Fmt.Dump.list pp_search_document)
(TutorialSearch.all ())
diff --git a/tool/ood-gen/lib/vid.ml b/tool/ood-gen/lib/vid.ml
new file mode 100644
index 0000000000..8f0ac7128b
--- /dev/null
+++ b/tool/ood-gen/lib/vid.ml
@@ -0,0 +1,2 @@
+type t = [%import: Data_intf.Video.t] [@@deriving yaml, show]
+type video_list = t list [@@deriving yaml, show]
diff --git a/tool/ood-gen/lib/video.ml b/tool/ood-gen/lib/video.ml
index a9e4817a3b..64ba8b1224 100644
--- a/tool/ood-gen/lib/video.ml
+++ b/tool/ood-gen/lib/video.ml
@@ -1,7 +1,3 @@
-open Data_intf.Video
-
-type video_list = t list [@@deriving yaml, show]
-
let all () = Youtube.all () @ Watch.all ()
let template () =
@@ -9,7 +5,7 @@ let template () =
include Data_intf.Video
let all =%a
|ocaml}
- pp_video_list (all ())
+ Vid.pp_video_list (all ())
let scrape () =
Youtube.scrape "data/video-youtube.yml";
diff --git a/tool/ood-gen/lib/watch.ml b/tool/ood-gen/lib/watch.ml
index 55e8da4f1d..6f43a0359d 100644
--- a/tool/ood-gen/lib/watch.ml
+++ b/tool/ood-gen/lib/watch.ml
@@ -1,14 +1,11 @@
open Ocamlorg.Import
-open Data_intf.Video
-
-type video_list = t list [@@deriving yaml, show]
let all () =
let ( let* ) = Result.bind in
let videos =
let file = "video-watch.yml" in
let* yaml = Utils.yaml_file file in
- yaml |> video_list_of_yaml |> Result.map_error (Utils.where file)
+ yaml |> Vid.video_list_of_yaml |> Result.map_error (Utils.where file)
in
Result.get_ok ~error:(fun (`Msg msg) -> Exn.Decode_error msg) videos
@@ -28,7 +25,7 @@ let get_string_or_none = function `String s -> s | _ -> ""
let of_json json =
{
- title = Ezjsonm.find json [ "name" ] |> Ezjsonm.get_string;
+ Vid.title = Ezjsonm.find json [ "name" ] |> Ezjsonm.get_string;
description = Ezjsonm.find json [ "description" ] |> get_string_or_none;
url = Ezjsonm.find json [ "url" ] |> Ezjsonm.get_string;
thumbnail =
@@ -44,7 +41,7 @@ let of_json json =
let watch_to_yaml t =
`O
[
- ("title", `String t.title);
+ ("title", `String t.Vid.title);
("description", `String t.description);
("url", `String t.url);
("thumbnail", `String t.thumbnail);
@@ -92,7 +89,7 @@ let get_all_videos () =
let scrape yaml_file =
let watch =
get_all_videos ()
- |> List.stable_sort (fun w1 w2 -> String.compare w1.title w2.title)
+ |> List.stable_sort (fun w1 w2 -> String.compare w1.Vid.title w2.Vid.title)
in
let yaml = to_yaml watch in
let output =
diff --git a/tool/ood-gen/lib/youtube.ml b/tool/ood-gen/lib/youtube.ml
index 601cf87e5b..86c9e2a45b 100644
--- a/tool/ood-gen/lib/youtube.ml
+++ b/tool/ood-gen/lib/youtube.ml
@@ -1,5 +1,18 @@
open Ocamlorg.Import
-open Data_intf.Video
+
+let to_yaml video =
+ match Vid.to_yaml video with
+ | `O u -> `O (List.filter (( <> ) ("description", `String "")) u)
+ | x -> x
+
+let add_key_default k v = function
+ | `O u when u |> List.split |> fst |> List.mem k |> not ->
+ prerr_endline k;
+ `O ((k, v) :: u)
+ | x -> x
+
+let of_yaml yml =
+ yml |> add_key_default "description" (`String "") |> Vid.of_yaml
type kind = Playlist | Channel
@@ -40,8 +53,6 @@ let source_to_url { kind; id; _ } =
let source_to_id { kind; id; _ } =
Printf.sprintf "yt:%s:%s" (kind_to_string kind) id
-type video_list = t list [@@deriving yaml, show]
-
type tag =
| Entry
| Title of string
@@ -103,7 +114,7 @@ let video_opt source = function
Some author_uri ) ->
Some
{
- title;
+ Vid.title;
url;
thumbnail;
description;
@@ -142,14 +153,14 @@ let all () =
let videos =
let file = "video-youtube.yml" in
let* yaml = Utils.yaml_file file in
- yaml |> video_list_of_yaml |> Result.map_error (Utils.where file)
+ yaml |> Vid.video_list_of_yaml |> Result.map_error (Utils.where file)
in
Result.get_ok ~error:(fun (`Msg msg) -> Exn.Decode_error msg) videos
module VideoSet = Set.Make (struct
- type nonrec t = t
+ type nonrec t = Vid.t
- let compare a b = compare a.url b.url
+ let compare a b = compare a.Vid.url b.Vid.url
end)
let scrape yaml_file =
@@ -170,8 +181,8 @@ let scrape yaml_file =
|> Seq.filter (fun video ->
(not src.only_ocaml)
|| String.(
- is_sub_ignore_case "ocaml" video.title
- || is_sub_ignore_case "ocaml" video.description)))
+ is_sub_ignore_case "ocaml" video.Vid.title
+ || is_sub_ignore_case "ocaml" video.Vid.description)))
|> VideoSet.of_seq |> Result.ok
in
match fetched with
@@ -179,8 +190,8 @@ let scrape yaml_file =
let yaml =
VideoSet.union fetched scraped
|> VideoSet.to_seq |> List.of_seq
- |> List.sort (fun a b -> compare b.published a.published)
- |> video_list_to_yaml
+ |> List.sort (fun a b -> compare b.Vid.published a.Vid.published)
+ |> Vid.video_list_to_yaml
in
let output =
Yaml.pp Format.str_formatter yaml;
diff --git a/tool/tailwind/dune b/tool/tailwind/dune
index 02f62f861d..bbcbeff0f5 100644
--- a/tool/tailwind/dune
+++ b/tool/tailwind/dune
@@ -2,40 +2,40 @@
(target tailwindcss-linux-x64)
(action
(progn
- (run
- curl
- -sLO
- https://github.com/tailwindlabs/tailwindcss/releases/download/v3.3.6/%{target})
+ (with-stdout-to
+ %{target}
+ (bash
+ "cat 2> /dev/null < $(which tailwindcss) || curl -#fSL https://github.com/tailwindlabs/tailwindcss/releases/download/v3.3.6/%{target}"))
(run chmod +x %{target}))))
(rule
(target tailwindcss-linux-arm64)
(action
(progn
- (run
- curl
- -sLO
- https://github.com/tailwindlabs/tailwindcss/releases/download/v3.3.6/%{target})
+ (with-stdout-to
+ %{target}
+ (bash
+ "cat 2> /dev/null < $(which tailwindcss) || curl -#fSL https://github.com/tailwindlabs/tailwindcss/releases/download/v3.3.6/%{target}"))
(run chmod +x %{target}))))
(rule
(target tailwindcss-macos-x64)
(action
(progn
- (run
- curl
- -sLO
- https://github.com/tailwindlabs/tailwindcss/releases/download/v3.3.6/%{target})
+ (with-stdout-to
+ %{target}
+ (bash
+ "cat 2> /dev/null < $(which tailwindcss) || curl -#fSL https://github.com/tailwindlabs/tailwindcss/releases/download/v3.3.6/%{target}"))
(run chmod +x %{target}))))
(rule
(target tailwindcss-macos-arm64)
(action
(progn
- (run
- curl
- -sLO
- https://github.com/tailwindlabs/tailwindcss/releases/download/v3.3.6/%{target})
+ (with-stdout-to
+ %{target}
+ (bash
+ "cat 2> /dev/null < $(which tailwindcss) || curl -#fSL https://github.com/tailwindlabs/tailwindcss/releases/download/v3.3.6/%{target}"))
(run chmod +x %{target}))))
(rule
@@ -81,3 +81,8 @@
(alias
(name default)
(deps tailwindcss))
+
+(install
+ (section bin)
+ (package tailwindcss)
+ (files tailwindcss))