From f5b2865705e5a4027b3fa0a52ded34a677bdb349 Mon Sep 17 00:00:00 2001 From: Frederic Ye Date: Wed, 3 Oct 2012 19:10:09 +0200 Subject: [PATCH] [doc] CONTRIBUTING: merged stdlib guidelines --- CONTRIBUTING.md | 243 ++++++++++++++++++++++++++++++++++++++++++ lib/stdlib/GUIDELINES | 234 ---------------------------------------- 2 files changed, 243 insertions(+), 234 deletions(-) delete mode 100644 lib/stdlib/GUIDELINES diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 923558dc..76ba414d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,6 +37,249 @@ YEAR.MONTH.DAY, Version SEMVER (stability), GIT_HASH * change 3 +# Stdlib + +## How to write an Opa module + +Read this before writing an Opa module. +If you don't, chances are that the module will not be kept. + +The role of these guidelines is to make sure that: + +- we can find our way in the code easily +- we produce documentation for end-users +- your changes break neither the compiler nor the user's code. + +### I. All code must be documented. + +For examples of documented code, see the existing code. +Try to stay as much as possible is the same spirit, and +check that the generated html (opadoc) is well formed. + +Documenting takes only a few minutes. +Understanding undocumented code takes hours or days. +If you don't document your code, someone will hate you passionately. +You have been warned. + +Important Note: Don't confuse "commenting" and "documenting". +Comments are good but can't replace documentation. +Again, see the examples. + +Documentation MUST indicate, among other things +- what the module is for +- who it is for ("@audience PUBLIC"/"@audience PRIVATE"/...) +- possibly, how it is used, by who, in particular for modules of stdlib.core, +because any change in this kind of modules can have an impact on the compiler. + +For an example of documentation, see DOCUMENTING, in this directory, +or see all the existing documentation. + +### II. How should I call my module? Where should I put it? + +We have a notion of packages. The compiler is fully separated with respect to +packages. + +The main criterium for the separation of files is the packages dependencies. + +Package names can be named with a '.' (dot), but it is a fake hierarchy. In +fact, a dot can be seen like an underscore, and packages are identifying by +their names, so, if it is not needed, do not go too deep in the hierarchy. + +#### II.1) stdlib.core + +If your module is used by the compiler, it must go to : stdlib.core +This package is needed for core Opa features, it contains mostly run time +support, like comparison, serialization, servers codes. + +This package also defines its own interface for the compiler, i.e. the complete +set of function the compiler can insert calls on. This interface is declared by declaration tagged by @opacapi directives. +Theses declarations should only be toplevel aliases, on functions in stdlib.core, +and the name of each alias must be the full path of the function with dots +replaced by underscores. +E.g. : Value_serialize = Value.serialize + +Be extremly aware of the fact that we are trying to reduce as much as possible +the size of the minimal server. This has also an important impact on the debugging. +If your module is not used by the compiler, then, it has really nothing to do in stdlib.core. +If you add a lot of code in stdlib.core, someone will hate you passionately. +You have been warned. + +##### II.2) stdlib.* + +These packages are additionnaly libs we want to be part of the standard library. +It is still not really clear if a package should be in the stdlib, or not. +Probably most of generic packages developped @mlstate will be in stdlib.* + +If your package is too specific, then it is probably better to make a separate package. + +some example: + facebook -> not is stdlib, TODO: specify the package organisation. + fgraph -> stdlib.fgraph + +If you want to add several modules related to the same thing, it would be preferable +to create a new package. (cf e.g. fgraph) +If your module is an extension of an existing package, simply add it in the corresponding package. + +### III. Code goes into modules. + +Remember, by default, in Opa, everything sits in the current package namespace. +Every typename and every value name you create therefore pollutes the current package namespace, +potentially breaking someone else's code. + +Consequently, all your values must be in modules, and/or restricting the interfaces +with the scopes directives : +`@public` +`@package` +`@private` + +Similarly, all your types should respect the namespace conventions. +For the moment, we don't have a syntax to put types into record. +Until then, if you wish to add a type "foo" to your module "Foo", call it "Foo.foo". +The name of types should strictly follow the hierarchy of modules. + +If you wish to add a function or a type to the global namespace, ask the Opa team first. +Be aware that big changes in the stdlib are dangerous because of the number of potential +users which already use it. +Be aware that if you add a function with a ugly name (or even worth, a function with a +name incoherent with the implementation), someone could use it the next day, +and if you want to change some names, and types after that, it will imply a big refactoring +everywhere your functions are used. + +If you add function which does not follow naming conventions, or argument order conventions, +or any other conventions written in this file, someone will hate you passionately. +You have been warned. + +Remember that once a Opa distribution will be done, we will no more be able to change any interfaces ! +There is no hurry. Take your time. Review your code, ask advice for names of functions to other people, +it is always good to have several point of vues. +See also if a correspond lib does not already exists in an other language. +For exemple, if there is a reference API in an other language, you can keep the same names +(convention used e.g. in fgraph, the API is mostly preserved from OcamlGraph) + +Let's say we are playing in a contest of beauty of API. + +### IV. Naming conventions. + +values_look_like_this +ModuleLookLikeThis +TypesInModule.Look.like_this +fields_look_like_this +files_look_like_this.opa + +Accessor functions: + +get_foo +is_foo + +Conversion functions: + +Foo.of_bar +Foo.to_bar + +### IV. Types are closed. + +When you define a new type or data structure, chances are that users don't need +to know how it's implemented, so you should make it @abstract (if users should +see the type but not its definition) or @private (if the type is purely +internal). This will make error messages simpler for the user and it might make +type-checking a tad faster. + +### V. Mutability. + +Mutability is bad. You have no idea how bad it is until you've attempted to +execute mutable code in parallel. Consequently, mutable code should be +avoided. Really. + +In the current versions of Opa, the unit of mutability is the session. If you +need mutability, use a session. If you need a global state shared by the server, +use [UserContext]. If you need something to be saved for future uses, use the +database. Don't write volatile stuff into the database. No sessions, no session +identifiers, etc. + +If you have a use case that fits none of these, and if you are really convinced +that you need a reference, come and see David. He might scream at you, of +course. + +### V. Interacting with the compiler. + +If you define a value which needs to be used by the compiler, there are a few +steps you need to take in order to make sure that it won't be removed +prematurely by a compiler optimization and/or by the persons maintaining the +library. + +For this purpose, you should + +- mark your value as @opacapi +- add it to opacapi/opacapi.ml +- in some cases, add it to the roots +- document how and why it's used in the compiler + +### VI. Checking that it works. + +Compile with the normal procedure (make), and run tests. + +### VII. Bypasses. + +Make what you want with bypasses. Do not export private bypasses. + +### VIII. Fold, map, etc. + +The base loops upon each data structure are + +- fold, foldi +- map, mapi +- iter, iteri +- filter, filteri +- reduce, reducei +- filter_map, filter_mapi + +Wherever possible, data structures should also support constructors + +- unfold, unfoldi +- init +- of_list + +And conversion to external data structures + +- to_list +- to_map + +Unless your data structure doesn't support these constructions, they should all exist, exactly with this name. +If your data structure supports several folds (or several maps, etc), one of them, the default one, must be +called exactly "fold" (respectively "map"). Name the variants with a suffix, which makes the difference explicit +(e.g. "fold" -> "fold_backwards", "iteri" -> "iteri_backwards", etc.) + +The order of arguments is the following, keep it consistent for all data structures: + +function ( iterator, structure, [more-arguments]) : 'result + +- exists: ( f:('a -> bool), structure: t('a) ): bool + +- filter: ( f:'a -> bool, structure: t('a) ): t('a) +- filteri: ( f:int -> 'a -> bool, structure: t('a) ): t('a) +- filter_map: ( f:'a -> option('b), structure: t('a) ): t('b) +- filter_mapi: ( f:int -> 'a -> option('b), structure: t('a) ): t('b) + +- fold: ( f:('item, 'acc) -> 'acc, structure: t('item), init:'acc ): 'acc +- foldi: ( f:(int, 'item, 'acc) -> 'acc, structure: t('item), init:'acc ): 'acc + +- init: ( f:int -> 'item, size: int): t('item) + +- iter: ( f:('a -> void), structure: t('a) ): void +- iteri: ( f:(int, 'a) -> void, structure: t('a) ): void + +- map: ( f:'a -> 'b, structure: t('a) ): t('b) +- mapi: ( f:(int,'a) -> 'b, structure: t('a) ): t('b) + +- mem: ( elt:'a, structure: t('a) ): bool + +- reduce: ( f:'a -> 'a, structure: t('a) ): option('a) //{none} if the structure was empty +- reducei: ( f:int -> 'a -> 'a, structure: t('a) ): option('a) //{none} if the structure was empty + +- unfold: ( f:'counter -> option(('item, 'counter)), init: 'counter ): t('item) +- unfoldi: ( f:(int, 'counter)-> option(('item, 'counter)), init: 'counter): t('item) + +