Skip to content

Commit 355478d

Browse files
authored
Merge pull request #299 from talex5/docs
Add Best Practices section to README
2 parents 84e9ce9 + c24655c commit 355478d

2 files changed

Lines changed: 117 additions & 0 deletions

File tree

README.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ Eio replaces existing concurrency libraries such as Lwt
4646
* [Async](#async)
4747
* [Lwt](#lwt)
4848
* [Unix and System Threads](#unix-and-system-threads)
49+
* [Best Practices](#best-practices)
50+
* [Switches](#switches-1)
51+
* [Casting](#casting)
52+
* [Passing Stdenv.t](#passing-stdenvt)
4953
* [Further Reading](#further-reading)
5054

5155
<!-- vim-markdown-toc -->
@@ -1320,6 +1324,117 @@ The [Eio_unix][] module provides features for using Eio with OCaml's Unix module
13201324
In particular, `Eio_unix.run_in_systhread` can be used to run a blocking operation in a separate systhread,
13211325
allowing it to be used within Eio without blocking the whole domain.
13221326

1327+
## Best Practices
1328+
1329+
This section contains some recommendations for designing library APIs for use with Eio.
1330+
1331+
### Switches
1332+
1333+
A function should not take a switch argument if it could create one internally instead.
1334+
1335+
Taking a switch indicates that a function creates resources that outlive the function call,
1336+
and users seeing a switch argument will naturally wonder what these resources may be
1337+
and what lifetime to give them, which is confusing if this is not needed.
1338+
1339+
Creating the switch inside your function ensures that all resources are released
1340+
promptly.
1341+
1342+
```ocaml
1343+
(* BAD - switch should be created internally instead *)
1344+
let load_config ~sw path =
1345+
parse_config (Eio.Path.open_in ~sw path)
1346+
1347+
(* GOOD - less confusing and closes file promptly *)
1348+
let load_config path =
1349+
Switch.run @@ fun sw ->
1350+
parse_config (Eio.Path.open_in ~sw path)
1351+
```
1352+
1353+
Of course, you could use `with_open_in` in this case to simplify it further.
1354+
1355+
### Casting
1356+
1357+
Unlike many languages, OCaml does not automatically cast objects (polymorphic records) to super-types as needed.
1358+
Remember to keep the type polymorphic in your interface so users don't need to do this manually.
1359+
This is similar to the case with polymorphic variants (where APIs should use `[< ...]` or `[> ...]`).
1360+
1361+
For example, if you need an `Eio.Flow.source` then users should be able to use a `Flow.two_way`
1362+
without having to cast it first:
1363+
1364+
<!-- $MDX skip -->
1365+
```ocaml
1366+
(* BAD - user must cast to use function: *)
1367+
module Message : sig
1368+
type t
1369+
val read : Eio.Flow.source -> t
1370+
end
1371+
1372+
(* GOOD - a Flow.two_way can be used without casting: *)
1373+
module Message : sig
1374+
type t
1375+
val read : #Eio.Flow.source -> t
1376+
end
1377+
```
1378+
1379+
If you want to store the argument, this may require you to cast internally:
1380+
1381+
```ocaml
1382+
module Foo : sig
1383+
type t
1384+
val of_source : #Eio.Flow.source -> t
1385+
end = struct
1386+
type t = {
1387+
src : Eio.Flow.source;
1388+
}
1389+
1390+
let of_source x = {
1391+
src = (x :> Eio.Flow.source);
1392+
}
1393+
end
1394+
```
1395+
1396+
Note: the `#type` syntax only works on types defined by classes, whereas the slightly more verbose `<type; ..>` works on all object types.
1397+
1398+
### Passing Stdenv.t
1399+
1400+
The `env` value you get from `Eio_main.run` is a powerful capability,
1401+
and programs are easier to understand when it's not passed around too much.
1402+
1403+
In many cases, it's clearer (if a little more verbose) to take the resources you need as separate arguments, e.g.
1404+
1405+
<!-- $MDX skip -->
1406+
```ocaml
1407+
module Status : sig
1408+
val check :
1409+
clock:#Eio.Time.clock ->
1410+
net:#Eio.Net.t ->
1411+
bool
1412+
end
1413+
```
1414+
1415+
You can also provide a convenience function that takes an `env` too.
1416+
Doing this is most appropriate if many resources are needed and
1417+
your library is likely to be initialised right at the start of the user's application.
1418+
1419+
In that case, be sure to request only the resources you need, rather than the full set.
1420+
This makes it clearer what you library does, makes it easier to test,
1421+
and allows it to be used on platforms without the full set of OS resources.
1422+
If you define the type explicitly, you can describe why you need each resource there:
1423+
1424+
<!-- $MDX skip -->
1425+
```ocaml
1426+
module Status : sig
1427+
type 'a env = <
1428+
net : #Eio.Net.t; (** To connect to the servers *)
1429+
clock : #Eio.Time.clock; (** Needed for timeouts *)
1430+
..
1431+
> as 'a
1432+
1433+
val check : _ env -> bool
1434+
end
1435+
```
1436+
1437+
13231438
## Further Reading
13241439

13251440
- [lib_eio/eio.mli](lib_eio/eio.mli) documents Eio's public API.

doc/prelude.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ module Eio_main = struct
3535
method clock = fake_clock env#clock
3636
end
3737
end
38+
39+
let parse_config (flow : #Eio.Flow.source) = ignore

0 commit comments

Comments
 (0)