Haskell с emacs org-mode: переменная не находится в области видимости - PullRequest
3 голосов
/ 28 мая 2019

После разочарования, я решил снова попробовать Haskell в Egcs-режиме Emacs.Я использую Haskell stack-ghci (8.6.3), Emacs 26.2, org-mode 9.2.3, настроенный с intero.Этот кодовый блок

#+begin_src haskell :results raw :session *haskell*
pyth2 :: Int -> [(Int, Int, Int)]
pyth2 n =
  [ (x, y, z)
  | x <- [1 .. n]
  , y <- [x .. n]
  , z <- [y .. n]
  , x ^ 2 + y ^ 2 == z ^ 2
  ]
#+end_src

дает следующие РЕЗУЛЬТАТЫ:

*Main| *Main| *Main| *Main| *Main| 
<interactive>:59:16: error: Variable not in scope: n
<interactive>:60:16: error: Variable not in scope: n
<interactive>:61:16: error: Variable not in scope: n

Однако, этот

#+begin_src haskell :results raw
tripleMe x = x + x + x
#+end_src

работает нормально.Я добавил :set +m к ghci.conf и к отдельному блоку кода безрезультатно.Этот код отлично работает в отдельном файле hs, запущенном в отдельном REPL.Код pyth2 в отдельном файле также может быть вызван из REPL, запущенного в режиме org, и прекрасно работает.Не уверен, как поступить.При необходимости может включать информацию об инициализации Emacs.

Ответы [ 2 ]

3 голосов
/ 30 мая 2019

Это проблема GHCi.

Та же ошибка возникает, когда ваш код копируется непосредственно в GHCi, что также дает ошибку разбора, когда он встречает новую строку после знака равенства.Эта первая ошибка не отображается здесь, потому что org-babel показывает только значение последнего выражения (в этом случае ошибка, вызванная пониманием списка).

Я не совсем знаком с тем, как режим Haskell отправляет код в GHCi, но похоже, что он включает загрузку в буфер в GHCi в виде файла, что может быть причиной того, что у вас не было этой проблемыработа с файлом hs.

