在 Racket 中动态加载模块

Flo*_*ian 3 racket

对于一个项目,我正在创建一个类driver%,它应该是提供相同过程的不同模块的抽象层。该类将使用指定要使用的模块的参数进行初始化。

此外,我想driver%公开相同的过程但没有副作用,以方便使用驱动程序的单元测试类。

考虑以下:

module_one.rkt
#lang racket
(provide foo)
(define (foo)
  (display "called from "module one"))
Run Code Online (Sandbox Code Playgroud) module_two.rkt
#lang racket
(provide foo)
(define (foo)
  (display "called from "module two"))
Run Code Online (Sandbox Code Playgroud) driver.rkt
#lang racket
(require (prefix-in one: "module_one.rkt")
         (prefix-in two: "module_two.rkt"))

(provide driver%)

(define driver%
  (class object%
         (super-new)
         (init driver-choice)
         (define choice driver-choice)
         (define/public (foo)
           (case choice
             [(1) (one:foo)]
             [(2) (two:foo)]
             [else void]))))       
Run Code Online (Sandbox Code Playgroud)

这满足了上述要求,但不是很优雅:对于每个公开的过程,必须添加另一个 case 表达式。这似乎没有必要,因为 API 的选择是在类实例化时做出的,所以选择在任何地方都是相同的。

对于这个问题,什么是更可接受的解决方案?我已经研究过使用local-require,但这似乎不起作用define/public

非常感谢!

Sor*_*ase 5

您可以使用dynamic-require动态要求模块。您还可以定义宏来减少重复代码。

但首先请注意,可以class完全避免:

;; module-one.rkt
#lang racket
(provide foo bar)
(define (foo) (displayln "called foo from module-one"))
(define (bar) (displayln "called bar from module-one"))
Run Code Online (Sandbox Code Playgroud)
;; module-two.rkt
#lang racket
(provide foo bar)
(define (foo) (displayln "called foo from module-two"))
(define (bar) (displayln "called bar from module-two"))
Run Code Online (Sandbox Code Playgroud)
;; driver.rkt
#lang racket

(define ((make-driver choice) method-name)
  (case choice
    [(1) ((dynamic-require "module-one.rkt" method-name))]
    [(2) ((dynamic-require "module-two.rkt" method-name))]
    [else (void)]))

(define a-driver (make-driver 1))
(a-driver 'foo)
(a-driver 'bar)

(define b-driver (make-driver 2))
(b-driver 'foo)
(b-driver 'bar)
Run Code Online (Sandbox Code Playgroud)

输出:

called foo from module-one
called bar from module-one
called foo from module-two
called bar from module-two
Run Code Online (Sandbox Code Playgroud)

如果您确实想使用class,这里有一种可能性:

;; driver.rkt
#lang racket

(require syntax/parse/define)

(define-simple-macro (driver 
                       #:modules ([mod-id mod-path] ...)
                       #:methods (methods ...))
  (class object% (super-new)
    (init-field driver-choice)
    (begin
      (define/public (methods)
        (define method-name 'methods)
        (case driver-choice
          [(mod-id) ((dynamic-require mod-path method-name))]
          ...
          [else (void)])) ...)))

(define driver%
  (driver
   #:modules ([1 "module-one.rkt"] [2 "module-two.rkt"])
   #:methods (foo bar)))

(define a-driver (new driver% [driver-choice 1]))
(send a-driver foo)
(send a-driver bar)

(define b-driver (new driver% [driver-choice 2]))
(send b-driver foo)
(send b-driver bar)
Run Code Online (Sandbox Code Playgroud)

输出:

called foo from module-one
called bar from module-one
called foo from module-two
called bar from module-two
Run Code Online (Sandbox Code Playgroud)