【问题标题】:Haskell with emacs org-mode: Variable not in scope带有emacs org-mode的Haskell:变量不在范围内
【发布时间】:2019-10-13 09:54:24
【问题描述】:

在与之前的挫折中徘徊之后,我决定再次在 Emacs org-mode 中尝试 Haskell。我正在使用 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 和单个代码块中,但没有效果。此代码在单独的 REPL 中运行的单独 hs 文件中运行良好。单独文件中的pyth2 代码也可以从 org-mode 启动的 REPL 中调用,并且也可以正常运行。不知道如何进行。如有必要,可以包含 Emacs 初始化信息。

【问题讨论】:

  • 我重写了my-org-babel-execute-haskell-blocks 函数。它现在可以正确处理声明中的模式匹配,并且对 cme​​ts 也有一些支持。我不确定这个功能对你有多大用处,但如果你打算使用它,你一定要使用更新版本。

标签: emacs org-mode haskell-stack


【解决方案1】:

这是一个 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 声明该函数没有任何错误。

编辑:我提供的前一个函数无法处理模式匹配声明。我已经重写了函数来解决这个问题并注意评论。这个新功能应该会更有用。但是,值得注意的是,我没有以复杂的方式处理多行 cmets,因此具有多行 cmets 的代码块可能无法正确包装。

(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】:

    在 org-mode 邮件列表中,我得到的答案基本上和你说的一样,D. Gillis。他有一个类似的解决方法,实际上更以组织模式为中心。在您的代码块将放置此“抽屉”的标题下

    :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 不能很好地与现有的 org-mode 启动的 REPL 配合使用,即,如果您从 haskell-mode 启动 REPL,它会杀死原始的 org-mode *haskkell*REPL,以及任何新的尝试运行组织模式代码块看不到这个新的非*haskell*REPL。然后如果你杀死 haskell-mode REPL 并尝试运行 org-mode 块,你会得到 ​​p>

    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 是必要的。谁知道更好的解决方案,请告诉我们。

    【讨论】:

    • 属性抽屉是包装多行声明的好方法。我不知道你能做到这一点。不利的一面是,如果您想使用裸表达式从 REPL 中获取结果(例如 pyth2 10),则必须将其放在自己的源代码块中。如果您将它放在同一源代码块中的 pyth2 声明下方,您将收到一个解析错误,说明它需要一个声明。
    • 没试过,但我相信你是对的。所以是的,如果 ob-haskell 可以在幕后处理这些问题,那就太好了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多