Существует несколько вариантов исправления, ни один из которых не является абсолютно идеальным:

  1. Переместить некоторую часть списка в первую строку (Например, первая строка может быть pyth2 n = [).
  2. Обернуть все определение функции с помощью :{ и :}.
  3. Написать функцию Elisp, чтобы изменить то, что отправляется в GHCi, изатем изменяет его после оценки.

Первые два варианта требуют, чтобы вы отформатировали код в форме, которую GHCi примет.В вашем примере, первый вариант может быть не слишком плохим, но это не всегда будет так тривиально для всех многострочных объявлений (например, объявлений функций сопоставления с образцом).Недостатком второго варианта является то, что он требует добавления скобок к коду, которого не должно быть в реальном исходном коде.

Чтобы решить проблему добавления посторонних скобок, я написал команду Elisp (my-org-babel-execute-haskell-blocks), который помещает эти скобки вокруг блоков кода, которые он находит, оценивает область и затем удаляет скобки.Обратите внимание, что эта функция требует, чтобы блоки были отделены от всего остального кода хотя бы одной пустой строкой.

Вызов my-org-babel-execute-haskell-blocks в вашем примере объявляет функцию без ошибок.

EDIT: Предыдущая функция, которую я дал, не работала с объявлениями сопоставления с образцом.Я переписал функцию, чтобы исправить эту проблему, а также чтобы быть в курсе комментариев.Эта новая функция должна быть значительно более полезной.Однако стоит отметить, что я не обрабатывал многострочные комментарии сложным образом, поэтому блоки кода с многострочными комментариями могут быть неправильно упакованы.

(defun my-org-babel-execute-haskell-blocks ()
  "Wraps :{ and :} around all multi-line blocks and then evaluates the source block.
Multi-line blocks are those where all non-indented, non-comment lines are declarations using the same token."
  (interactive)
  (save-excursion
    ;; jump to top of source block
    (my-org-jump-to-top-of-block)
    (forward-line)
    ;; get valid blocks
    (let ((valid-block-start-ends (seq-filter #'my-haskell-block-valid-p (my-get-babel-blocks))))
      (mapcar #'my-insert-haskell-braces valid-block-start-ends)
      (org-babel-execute-src-block)
      (mapcar #'my-delete-inserted-haskell-braces (reverse valid-block-start-ends)))))


(defun my-get-blocks-until (until-string)
  (let ((block-start nil)
        (block-list nil))
    (while (not (looking-at until-string))
      (if (looking-at "[[:space:]]*\n")
          (when (not (null block-start))
            (setq block-list (cons (cons block-start (- (point) 1))
                                   block-list)
                  block-start nil))
        (when (null block-start)
          (setq block-start (point))))
      (forward-line))
    (when (not (null block-start))
      (setq block-list (cons (cons block-start (- (point) 1))
                             block-list)))))

(defun my-get-babel-blocks ()
  (my-get-blocks-until "#\\+end_src"))

(defun my-org-jump-to-top-of-block ()
  (forward-line)
  (org-previous-block 1))

(defun my-empty-line-p ()
  (beginning-of-line)
  (= (char-after) 10))

(defun my-haskell-type-declaration-line-p ()
  (beginning-of-line)
  (and (not (looking-at "--"))
       (looking-at "^.*::.*$")))

(defun my-insert-haskell-braces (block-start-end)
  (let ((block-start (car block-start-end))
        (block-end (cdr block-start-end)))
    (goto-char block-end)
    (insert "\n:}")
    (goto-char block-start)
    (insert ":{\n")))


(defun my-delete-inserted-haskell-braces (block-start-end)
  (let ((block-start (car block-start-end))
        (block-end (cdr block-start-end)))
    (goto-char block-start)
    (delete-char 3)
    (goto-char block-end)
    (delete-char 3)))


(defun my-get-first-haskell-token ()
  "Gets all consecutive non-whitespace text until first whitespace"
  (save-excursion
    (beginning-of-line)
    (let ((starting-point (point)))
      (re-search-forward ".*?[[:blank:]\n]")
      (goto-char (- (point) 1))
      (buffer-substring-no-properties starting-point (point)))))


(defun my-haskell-declaration-line-p ()
  (beginning-of-line)
  (or (looking-at "^.*=.*$")  ;; has equals sign
      (looking-at "^.*\n[[:blank:]]*|")
      (looking-at "^.*where[[:blank:]]*$")))


(defun my-haskell-block-valid-p (block-start-end)
  (let ((block-start (car block-start-end))
        (block-end (cdr block-start-end))
        (line-count 0))
        (save-excursion
          (goto-char block-start)
          (let ((token 'nil)
                (is-valid t))
            ;; eat top comments
            (while (or (looking-at "--")
                       (looking-at "{-"))
              (forward-line))
            (when (my-haskell-type-declaration-line-p)
              (progn
                (setq token (my-get-first-haskell-token)
                      line-count 1)
                (forward-line)))
            (while (<= (point) block-end)
              (let ((current-token (my-get-first-haskell-token)))
                (cond ((string= current-token "") ; line with indentation
                       (when (null token) (setq is-valid nil))
                       (setq line-count (+ 1 line-count)))
                      ((or (string= (substring current-token 0 2) "--") ;; skip comments
                           (string= (substring current-token 0 2) "{-"))
                       '())
                      ((and (my-haskell-declaration-line-p)
                            (or (null token) (string= token current-token)))
                       (setq token current-token
                             line-count (+ 1 line-count)))
                      (t (setq is-valid nil)
                         (goto-char (+ 1 block-end))))
                (forward-line)))
            (and is-valid (> line-count 1))))))
2 голосов
/ 31 мая 2019

В списке рассылки в режиме org я получил ответ, который в основном говорит то же, что и вы, Д. Гиллис.У него был похожий обходной путь, который на самом деле более ориентирован на организацию.Под заголовком, где ваши блоки кода будут помещены в этот «ящик»

:PROPERTIES:
:header-args:haskell: :prologue ":{\n" :epilogue ":}\n"
:END:

, а затем (возможно, в локальной переменной) запустите

#+begin_src haskell :results output
:set prompt-cont ""
#+end_src

По неизвестным причинам мне пришлосьвключите :results output, в противном случае происходит загадочная ошибка "ожидание строки".

В некоторых других заметках, haskell babel не отвечает / не заботится о опции :session, т.е. когда вы запускаетеблок кода, начинается REPL *haskell*, и это будет единственный REPL.Кроме того, haskell-mode запущенный REPL плохо работает с существующим REPL, инициированным в режиме org, то есть, если вы запускаете REPL с haskell-mode, он убивает исходный *haskkell* REPL режима org и любую новую попытку.чтобы запустить кодовые блоки режима орг не могут видеть этот новый, не *haskell* REPL.Затем, если вы убьете haskell-mode REPL и попытаетесь запустить блоки в режиме org, вы получите

executing Haskell code block...
inferior-haskell-start-process: List contains a loop: ("--no-build" "--no-load" "--ghci-options=-ferror-spans" "--no-build" "--no-load" . #2)

... у вас все в порядке - и, кажется, ничто не встряхивает его, ни перезапуск / обновлениеили уничтожение, перезагрузка файла, т. е. необходим полный перезапуск Emacs.Если вы знаете лучшее решение, пожалуйста, сообщите нам.

...