繁体   English   中英

带有emacs org-mode的Haskell:变量不在范围内

[英]Haskell with emacs org-mode: Variable not in scope

在从以前沮丧地徘徊之后,我决定再次尝试在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 init信息。

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

编辑:我给出的上一个函数无法处理模式匹配声明。 我已经重写了修复这个问题的功能以及注释意识。 这个新功能应该更有用。 但是,值得注意的是我没有以复杂的方式处理多行注释,因此具有多行注释的代码块可能无法正确包装。

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

在组织模式邮件列表上,我得到了一个答案,基本上和你说的一样,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与现有组织haskell-mode启动的REPL不兼容,即,如果从haskell-mode启动REPL,它会杀死原始组织模式*haskkell* REPL,以及任何新尝试运行org-mode代码块无法看到这个新的,非*haskell* REPL。 然后,如果你杀死haskell-mode REPL并尝试运行org-mode块,你就得到了

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM