Режим ракетки: Могу ли я оценить одну форму в данном пространстве имен в REPL? - PullRequest
2 голосов
/ 06 апреля 2019

Я работаю в Racket REPL через racket-mode в Emacs, пишу код в нескольких модулях.

Есть ли способ выполнить одну форму из модуля, в котором я сейчас не нахожусь, в контексте его собственного модуля?

Например:

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

Если я позвоню racket-run из web.rkt, я получу подсказку со словами web.rkt>. Если я тогда запускаю (display-default-view), я получаю «Hello world».

Если я тогда зайду view.rkt и изменим определение текста по умолчанию на:

(define default-text "Hi")

и переоцените определение default-text, оно оценивается нормально, и мое приглашение все еще говорит web.rkt>.

Когда я ввожу default-text в REPL, я получаю "Привет". Но когда я запускаю (display-default-view), я все равно получаю "Hello world". Я предполагаю, что это потому, что все, что я сделал, это определил новый default-text в web.rkt.

Я бы ожидал, что изменит выходную информацию на "Привет, мир", то есть будет обновлено поведение модуля view.rkt. Точно так же, как я видел бы, если default-text жил в модуле web.rkt.

Идея динамической переоценки отдельных форм в repl для изменения поведения программы потрясающая, но, похоже, она здесь не совсем работает.

Есть ли способ заставить это вести себя так, как я ожидал бы в режиме ракетки? Или, если нет, механизм, позволяющий просто войти в модуль, не запуская его, чтобы я сам мог что-то построить для танца enter-execute-exit?

1 Ответ

2 голосов
/ 08 апреля 2019

Обновлен, более простой ответ:

Мы можем оценить формы в REPL в пространстве имен текущего файла, введя это пространство имен в REPL, оценив эти формы, а затем повторно введя нашоригинальное пространство имен.Кажется, что проще всего сделать это, обернув эти формы функциями для ввода пространства имен текущего файла (до) и повторного ввода исходного пространства имен (после), а затем отправив все это в существующий код режима Racket для оценки форм вРЕПЛ.

Мы можем сделать это, построив строку наших упакованных команд, записав ее во временный буфер, отметив весь буфер как наш регион, а затем отправив его в 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. Это работало с выпуском 01 апреля 2018 года, но новые версии, по-видимому, реорганизовали некоторые внутренние компоненты, на которые он опирался. Почти во всех случаях предпочтительным является приведенный выше код.)

Извините, я считаю, что изначально неправильно понял вопрос.Похоже, вы имеете в виду выполнение команды прямо из view.rkt без необходимости вручную изменять пространство имен в REPL.Я не видел ни одной встроенной функциональности в режиме ракетки, которая бы это делала, но не так уж сложно написать обертку 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, если это поможет.


Вы можете использовать функцию enter! в модуле racket/enter для переключения пространств имен для изменения определений в пространстве имен другого файла.

После вызова racket-run в web.rkt,Вы можете сделать следующее в 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 для получения более подробной информации о загрузке интерактивного модуля .

...