It should be somewhat straightforward (but still a lot of work) if you manually write stubs for all the functions and resources of the target language. There are lots of approaches, here a naïve one comes to my mind. You would write almost a normal OCaml project, but where relying on target language you would leave the functions unfinished, you put e.g. let foo x y = ignore (x, y); raise (Julia_call "foo"). You would use Ppxlib metaquotation: dune stanza (preprocess (pps ppxlib.metaquot)) in your translator file. You’d have a recursive descent translator pattern matching on the expressions of the OCaml sublanguage, e.g.
Compiling OCaml to Julia most likely is going to be a lot of work. As a short term solution though you can try to compile Julia code to a shared library with GitHub - JuliaLang/PackageCompiler.jl: Compile your Julia Package and then use OCaml C FFI to call it. I have never tried it, so I’m not sure how it will work in practice though.
To make an obvious point: usually the way we deal with interoperability between two source languages is not to compile one into another, but to link their programs together at a lower level (assembly, LLVM, the JVM or dotnet runtime, etc.). Both languages use a “foreign function interface” (FFI) to talk to each other. Most languages come with a good FFI for C, the common system language, and two arbitrary languages often end up talking to each other by going through C as an intermediate layer.
Using an FFI is a lot less work than trying to compile a language into another. Have you tried this first?
There are three common issues with FFI:
You can only exchange values directly at types that both languages understand. Often those are rather low-level types (arrays of numbers), and for everything else you have to write wrappers and this is boring work. This may or may not be an issue depending on the nature of the libraries from the other language you want to expose.
The wrapping/copying of values at the interface boundary can be a performance bottleneck in some cases. (Again, it depends on the data you exchange, and also on the expected number of FFI calls.)
They may break the usual safety guarantees of the language. Using them is risky in this sense. (Giving up on some reasoning guarantees is often unavoidable when you program hybrid systems using different languages.) But then, a compilation layer can also “change” the semantics of programs, potentially introduce bugs, for simplicity or performance reasons.
Thanks! This looks like the type of blackmagic I’m looking for.
I’m familiar with Lisp/Clojure style macros, as well as Rust macro_rules and Rust procedural macros, but new to ppx. What would you suggest I read to learn ppx for this precise purpose ?
I also have some mechanical questions:
“ppx” sounds like a “preprocessor”, i.e. run before. In our case, we want the lexer/parser/typechecker to run on the MODIFIED code, i.e. have our transforms run AFTER lexing/parsing/type-checking. How does this work ?
preprocessor sounds like (1) we read ocaml code (2) we output ocaml code, (3) we then compile the output ocaml code; however in our case, we want to do a transform where (1) we read ocaml code, (2) we output GoLang/JuliaLang, and (3) we stop execution; how does this fit in mechanically ?
Under the hood, rehp is a fork of js_of_ocaml, refactored to make supporting other language backends easier. Most of this project can be merged upstream into js_of_ocaml , aside from the parts that are specific to only one language backend.
To just quickly address (3), I meant to use a preprocessor (the meta-preprocessor) to write the translator, not to write a preprocessor. To address (4), you would use the package ocaml-base-compiler, library ocamlcommon, module Compile_common, function parse_impl, to get the Parsetree.structure (AST). Then you would pass this to your translator function, and the translator function could write Julia code to a text file directly.