Skip to content

Commit d7d1144

Browse files
committed
Add docs about recursive modules
1 parent 81131c6 commit d7d1144

3 files changed

Lines changed: 142 additions & 2 deletions

File tree

_docs_src/mkdocs.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ nav:
1212
- Matplotlib Example 2: matplotlib-2.md
1313
- Handling Dictionaries: dictionaries.md
1414
- Handling Dictionaries 2: dictionaries-2.md
15+
- Recursive Classes: recursive.md
1516
- Rules:
1617
- Types: types.md
1718
- Function & Argument Names: names.md
1819
- Attributes & Properties: attributes.md
1920
- Instance Methods: instance-methods.md
20-
- Class & Static Methods: static-methods.md
21+
- Class & Static Methods; Functions: static-methods.md
2122
- Miscellaneous:
2223
- CLI Options: cli-options.md
2324
- Converting `pyobjects` to OCaml types: of-pyobject.md

_docs_src/src/recursive.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Binding Recursive Classes
2+
3+
You will often run into cases in which you need to bind classes that are cyclical. Here's an example:
4+
5+
```python
6+
class Foo:
7+
@staticmethod
8+
def make_bar():
9+
return Bar()
10+
11+
class Bar:
12+
@staticmethod
13+
def make_foo():
14+
return Foo()
15+
```
16+
17+
`Foo` has a method that returns a `Bar` object, and `Bar` has a method that returns a `Foo` object.
18+
19+
While this works fine in Python, we have to be more explicit in OCaml to use recursive modules. Technically, `pyml_bindgen` doesn't handle recursive modules. But it is simple enough to edit the output by hand. Let's see.
20+
21+
22+
## Value specs
23+
24+
Since there are two classes to bind, we will make two val spec files.
25+
26+
`foo_val_specs.txt`
27+
28+
```ocaml
29+
val make_bar : unit -> Bar.t
30+
```
31+
32+
`bar_val_specs.txt`
33+
34+
```ocaml
35+
val make_foo : unit -> Foo.t
36+
```
37+
38+
## Run `pyml_bindgen`
39+
40+
Now, run `pyml_bindgen` with some extra shell commands to make the output look nicer.
41+
42+
```
43+
pyml_bindgen foo_val_specs.txt silly Foo --caml-module Foo -r no_check \
44+
| ocamlformat --enable --name=a.ml - > lib.ml
45+
46+
printf "\n" >> lib.ml
47+
48+
pyml_bindgen bar_val_specs.txt silly Bar --caml-module Bar -r no_check \
49+
| ocamlformat --enable --name=a.ml - >> lib.ml
50+
```
51+
52+
## Fix the output
53+
54+
If you were to try and compile that code, you'd get a lot of errors including about unknown`Bar` module.
55+
56+
To fix it, change `module Foo : sig` to `module rec Foo : sig` and `module Bar : sig` to `and Bar : sig`.
57+
58+
Once you do that, everything will compile fine :)
59+
60+
Here is what the output should look like:
61+
62+
```ocaml
63+
module rec Foo : sig
64+
type t
65+
66+
val of_pyobject : Pytypes.pyobject -> t
67+
68+
val to_pyobject : t -> Pytypes.pyobject
69+
70+
val make_bar : unit -> Bar.t
71+
end = struct
72+
let filter_opt l = List.filter_map Fun.id l
73+
74+
let import_module () = Py.Import.import_module "silly"
75+
76+
type t = Pytypes.pyobject
77+
78+
let of_pyobject pyo = pyo
79+
80+
let to_pyobject x = x
81+
82+
let make_bar () =
83+
let class_ = Py.Module.get (import_module ()) "Foo" in
84+
let callable = Py.Object.find_attr_string class_ "make_bar" in
85+
let kwargs = filter_opt [] in
86+
Bar.of_pyobject
87+
@@ Py.Callable.to_function_with_keywords callable [||] kwargs
88+
end
89+
90+
and Bar : sig
91+
type t
92+
93+
val of_pyobject : Pytypes.pyobject -> t
94+
95+
val to_pyobject : t -> Pytypes.pyobject
96+
97+
val make_foo : unit -> Foo.t
98+
end = struct
99+
let filter_opt l = List.filter_map Fun.id l
100+
101+
let import_module () = Py.Import.import_module "silly"
102+
103+
type t = Pytypes.pyobject
104+
105+
let of_pyobject pyo = pyo
106+
107+
let to_pyobject x = x
108+
109+
let make_foo () =
110+
let class_ = Py.Module.get (import_module ()) "Bar" in
111+
let callable = Py.Object.find_attr_string class_ "make_foo" in
112+
let kwargs = filter_opt [] in
113+
Foo.of_pyobject
114+
@@ Py.Callable.to_function_with_keywords callable [||] kwargs
115+
end
116+
```
117+
118+
## Using the generated modules
119+
120+
You can use the generated modules as you would any others.
121+
122+
```ocaml
123+
open Lib
124+
125+
let () = Py.initialize ()
126+
127+
let (_bar : Bar.t) = Foo.make_bar ()
128+
let (_foo : Foo.t) = Bar.make_foo ()
129+
```
130+
131+
## Wrap-up
132+
133+
You may come across cyclic classes when binding Python code. If you want to bind them in OCaml as it, you will need to use recursive module. For now, `pyml_bindgen` won't generate them for you automatically, but it is not *too* bad to change them by hand :)

_docs_src/src/static-methods.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Class & Static Methods
1+
# Class & Static Methods; Functions
22

33
Value specs for class/static methods look like this:
44

@@ -39,3 +39,9 @@ val __init__ : name:string -> age:int -> unit -> t
3939
```
4040

4141
If you want to generate functions that ensure the class is correct, you can return `t option` or `t Or_error.t` instead.
42+
43+
## Functions
44+
45+
You can also bind functions that are not associated with a class.
46+
47+
The rules are the same for the class and static methods. To tell `pyml_bindgen` that you are actually binding module functions rather than class methods, you have to pass in a command line option `--associated-with module`.

0 commit comments

Comments
 (0)