Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 235 lines (170 sloc) 9.769 kb
fccc685 Initial open-source release
MLstate authored
1 ==== How to write an OPA module ====
2
3 Read this before writing an OPA module.
4 If you don't, chances are that the module will not be kept.
5
6 The role of these guidelines is to make sure that:
7 + we can find our way in the code easily
8 + we produce documentation for end-users
9 + your changes break neither the compiler nor the user's code.
10
11 ==== I. All code must be documented. ========
12
13 For examples of documented code, see the existing code.
14 Try to stay as much as possible is the same spirit, and
15 check that the generated html (opadoc) is well formed.
16
17 Documenting takes only a few minutes.
18 Understanding undocumented code takes hours or days.
19 If you don't document your code, someone will hate you passionately.
20 You have been warned.
21
22 Important Note: Don't confuse "commenting" and "documenting".
23 Comments are good but can't replace documentation.
24 Again, see the examples.
25
26 Documentation MUST indicate, among other things
27 - what the module is for
28 - who it is for ("@audience PUBLIC"/"@audience PRIVATE"/...)
29 - possibly, how it is used, by who, in particular for modules of stdlib.core,
30 because any change in this kind of modules can have an impact on the compiler.
31
32 For an example of documentation, see DOCUMENTING, in this directory,
33 or see all the existing documentation.
34
35 ==== II. How should I call my module? Where should I put it? ========
36
37 We have a notion of packages. The compiler is fully separated with respect to
38 packages.
39
40 The main criterium for the separation of files is the packages dependencies.
41
42 Package names can be named with a '.' (dot), but it is a fake hierarchy. In
43 fact, a dot can be seen like an underscore, and packages are identifying by
44 their names, so, if it is not needed, do not go too deep in the hierarchy.
45
46 II.1) stdlib.core
47
48 If your module is used by the compiler, it must go to : stdlib.core
49 This package is needed for core opa features, it contains mostly run time
50 support, like comparison, serialization, servers codes.
51
52 This package also defines its own interface for the compiler, i.e. the complete
53 set of function the compiler can insert calls on. This interface is declared by declaration tagged by @opacapi directives.
54 Theses declarations should only be toplevel aliases, on functions in stdlib.core,
55 and the name of each alias must be the full path of the function with dots
56 replaced by underscores.
57 E.g. : Value_serialize = Value.serialize
58
59 Be extremly aware of the fact that we are trying to reduce as much as possible
60 the size of the minimal server. This has also an important impact on the debugging.
61 If your module is not used by the compiler, then, it has really nothing to do in stdlib.core.
62 If you add a lot of code in stdlib.core, someone will hate you passionately.
63 You have been warned.
64
65 II.2) stdlib.*
66
67 These packages are additionnaly libs we want to be part of the standard library.
68 It is still not really clear if a package should be in the stdlib, or not.
69 Probably most of generic packages developped @mlstate will be in stdlib.*
70
71 If your package is too specific, then it is probably better to make a separate package.
72
73 some example:
74 facebook -> not is stdlib, TODO: specify the package organisation.
75 fgraph -> stdlib.fgraph
76
77 If you want to add several modules related to the same thing, it would be preferable
78 to create a new package. (cf e.g. fgraph)
79 If your module is an extension of an existing package, simply add it in the corresponding package.
80
81 ==== III. Code goes into modules. ====
82
83 Remember, by default, in OPA, everything sits in the current package namespace.
84 Every typename and every value name you create therefore pollutes the current package namespace,
85 potentially breaking someone else's code.
86
87 Consequently, all your values must be in modules, and/or restricting the interfaces
88 with the scopes directives :
89 @public
90 @package
91 @private
92
93 Similarly, all your types should respect the namespace conventions.
94 For the moment, we don't have a syntax to put types into record.
95 Until then, if you wish to add a type "foo" to your module "Foo", call it "Foo.foo".
96 The name of types should strictly follow the hierarchy of modules.
97
98 If you wish to add a function or a type to the global namespace, ask the opa team first.
99 Be aware that big changes in the stdlib are dangerous because of the number of potential
100 users which already use it.
101 Be aware that if you add a function with a ugly name (or even worth, a function with a
102 name incoherent with the implementation), someone could use it the next day,
103 and if you want to change some names, and types after that, it will imply a big refactoring
104 everywhere your functions are used.
105
106 If you add function which does not follow naming conventions, or argument order conventions,
107 or any other conventions written in this file, someone will hate you passionately.
108 You have been warned.
109
110 Remember that once a OPA distribution will be done, we will no more be able to change any interfaces !
111 There is no hurry. Take your time. Review your code, ask advice for names of functions to other people,
112 it is always good to have several point of vues.
113 See also if a correspond lib does not already exists in an other language.
114 For exemple, if there is a reference API in an other language, you can keep the same names
115 (convention used e.g. in fgraph, the API is mostly preserved from OcamlGraph)
116
117 Let's say we are playing in a contest of beauty of API.
118
119 ==== IV. Naming conventions. ====
120
121 values_look_like_this
122 ModuleLookLikeThis
123 TypesInModule.Look.like_this
124 fields_look_like_this
125 files_look_like_this.opa
126
127 Accessor functions:
128
129 get_foo
130 is_foo
131
132 Conversion functions:
133
134 Foo.of_bar
135 Foo.to_bar
136
137 ==== IV. Types are closed. ====
138
139 When you define a new type or data structure, chances are that users don't need
140 to know how it's implemented, so you should make it @abstract (if users should
141 see the type but not its definition) or @private (if the type is purely
142 internal). This will make error messages simpler for the user and it might make
143 type-checking a tad faster.
144
145 ==== V. Mutability. ====
146
147 Mutability is bad. You have no idea how bad it is until you've attempted to
148 execute mutable code in parallel. Consequently, mutable code should be
149 avoided. Really.
150
151 In the current versions of OPA, the unit of mutability is the session. If you
152 need mutability, use a session. If you need a global state shared by the server,
153 use [UserContext]. If you need something to be saved for future uses, use the
154 database. Don't write volatile stuff into the database. No sessions, no session
155 identifiers, etc.
156
157 If you have a use case that fits none of these, and if you are really convinced
158 that you need a reference, come and see David. He might scream at you, of
159 course.
160
161 ==== V. Interacting with the compiler. ===
162
163 If you define a value which needs to be used by the compiler, there are a few
164 steps you need to take in order to make sure that it won't be removed
165 prematurely by a compiler optimization and/or by the persons maintaining the
166 library.
167
168 For this purpose, you should
169 - mark your value as @opacapi
170 - add it to opacapi/opacapi.ml
171 - in some cases, add it to the roots
172 - document how and why it's used in the compiler
173
174 ==== VI. Checking that it works. ===
175
176 Compile with the normal procedure (make), and run tests.
177
178 ==== VII. Bypasses ===
179
180 Make what you want with bypasses. Do not export private bypasses.
181
182 ==== VIII. Fold, map, etc. ===
183
184 The base loops upon each data structure are
185 - fold, foldi
186 - map, mapi
187 - iter, iteri
188 - filter, filteri
189 - reduce, reducei
190 - filter_map, filter_mapi
191
192 Wherever possible, data structures should also support constructors
193 - unfold, unfoldi
194 - init
195 - of_list
196
197 And conversion to external data structures
198 - to_list
199 - to_map
200
201 Unless your data structure doesn't support these constructions, they should all exist, exactly with this name.
202 If your data structure supports several folds (or several maps, etc), one of them, the default one, must be
203 called exactly "fold" (respectively "map"). Name the variants with a suffix, which makes the difference explicit
204 (e.g. "fold" -> "fold_backwards", "iteri" -> "iteri_backwards", etc.)
205
206 The order of arguments is the following, keep it consistent for all data structures:
207
208 function ( iterator, structure, [more-arguments]) : 'result
209
210 - exists: ( f:('a -> bool), structure: t('a) ): bool
211
212 - filter: ( f:'a -> bool, structure: t('a) ): t('a)
213 - filteri: ( f:int -> 'a -> bool, structure: t('a) ): t('a)
214 - filter_map: ( f:'a -> option('b), structure: t('a) ): t('b)
215 - filter_mapi: ( f:int -> 'a -> option('b), structure: t('a) ): t('b)
216
217 - fold: ( f:('item, 'acc) -> 'acc, structure: t('item), init:'acc ): 'acc
218 - foldi: ( f:(int, 'item, 'acc) -> 'acc, structure: t('item), init:'acc ): 'acc
219
220 - init: ( f:int -> 'item, size: int): t('item)
221
222 - iter: ( f:('a -> void), structure: t('a) ): void
223 - iteri: ( f:(int, 'a) -> void, structure: t('a) ): void
224
225 - map: ( f:'a -> 'b, structure: t('a) ): t('b)
226 - mapi: ( f:(int,'a) -> 'b, structure: t('a) ): t('b)
227
228 - mem: ( elt:'a, structure: t('a) ): bool
229
230 - reduce: ( f:'a -> 'a, structure: t('a) ): option('a) //{none} if the structure was empty
231 - reducei: ( f:int -> 'a -> 'a, structure: t('a) ): option('a) //{none} if the structure was empty
232
233 - unfold: ( f:'counter -> option(('item, 'counter)), init: 'counter ): t('item)
234 - unfoldi: ( f:(int, 'counter)-> option(('item, 'counter)), init: 'counter): t('item)
Something went wrong with that request. Please try again.