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-requirestill 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"))))