Racket-Mode:我可以在 REPL 的给定名称空间内评估单个表单吗?
Racket-Mode: Can I evaluate a single form within a given namespace at the REPL?
我在 Emacs 中通过 racket-mode
在 Racket REPL 工作,在多个模块中编写代码。
有没有办法在我当前 'in' 所在的模块的上下文中从它自己的模块的上下文中执行单个表单?
例如:
web.rkt
#lang racket
(require "view.rkt")
(define (display-default-view)
(display (default-view)))
view.rkt
#lang racket
(provide default-view)
(define default-text "Hello")
(define (default-view)
(string-append default-text " world"))
如果我从 web.rkt
调用 racket-run
,我会收到一条提示 web.rkt>
。如果我然后 运行 (display-default-view)
我得到 "Hello world".
如果我随后访问 view.rkt
并将默认文本定义更改为:
(define default-text "Hi")
并重新计算 default-text
定义,它的计算结果很好,我的提示仍然是 web.rkt>
。
当我在 REPL 输入 default-text
时,我得到 "Hi"。但是当我 运行 (display-default-view)
我仍然得到 "Hello world"。我假设这是因为我所做的只是在 web.rkt
中定义了一个新的 default-text
。
我 期望 看到输出更改为 "Hi world" --- 即要更新 view.rkt
模块的行为。就像我会看看 default-text
是否住在 web.rkt
模块中一样。
在 repl 中动态重新评估单个表单以改变程序行为的想法很棒,但在这里似乎不太行得通。
有没有办法让它在球拍模式下表现得像我期望的那样?或者,如果没有,一种只进入模块的机制,而不 运行 宁它,这样我就可以自己构建一些东西来进行进入 - 执行 - 退出舞蹈?
更新后的更简单的答案:
我们可以通过在 REPL 中输入该命名空间,评估这些表单,然后重新输入我们的原始命名空间,来评估当前文件命名空间中 REPL 中的表单。最简单的方法似乎是用函数包装这些表单以进入当前文件的命名空间(之前)并重新进入原始命名空间(之后),然后将所有这些发送到现有的 Racket 模式代码中以评估表单REPL。
我们可以通过构建我们包装的命令的字符串,将其写入临时缓冲区,将整个缓冲区标记为我们的区域,然后将其发送到 racket-send-region
。
(defun my-racket-current-namespace-wrapped-commands (buffer-file-string commands)
"generate string containing commands wrapped with Racket functions to enter
the current-namespace and then exit it upon finishing"
(concat "(require (only-in racket/enter enter!))"
"(enter! (file "
buffer-file-string
"))"
commands
"(enter! #f)"))
(defun my-racket--send-wrapped-current-namespace (commands)
"sends wrapped form of commands to racket-send-region function via a temporary buffer"
(let ((buffer-file-string (prin1-to-string buffer-file-name)))
(with-temp-buffer
(insert
(my-racket-current-namespace-wrapped-commands buffer-file-string commands))
(mark-whole-buffer)
(racket-send-region (point-min) (point-max)))))
(defun my-racket-send-region-current-namespace (start end)
"send region to REPL in current namespace"
(interactive "r")
(unless (region-active-p)
(user-error "No region"))
(let ((commands (buffer-substring (region-beginning) (region-end))))
(my-racket--send-wrapped-current-namespace commands)))
(defun my-racket-send-last-sexp-current-namespace ()
"send last sexp to REPL in current namespace"
(interactive)
(let ((commands (buffer-substring (my-racket--repl-last-sexp-start)
(point))))
(my-racket--send-wrapped-current-namespace commands)))
(defun my-racket--repl-last-sexp-start ()
"get start point of last-sexp
permanent (and slightly simplified) copy of racket mode's last-sexp-start private function"
(save-excursion
(progn
(backward-sexp)
(if (save-match-data (looking-at "#;"))
(+ (point) 2)
(point)))))
这些函数应该主要与版本无关——它们只依赖于 racket-send-buffer
(这似乎可能保留在未来的版本中)。
编辑 1:(注意 - 对于较新版本的 Racket-mode,这似乎不起作用。这在 2018 年 4 月 1 日发布时有效,但较新版本似乎已经重构这依赖于一些内部结构。在几乎所有情况下,上面的代码都是可取的。)
对不起,我相信我最初误解了这个问题。看起来你的意思是直接从 view.rkt 执行命令,而不必手动更改 REPL 中的名称空间。我没有在 racket-mode 中看到任何内置功能可以做到这一点,但是围绕这个过程编写一个 Elisp 包装器并不太难。 enter!
中的以下导入,切换到当前缓冲区的文件的命名空间,发送区域中的代码,然后切换回原始命名空间。使用的代码与球拍模式用于 racket-send-region
和 racket-send-last-sexp
.
的代码非常相似
(defun my-racket-send-region-current-namespace (start end)
"Send the current region to the Racket REPL as that namespace"
(interactive "r")
(when (and start end)
(racket-repl t)
(racket--repl-forget-errors)
(let ((proc (racket--get-repl-buffer-process)))
(with-racket-repl-buffer
(save-excursion
(goto-char (process-mark proc))
(insert ?\n)
(set-marker (process-mark proc) (point))))
(comint-send-string proc "(require (only-in racket/enter enter!))")
(comint-send-string proc
(concat "(enter! (file "
(prin1-to-string buffer-file-name)
"))"))
(comint-send-string proc "\n"))
(racket--repl-show-and-move-to-end)
(racket--send-region-to-repl start end)
(let ((proc (racket--get-repl-buffer-process)))
(with-racket-repl-buffer
(save-excursion
(goto-char (process-mark proc))
(insert ?\n)
(set-marker (process-mark proc) (point))))
(comint-send-string proc "(enter! #f)")
(comint-send-string proc "\n"))))
(defun my-racket-send-last-sexp-current-namespace ()
(interactive)
(my-racket-send-region-current-namespace
(save-excursion
(backward-sexp)
(if (save-match-data (looking-at "#;"))
(+ (point) 2)
(point)))
(point)))
请注意,如果您经常使用此功能,则此功能可能会使用更多的错误检查(例如,require/enter
的导入将破坏任何先前对 enter!
的定义)。
我还保留了下面关于如何在 REPL 中手动切换命名空间的原文,以防有帮助。
您可以使用racket/enter
模块中的函数enter!
切换命名空间来修改其他文件命名空间中的定义。
在 web.rkt 中调用 racket-run
后,您可以在 REPL 中执行以下操作:
(display-default-view) ;; output is "Hello world"
(require racket/enter)
(enter! "view.rkt") ;; change namespace to view.rkt
(define default-text "Hi")
(enter! #f) ;; return to original namespace
(display-default-view) ;; output is "Hi world"
参见Racket documentation for more details on interactive module loading。
我在 Emacs 中通过 racket-mode
在 Racket REPL 工作,在多个模块中编写代码。
有没有办法在我当前 'in' 所在的模块的上下文中从它自己的模块的上下文中执行单个表单?
例如:
web.rkt
#lang racket
(require "view.rkt")
(define (display-default-view)
(display (default-view)))
view.rkt
#lang racket
(provide default-view)
(define default-text "Hello")
(define (default-view)
(string-append default-text " world"))
如果我从 web.rkt
调用 racket-run
,我会收到一条提示 web.rkt>
。如果我然后 运行 (display-default-view)
我得到 "Hello world".
如果我随后访问 view.rkt
并将默认文本定义更改为:
(define default-text "Hi")
并重新计算 default-text
定义,它的计算结果很好,我的提示仍然是 web.rkt>
。
当我在 REPL 输入 default-text
时,我得到 "Hi"。但是当我 运行 (display-default-view)
我仍然得到 "Hello world"。我假设这是因为我所做的只是在 web.rkt
中定义了一个新的 default-text
。
我 期望 看到输出更改为 "Hi world" --- 即要更新 view.rkt
模块的行为。就像我会看看 default-text
是否住在 web.rkt
模块中一样。
在 repl 中动态重新评估单个表单以改变程序行为的想法很棒,但在这里似乎不太行得通。
有没有办法让它在球拍模式下表现得像我期望的那样?或者,如果没有,一种只进入模块的机制,而不 运行 宁它,这样我就可以自己构建一些东西来进行进入 - 执行 - 退出舞蹈?
更新后的更简单的答案:
我们可以通过在 REPL 中输入该命名空间,评估这些表单,然后重新输入我们的原始命名空间,来评估当前文件命名空间中 REPL 中的表单。最简单的方法似乎是用函数包装这些表单以进入当前文件的命名空间(之前)并重新进入原始命名空间(之后),然后将所有这些发送到现有的 Racket 模式代码中以评估表单REPL。
我们可以通过构建我们包装的命令的字符串,将其写入临时缓冲区,将整个缓冲区标记为我们的区域,然后将其发送到 racket-send-region
。
(defun my-racket-current-namespace-wrapped-commands (buffer-file-string commands)
"generate string containing commands wrapped with Racket functions to enter
the current-namespace and then exit it upon finishing"
(concat "(require (only-in racket/enter enter!))"
"(enter! (file "
buffer-file-string
"))"
commands
"(enter! #f)"))
(defun my-racket--send-wrapped-current-namespace (commands)
"sends wrapped form of commands to racket-send-region function via a temporary buffer"
(let ((buffer-file-string (prin1-to-string buffer-file-name)))
(with-temp-buffer
(insert
(my-racket-current-namespace-wrapped-commands buffer-file-string commands))
(mark-whole-buffer)
(racket-send-region (point-min) (point-max)))))
(defun my-racket-send-region-current-namespace (start end)
"send region to REPL in current namespace"
(interactive "r")
(unless (region-active-p)
(user-error "No region"))
(let ((commands (buffer-substring (region-beginning) (region-end))))
(my-racket--send-wrapped-current-namespace commands)))
(defun my-racket-send-last-sexp-current-namespace ()
"send last sexp to REPL in current namespace"
(interactive)
(let ((commands (buffer-substring (my-racket--repl-last-sexp-start)
(point))))
(my-racket--send-wrapped-current-namespace commands)))
(defun my-racket--repl-last-sexp-start ()
"get start point of last-sexp
permanent (and slightly simplified) copy of racket mode's last-sexp-start private function"
(save-excursion
(progn
(backward-sexp)
(if (save-match-data (looking-at "#;"))
(+ (point) 2)
(point)))))
这些函数应该主要与版本无关——它们只依赖于 racket-send-buffer
(这似乎可能保留在未来的版本中)。
编辑 1:(注意 - 对于较新版本的 Racket-mode,这似乎不起作用。这在 2018 年 4 月 1 日发布时有效,但较新版本似乎已经重构这依赖于一些内部结构。在几乎所有情况下,上面的代码都是可取的。)
对不起,我相信我最初误解了这个问题。看起来你的意思是直接从 view.rkt 执行命令,而不必手动更改 REPL 中的名称空间。我没有在 racket-mode 中看到任何内置功能可以做到这一点,但是围绕这个过程编写一个 Elisp 包装器并不太难。 enter!
中的以下导入,切换到当前缓冲区的文件的命名空间,发送区域中的代码,然后切换回原始命名空间。使用的代码与球拍模式用于 racket-send-region
和 racket-send-last-sexp
.
(defun my-racket-send-region-current-namespace (start end)
"Send the current region to the Racket REPL as that namespace"
(interactive "r")
(when (and start end)
(racket-repl t)
(racket--repl-forget-errors)
(let ((proc (racket--get-repl-buffer-process)))
(with-racket-repl-buffer
(save-excursion
(goto-char (process-mark proc))
(insert ?\n)
(set-marker (process-mark proc) (point))))
(comint-send-string proc "(require (only-in racket/enter enter!))")
(comint-send-string proc
(concat "(enter! (file "
(prin1-to-string buffer-file-name)
"))"))
(comint-send-string proc "\n"))
(racket--repl-show-and-move-to-end)
(racket--send-region-to-repl start end)
(let ((proc (racket--get-repl-buffer-process)))
(with-racket-repl-buffer
(save-excursion
(goto-char (process-mark proc))
(insert ?\n)
(set-marker (process-mark proc) (point))))
(comint-send-string proc "(enter! #f)")
(comint-send-string proc "\n"))))
(defun my-racket-send-last-sexp-current-namespace ()
(interactive)
(my-racket-send-region-current-namespace
(save-excursion
(backward-sexp)
(if (save-match-data (looking-at "#;"))
(+ (point) 2)
(point)))
(point)))
请注意,如果您经常使用此功能,则此功能可能会使用更多的错误检查(例如,require/enter
的导入将破坏任何先前对 enter!
的定义)。
我还保留了下面关于如何在 REPL 中手动切换命名空间的原文,以防有帮助。
您可以使用racket/enter
模块中的函数enter!
切换命名空间来修改其他文件命名空间中的定义。
在 web.rkt 中调用 racket-run
后,您可以在 REPL 中执行以下操作:
(display-default-view) ;; output is "Hello world"
(require racket/enter)
(enter! "view.rkt") ;; change namespace to view.rkt
(define default-text "Hi")
(enter! #f) ;; return to original namespace
(display-default-view) ;; output is "Hi world"
参见Racket documentation for more details on interactive module loading。