The purpose is I want to build a set of low-level LLVM bindings like
- llvm 12
- llvm 13
- llvm 14
And provides a high-level package to choose them dynamically(by detecting LLVM version on the system), does that possible with raco
info.rkt?
The purpose is I want to build a set of low-level LLVM bindings like
And provides a high-level package to choose them dynamically(by detecting LLVM version on the system), does that possible with raco
info.rkt?
In general dynamic-require
is the way to do a conditional require.
If you're doing FFI bindings to LLVM then I think you could use the version
argument to ffi-lib
?
You can supply a list of versions, assuming you'll get the same bindings from all versions.
Otherwise I think you could make multiple calls to ffi-lib
, one version at a time, until one succeeds. Then you know which version and can supply certain bindings or not?
Also, here's an example from db-lib
of calling it differently based on operating system.
Caveat: It's been some years since I worked with FFI, and even then it wasn't sophisticated. Other people will probably have better advice!
Note that dynamic-require
still obeys lexical scope so code outside of that scope won't have access to imported bindings. In order to do something like a conditional dynamic-require
, you will probably need to return the bindings in a struct or something (assuming you have multiple elements being dynamically imported).
(struct foobar (foo bar))
(define (load-foobar)
(if some-cond
(let ([foo (dynamic-require 'foobar-impl1 'foo)]
[bar (dynamic-require 'foobar-impl1 'bar)])
(foobar foo bar))
(let ([foo (dynamic-require 'foobar-impl2 'foo)]
[bar (dynamic-require 'foobar-impl2 'bar)])
(foobar foo bar))))
Alternatively, you could have dummy implementations for everything (define foollvm (lambda args (error 'foollvm "function not loaded)))
and then later on call (set! foollvm foollvm-impl)
. As long as the set!
is done in the same module as define
, this should always work. If going this route, I recommend raising an exception if the dummy function is called before initialized rather than using undefined
or #f
so that errors are clear and the code fails fast.
(define foo (lambda args (error 'foo "function not loaded")))
(define bar (lambda args (error 'bar "function not loaded")))
(if some-cond
(let ()
(set! foo (dynamic-require 'foobar-impl1 'foo))
(set! bar (dynamic-require 'foobar-impl1 'bar)))
(let ()
(set! foo (dynamic-require 'foobar-impl2 'foo))
(set! bar (dynamic-require 'foobar-impl2 'bar))))
I agree it's a good idea to think about the scenario where nothing is found.
Note that
dynamic-require
still obeys lexical scope so code outside of that scope won't have access to imported bindings. In order to do something like a conditionaldynamic-require
, you will probably need to return the bindings in a struct or something (assuming you have multiple elements being dynamically imported).
I wouldn't say this is really about lexical scope? dynamic-require
isn't a macro, it's just a function that returns a value. (Usually the value is a procedure value: some procedure from that module.) You can bind that returned value to a variable in all the usual ways.
For example you can just:
(define foo (dynamic-require 'modpath 'foo))
as well as do things like:
(define mods-to-try (list 'mod-path-1 'mod-path-2))
(define foo (or (for/or ([mod (in-list mods-to-try)])
(with-handlers ([exn:fail? (λ _ #f)])
(dynamic-require mod 'foo)))
(λ _ (error 'foo "no implementation found"))))