Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First class module (Learning/Language/Module System) #2090

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4a7a675
First class module
Feb 23, 2024
7be2a11
Merge branch 'first-class-module' of github.com:F-Loyer/ocaml.org int…
Feb 23, 2024
a20fe5a
Little typos
Feb 23, 2024
b381255
Little typos
Feb 23, 2024
1f28852
Little typos
Feb 23, 2024
eb7b4fa
Line ediitng
christinerose Feb 26, 2024
4c11fd6
Line ediitng
christinerose Feb 26, 2024
1b731be
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
10982a3
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
c5e508f
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
2cb7439
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
1e806b5
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
50549d2
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
23bafc5
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
b9f5311
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
7f19409
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
c67cf81
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
2fbafba
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
7d20e97
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
5739102
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
7d37cc4
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
d694baf
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
0c695de
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
1685fcf
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
dac5995
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
e3e6350
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
8450acd
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
4046171
Update data/tutorials/language/1ms_03_first_class_modules.md
F-Loyer Mar 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions data/tutorials/language/1ms_03_first_class_modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
---
id: first-class-modules
title: First-Class Modules
description: >
First-Class Modules permits the use of modules as if they are usual values.
category: "Module System"
---
## Introduction

This tutorial details how to use first-class modules in a program. This
can dispense the user of a library to use functors in order to create some
modules.

We will use a dummy database driver implementation with functors and first-class modules in this tutorial.

**Prerequisites**: [Modules](/docs/modules), [Functors](/doc/functors).

## An Example With Functors

Let's suppose we want to have a program that can be used with multiple
databases. Each database has a specific set of parameters (host, credentials,
database name...) and a set of functions (prepare query, execute
query...) which can be implemented differently.

One implementation would be to define a generic module signature and
provide specific modules, one for each database type. The program can be
presented like this:

```ocaml
(* Database library *)

module type Database = sig
val execute : string -> string list
end

module type Postgres_Connection = sig
val hostname : string
val database_name : string
end

module Postgres(Cnx : Postgres_Connection) : Database = struct
let execute query =
Printf.printf "[%s@%s] exec %s\n"
Cnx.database_name Cnx.hostname query;
[ "A dummy result" ]
end

(* Main program *)

module My_Database =
Postgres(struct
let hostname = "localhost"
let database_name = "dummy"
end)

let () =
ignore @@ My_Database.execute "SELECT * FROM table"
```

Here, we would just have to change the `My_Database` definition
to use a database from an other type.

We can also define multiple `My_Database` modules if we want our program
to work simultaneously with multiple databases that may have different drivers.

## First-Class Modules

In the previous example, we saw that we could have a modular program
where changing a database would just need a couple lines changed. However,
the `My_Database` module is only visible from the module where it is declared.

Defining a generic `transaction` function that would send `start`, `commit`,
and/or `rollback` would also need to access this module. This can't always be practical.

First-class modules can make it possible to call this `transaction` function
with a module as a parameter. The idea is
to pass a module as if it was an argument of usual type, then convert
the argument back to a module if we want to use its exported values.

We have the following expressions:

```ocaml
let variable = (module M:ModuleType) in ...
let module M = (val argument:ModuleType) in ...
```

The first expression converts a module into
a variable of type `(module ModuleType)`. (We can replace `M` with a `struct ... end`
definition or a functor call).

The second expression converts back such a value into a module `M`, which
can be used in the expression (expression of the form `M.function`).

If a function just needs to get a first-class module and uses its exported values,
we can just declare this function in the following way:

```ocaml
let f (module M:ModuleType) =
...
```

Then the conversion is implicit and the body of the function can use
the module `M`.

The following program illustrates the use of first-class modules:

```ocaml
(* Database library *)

module type Database = sig
val execute : string -> string list
end

let postgress_connect _hostname _database_name =
(module struct
let hostname = _hostname
let database_name = _database_name
let execute query =
Printf.printf "[%s@%s] exec %s\n" database_name hostname query;
["Dummy result"]
end:Database)

let execute (module D:Database) query =
D.execute query

(* Main program *)

let () =
let database = postgress_connect "localhost" "dummy" in
ignore @@ execute database "SELECT * FROM table_a"
```

The program is a little simpler since we don't have to define a dedicated
module that contains database connection information, and the programmer
who has to use the database library can use it
without having to call functors directly. The database variable can be used
as if it had a usual type and passed to any function that needs to use the database.

Note, we can also define `execute` in the following way:

```ocaml
let execute database query =
let module D = (val database:Database) in
D.execute query
```

Here, the `database` argument can be used if we have to call other functions
that need it as a first-class module argument (let's say a commit/rollback
function).

Remember, OCaml modules can be seen as usual values. The database
example shows a use with a function argument, but we can also consider a list
of modules (ex: modules that represent drawable forms).

## Comparison to Object Programming

First-class modules can be compared with object instances from the object-programming
paradigm. It has in common:

- Encapsulation: the internal variables of the `Postgress` module are not
accessible,
- Polymorphism: the `execute` function can be used with different types
of databases and will behave differently.

However, the first-class module we have implemented lacks inheritence. It may be
compared to a Java class that implements a single interface. The `include`
statement may bring some inheritence features, but imported functions won't call
overidden functions. Then we can't consider the use of first-class modules as
strictly equivent to the oriented object paradigm.
Loading