Tuesday, August 11, 2009

Require Additional Modules

Often we need to require external modules to bring in functionalities in our pages. We would like that to be something like:

;; foo.shp
(require module ...)
`(xexpr goes here)

But since the whole script is evaluated into a procedure, the above won't work as require needs to be a top-level form, and you'll get the following error:

require: not at module level or top level


What we need to do is to parse out the require forms and handle them separately.

;; testing whether an expression is of the form (require ...)
(define (require-exp? term)
(and (pair? term) (equal? (car term) 'require)))

;; add the require modules into the handler-namespace
(define (require-modules! terms)
(define (helper modules)
(parameterize ((current-namespace handler-namespace))
(for-each (lambda (module)
(namespace-require module))
modules)))
(let ((modules (flatten (map cdr (filter require-exp? terms)))))
(helper modules)))

;; abstract the eval process
(define (evaluate-terms terms)
(require-modules! terms) ;; first register the required modules
;; then we filter out the required statement and evaluate the rest of the terms as a proc.
(eval `(lambda (request) . ,(filter (compose not require-exp?) terms))
handler-namespace))

(define (make-shp-handler path
#:default (default "index.shp")
#:not-found (not-found "notfound.shp"))
(lambda (request)
(let ((proc (evaluate-terms
(file->values
(url->shp-path (request-uri request) path default not-found)))))
(parameterize (($pathinfo ($pathinfo)))
(proc request)))))

The shp scripts now evaluates correctly with the require statements lifted out of the procedure body.

Caveats

The above code does not handle the extended require form such as only-in and prefix-in, and I could not find a programatic method to do so from the PLT docs either, so for now the only require statement supported will be a plain vanilla require.

Furthermore, the required modules are not reloadable at this time, so if your required module changes, it will not be refreshed. This is something we can improve in the future.

Also, the require statements, while defined within a single script, has global scopes. This most likely will work out correctly for majority of cases (the only time it could go wrong is when two separate libraries with the same definitions are required in two separate scripts expecting them to have different scopes). If this turns out to be an issue it would be addressed in the future.

Finally, if the required module have side effects, you cannot count on the evaluation order, and it is best that you require all of the modules you need with each of the scripts! This can lead to verbosity quickly, so we'll address on how to "refactor" scripts in the near future.

No comments:

Post a Comment