Hi all, I am a newcomer to ocaml and want to create a scriptable program.
What do I mean by scriptable? The program will source file in which the user can define configurations and some functions.
So when the program is invoked, it will call some of these functions and execute them based on the configuration, and the result will be used to modify behavior
How can I achieve this in ocaml? I am fine with using Lua, scheme, or any language for scripting. I am fine with using ocaml as well, as long as the user experience can be as simple as writing their functions in the config file.
It looks like dynlink is an option to use ocaml here. Are there any guides or examples on how to achieve what I want?
It is definitely possible to use OCaml as a configuration language, but it will require at least part of the OCaml toolchain to be present in your user’s machine, so that the configuration scripts written by your users can be executed and/or compiled. This may or may not be a deal-breaker for you (you need to make sure that the version used to build your program and the one in your user’s machine matches exactly). The requirements for native-code are more onerous than for bytecode as native-code compiler also uses the system assembler and linker. The requirements for bytecode are correspondingly simpler (no system tooling is required, just ocaml or ocamlc and the standard library).
If you decide to go this way, there are at least two different approaches, depending on whether you use dynlink or not. The difference is that when using dynlink you are literally linking your users’s configuration script into your applicative code, which has a number of security and safety implications.
In both cases you would write a “configuration API” (the library of configuration functions that your users are supposed to use in their configuration scripts). When using dynlink, the functions in the library would directly modify the program state. When not using dynlink, the library should produce as a side-effect some artifact (eg a text file in some format) that can then be read by your program to configure itself accordingly.
With dynlink: you compile the configuration script against the configuration API and then call Dynlink to load the resulting .cma (bytecode) or .cmxs (native-code). Upon loading, the plugin will modify your application state.
Without dynlink: you compile the configuration script against the configuration API and produce an executable, which you execute to produce a configuration artifact as a side-effect (eg the text file alluded above), which your program then reads to configure itself accordingly.
An variant of this approach is to skip the compilation step and simply use the bytecode interpreter ocaml to execute the script. This is the approach used by Dune for its “OCaml syntax”, where users can write build instructions using OCaml.
Finally, note that this approach is completely generic: it can be extended to any language at all for which you can provide a configuration API. You could write such a library in Python; then your users would write their configuration as a Python script, which your program would execute and which would produce a configuration artifact as a side-effect, which your program would then read to configure itself, etc…
Another approach is to include an interpreter for some language in your program code (eg pyml or ocaml-lua as mentioned already, there’s also ocs 1.0.3 · OCaml Package for Scheme) and use it to evaluate your users’ configuration scripts inside your program. Note that one can also use this approach with OCaml itself when using bytecode, by linking against compiler-libs which can be used to evaluate OCaml code; in that case you would not require a separate toolchain in your user’s machine (but you will need to somehow bundle the needed .cmi files for the standard library and the configuration API with your program).