Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running code only once in an OCaml library

I am writing an OCaml library that has some initialization code that needs to be run only once during the lifetime of the program using the library (and store some state that will persist for the lifetime of the program, but only be used within the library itself), and some cleanup code that needs to be run only as the program using the library exits.

If it is relevant, my library is two parts: an interface to a low-level C library, and some higher-level stuff to make programming with it easier. Can I do what I need somewhere in the C? Ideally my users wouldn't care how it was implemented, they would never see the C bits.

In Python I would do this by running code on import but OCaml's open doesn't actually run anything, it just sugars the module namespace, and then Python's atexit, but I can't find the Ocaml equivalent.

One approach I have considered is structuring my library as a "framework" but I don't think it's important enough to warrant such an over-engineered approach. Thanks!

UPDATE: OK got it - I think. I am using the C code to handle the cleanup on exit and I have monkeyed with the code a bit so there is a pointer to the global state on the C side

It would appear that in my library I now have

let global_env = env_create ()

And when it is open'd by the main program, this does get run... But how?

like image 240
Gaius Avatar asked Feb 25 '23 07:02

Gaius


2 Answers

Note that this can be done on the OCaml side with Pervasives.at_exit and top-level statements to create the environment and install the cleanup code :

let env = init ()
let cleanup () = do_clean env
let () = at_exit cleanup 

let f x = f_stub env x

The toplevel statements are executed when the module is loaded (whether you eventually use it or not) and modules are loaded in the order you specified them at link time (thus modules depending on others are guaranteed that their dependencies are initialized when their turn comes), see "Arguments ending with .cmo" in the manual of ocamlc. This entails that toplevel statements will have executed before you try to access the module. It's not a matter of opening the module, open is just a (bad) syntactic convenience.

If you want the init code to be performed if and only if a function of the module eventually gets called use a lazy value :

let env = lazy (init ())
let cleanup () = if Lazy.lazy_is_val env then (do_clean env) else () 
let () = at_exit cleanup

let f x = f_stub (Lazy.force env) x

Btw. don't forget to document the resulting issues with thread-safety...

like image 147
Daniel Bünzli Avatar answered Feb 26 '23 21:02

Daniel Bünzli


Just as let x = function ... defines a function x that is available from that point onwards, your let global_env = ... defines a value global_env that is. If you don't need the return value of env_create, because you run it only for it's side effects, you could also just mention env_create () at the end (to be honest, anywhere) of the ml file. In this case I would do let _ = env_create () though, which I think is more explicit.

EDIT: R pointed out that the following is wrong: "To do it purely in C, I think that _init and _fini are the things to look for." As explained in this HOWTO, it is indeed deprecated and should now be done via attributes.

like image 27
subsub Avatar answered Feb 26 '23 20:02

subsub