Draft tutorials on Modules, Functors and Libraries

Dear OCamlers,

The series on ocaml.org tutorial updates continue. This time, the ocaml.org team has three drafts related to the module system in a single pull request. We want your feedback on it:

The target audience is developers learning OCaml. No functional programming knowledge is assumed. However, it comes after the “Get Started” series:

  1. Installing OCaml
  2. A Tour of OCaml
  3. Your First OCaml Program

They also require the first two tutorials of the “Introduction” series as prerequisites:

  1. Values and Functions
  2. Basic Datatypes and Pattern Matching

As the previously announced drafts, these also contain overlooked issues. We want to make it better with your help.

Share your feedback on GitHub or here, but do not use the “Contribute” link at the bottom of the staging pages.

Hope it helps

9 Likes

Sharing some feedback here for Libraries With Dune · OCaml Documentation

  1. There’s no need for touch mixtli.opam as dune will generate the opam file
  2. I’d suggest not to introduce a new domain (clouds and their names) when teaching dune concepts, it will unnecessarily distract from the material. We can stick to simple concepts that everyone is familiar with, like say math (addition, subtraction, etc.: let add x y = x + y and so on)
  3. There’s no pressing need to introduce public_name for the executable stanza, at the beginner level. We can just say that the executable name is the same as its main module
  4. We should mention the most important reason for having wrapper modules for libraries, to avoid module name clashes, i.e. namespacing

Otherwise it looks good and is much needed.

3 Likes

Thanks for your feedback @yawaramin, this is really helpful.

  • There’s no need for touch mixtli.opam as dune will generate the opam file

Good idea.

  • I’d suggest not to introduce a new domain (clouds and their names) when teaching dune concepts, it will unnecessarily distract from the material. We can stick to simple concepts that everyone is familiar with, like say math (addition, subtraction, etc.: let add x y = x + y and so on)

We are trying to minimize the number of math-based examples, that’s why strings were picked here.

We’re also trying to have refreshing examples, to avoid the deja vu feeling from foo, bar, alice, bob and other canned examples. But if it turns out to be too much cognitive noise for the majority we’ll pick something else.

  • There’s no pressing need to introduce public_name for the executable stanza, at the beginner level.

But then we have to instruct learners to type dune exec ./cloud.exe, don’t we? Avoiding to explain that ./cloud.exe is not a file name was the goal. Some beginners seem to have a hard time with this.

We can just say that the executable name is the same as its main module

We are doing that in earlier tutorials which are using Dune (First Program and Functors). But since this tutorial is covering some Dune, I believe it is fit to introduce the different names and their roles.

  • We should mention the most important reason for having wrapper modules for libraries, to avoid module name clashes, i.e. namespacing

You are right, this is missing.

1 Like

I know this isn’t part of the PR, but I started looking through everything, and I noticed this:

Characters

Values of type char correspond to the 256 symbols of the Latin-1 set.

When I came to OCaml, this confused me. As far as I understand, it’s okay (and normal) to store UTF-8 in the string type, right? Thus:

# "Grüße".[2];;
- : char = '\195'

The char isn’t necessarily Latin-1 but could be just a part of an UTF-8 sequence.

If I have time, I’ll look through the rest too and see if I stumble upon anything else that confused me when getting into OCaml. (Thanks for all the work, I’m really excited about learning OCaml!)

1 Like

Indeed. That latin1 intepretation is an old-fashioned view that should not be propagated. The story is that char values are bytes (which for 0x20-0x7E you can specify by their corresponding US-ASCII literal if you fancy so), string values are immutable sequences of bytes, bytes values are mutable sequence of bytes. And that the recommended default way of interpreting strings or bytes as text if you need to is as UTF-8 encoded text.

This view can be found in the documentation preamble of the String module.

2 Likes

That’s sort-of the story but the documentation is not wholly consistent with the view that char values are bytes. For example for the int_of_char function (and the Char.code function), the documentation reference states that these “Return the ASCII code of the argument”. In reality, those functions work over the entire byte range, not just the ASCII range.

Edit: And of course a similar point arises with respect to char_of_int and Char.chr.

1 Like

File-Based Modules

In OCaml, every piece of code is wrapped into a module. Optionally, a module itself can be a submodule of another module, pretty much like directories in a file system.

When you write a program, let’s say using the two files amodule.ml and bmodule.ml, each automatically defines a module named Amodule and a module named Bmodule, which provides whatever you put into the files.

What I wondered about is how the “upcasing” actually works. As far as I understand, only the first letter is upcased. Thus a file name foo_bar.ml would define a module Foo_bar and a file name fooBar.ml would define a module FooBar, right?

Am I right that FooBar.ml would be a forbidden filename?

I think the idiomatic way for naming modules is to use underscores, not camel-case, right?

I think some information on that topics might be helpful, and perhaps also an explicit note that the upcasing of the first letter (and the first letter only!) is done automatically.

2 Likes

No you can do that. Details about interaction between the file system and the module system is documented here.

Though personally I rather avoid uppercase letters for starting source files. This gives an opportunity for other interesting files to stand out when you ls a directory (e.g. Makefile, README, etc.).

That’s still disputed today :–) But personally I simply use this algorithm.

2 Likes

I like that functors are introduced using Set. I would have liked to see this motivated by explaining why we need functors: it is not possible to implement a container (like a set) over some data type just using polymorphism because we need access to additional properties (like order) that are unavailable. The set implementation needs to know more about the base data type and this additional knowledge is provided by the functor argument.

2 Likes

Thanks, @lindig; as a rule of thumb, we try to present things in this order:

  1. Use
  2. Define your own

This is why we start with Set.Make.

We also try to show examples first and give explanations second; this allows referring to the example in the explanations and limits abstract sentences.

The motivation is presented as an answer to: “Why is this a functor?” Therefore, it comes second:

Most set operation implementations must use a comparison function. Using Stdlib.compare would make it impossible to use a user-defined comparison algorithm. Passing the comparison function as a higher-order parameter, as done in Array.sort , for example, would add a lot of boilerplate code. Providing set operations as a functor allows specifying the comparison function only once.

As usual, we’re open to suggestions and proposals.

Keep up the good work. Read two of them top to bottom and both were great

1 Like