racket

How to change the current directory and load one file from there in Racket?


I followed this QA to set the current directory. It seems work:

#lang racket
(current-directory)
; #<path:/home/user/foo>
(current-directory 
  (expand-user-path "~/foo/bar_lib")
  )
(current-directory)
; #<path:/home/user/foo/bar_lib>

But then I failed to load one file (This says more about why load should not be used load file normally):

#lang racket
; do the same as the above
(require "foo.rkt")
;; It will still use the old dir
; path: /home/user/foo/foo.rkt

Q1: How to change the current directory and load one file from there in Racket, e.g. load /home/user/foo/bar_lib/foo.rkt in the above?

I didn't use directly (require "bar_lib/foo.rkt") directly here because I can't ensure its relative dir is unchanged. That means I am a bit confusing about the doc saying for rel-string:

A path relative to the containing source (as determined by current-load-relative-directory or current-directory).

I don't know how that is done with those 2 commands. (current-directory) always give the directory where I run racket file.rkt or the value set before by (current-directory target-dir-str). current-load-relative-directory is similar except that its default value is #f.

I tried to check the source codes by keyword "require" but it seems to be implemented by C. Trying to understand that is a bit of overkill to only want to load one lib.

Q2: Does the doc mean rel-string must be based on the path of the file run, e.g. ~/foo/ for ~/foo/main.rkt? If so, then one workaround is found.


Solution

  • It is safe to use relative paths like (require "bar_lib/foo.rkt") for the same reason current-directory doesn't have the effect you expect.

    Expressions within a module definition such as your main.rkt or foo.rkt are not fully evaluated in order like they are when entered one-by-one at a REPL.

    The evaluation of a module form does not evaluate the expressions [as opposed to definitions] in the body of the module.... A module body is executed only when the module is explicitly instantiated via require...

    Because it's a syntactic form, require operates at a higher phase level than ordinary expressions like (current-directory "some-dir"). We can use begin-for-syntax to elevate to require's phase level to see what it would see:

    cd.rkt

    #lang racket
    
    (current-directory "/tmp")
    
    (begin-for-syntax
      (printf "dir = ~a~n" (current-directory)))
    
    $ racket cd.rkt
    dir = /home/user/
    
    $ racket
    > (require "cd.rkt")
    dir = /home/user/
    ; now in /tmp
    dir = /tmp/
    > (current-directory)
    #<path:/tmp/>
    

    This confirms the documentation's statement that module body expressions are executed only when the module is explicitly instantiated. (dir = is printed twice for reasons I won't go into here.)

    So while it would be possible to set the current directory in the same phase as require, it's rarely done in practice, as evidenced by the code in the racket repos. Additionally, it may not have the effect you intend. The current-load-relative-directory parameter supersedes current-directory, and current-load/use-compiled (called by require) is expected to manage current-load-relative-directory, potentially ignoring whatever you might have set it to.