简体   繁体   English

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

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

After wandering off in frustration from before, I've decided to try Haskell in Emacs org-mode again. 在从以前沮丧地徘徊之后,我决定再次尝试在Emacs org-mode中使用Haskell。 I'm using Haskell stack-ghci (8.6.3), Emacs 26.2, org-mode 9.2.3 set up with intero . 我正在使用Haskell stack-ghci (8.6.3),Emacs 26.2,org-mode 9.2.3设置intero This code block 这个代码块

#+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

produces this RESULTS: 产生这样的结果:

*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

However, this 但是,这个

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

works fine. 工作良好。 I've added the :set +m to both ghci.conf and the individual code block to no effect. 我已经将:set +m添加到ghci.conf和单个代码块中无效。 This code works fine in a separate hs file run in a separate REPL. 此代码在单独的REPL中运行的单独hs文件中正常工作。 The pyth2 code in a separate file also can be called from the org-mode started REPL and run just fine as well. 单独文件中的pyth2代码也可以从org-mode启动的REPL中调用,也可以正常运行。 Not sure how to proceed. 不知道如何继续。 Can include Emacs init info if necessary. 如有必要,可以包含Emacs init信息。

This is a GHCi issue. 这是一个GHCi问题。

The same error occurs when your code is copied directly into GHCi, which also gives a parse error when it encounters the new line after the equal sign. 当您的代码直接复制到GHCi时会发生同样的错误,这也会在等号后遇到新行时产生解析错误。 This first error isn't showing up here because org-babel only shows the value of the last expression (in this case, the error caused by the list comprehension). 这个第一个错误没有显示在这里,因为org-babel只显示最后一个表达式的值(在这种情况下,是由列表推导引起的错误)。

I'm not entirely familiar with how Haskell-mode sends the code to GHCi, but it looks like it involves loading in the buffer into GHCi as a file, which may be why you didn't have this problem working from the hs file. 我并不完全熟悉Haskell模式如何将代码发送到GHCi,但看起来它涉及将缓冲区加载到GHCi中作为文件,这可能就是为什么你没有从hs文件中解决这个问题。

There are a few options to fix this, none of which are completely ideal: 有几个选项可以解决这个问题,其中没有一个是完全理想的:

  1. Move some portion of the list into the first line (eg the first line could be pyth2 n = [ ). 将列表的某些部分移动到第一行(例如,第一行可以是pyth2 n = [ )。
  2. Wrap the entire function definition with :{ and :} . :{:}包装整个函数定义。
  3. Write an Elisp function to modify what is being sent to GHCi and then changes it back after it is evaluated. 编写一个Elisp函数来修改发送给GHCi的内容,然后在评估后将其更改回来。

The first two options require you to format your code in a form that the GHCi will accept. 前两个选项要求您以GHCi将接受的形式格式化您的代码。 In your example case, the first option may not be too bad, but this won't always be so trivial for all multi-line declarations (eg pattern-matching function declarations). 在您的示例中,第一个选项可能不会太糟糕,但对于所有多行声明(例如模式匹配函数声明),这并不总是那么简单。 The downside to the second option is that it requires adding brackets to the code that shouldn't be there in real source code. 第二个选项的缺点是它需要在代码中添加括号,这些代码不应该存在于实际源代码中。

To fix the issue of extraneous brackets being added, I've written an Elisp command ( my-org-babel-execute-haskell-blocks ) that places these brackets around code blocks that it finds, evaluates the region, and then deletes the brackets. 为了解决添加无关括号的问题,我编写了一个Elisp命令( my-org-babel-execute-haskell-blocks ),将这些括号放在它找到的代码块周围,评估区域,然后删除括号。 Note that this function requires that blocks be separated from all other code with at least one empty line. 请注意,此函数要求使用至少一个空行将块与所有其他代码分开。

Calling my-org-babel-execute-haskell-blocks on your example declares the function without any errors. 在您的示例上调用my-org-babel-execute-haskell-blocks声明该函数没有任何错误。

EDIT: The previous function I gave failed to work on pattern matching declarations. 编辑:我给出的上一个函数无法处理模式匹配声明。 I've rewritten the function to fix this issue as well as to be comment aware. 我已经重写了修复这个问题的功能以及注释意识。 This new function should be significantly more useful. 这个新功能应该更有用。 However, it's worth noting that I didn't handle multi-line comments in a sophisticated manner, so code blocks with multi-line comments may not be wrapped properly. 但是,值得注意的是我没有以复杂的方式处理多行注释,因此具有多行注释的代码块可能无法正确包装。

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

Over on the org-mode mailing list I got an answer that basically is saying the same as you, D. Gillis. 在组织模式邮件列表上,我得到了一个答案,基本上和你说的一样,D。Gillis。 He had a similar work-around that actually is more org-mode-centric. 他有一个类似的解决方案,实际上更多以组织模式为中心。 Under a heading where your code blocks will be put this "drawer" 在你的代码块放在这个“抽屉”的标题下

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

and then (possibly in a local variable) run 然后(可能在局部变量中)运行

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

For reasons unknown I've had to include the :results output otherwise a cryptic error of "expecting a string" happens. 由于未知的原因,我必须包括:results output否则会出现“期待字符串”的神秘错误。

On a few other notes, haskell babel doesn't respond/care about the :session option, ie, when you run a code block, a REPL *haskell* starts and that will be the sole REPL. 在其他几个注释中,haskell babel不响应/关心:session选项,即当你运行代码块时,REPL *haskell*启动,这将是唯一的REPL。 Also, a haskell-mode started REPL doesn't play well with an existing org-mode initiated REPL, ie, if you start a REPL from haskell-mode , it kills the original org-mode *haskkell* REPL, and any new attempt to run org-mode code blocks can't see this new, non- *haskell* REPL. 此外, haskell-mode启动REPL与现有组织haskell-mode启动的REPL不兼容,即,如果从haskell-mode启动REPL,它会杀死原始组织模式*haskkell* REPL,以及任何新尝试运行org-mode代码块无法看到这个新的,非*haskell* REPL。 Then if you kill the haskell-mode REPL and try to run org-mode blocks, you get 然后,如果你杀死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)

... you're hosed -- and nothing seems to shake it, not any restart/refresh, nor killing, reloading the file, ie, a complete restart of Emacs is necessary. ...你被软管 - 似乎没有动摇它,没有任何重启/刷新,也没有杀死,重新加载文件,即完全重启Emacs是必要的。 Anyone knowing a better solution, please tells usses. 任何人都知道更好的解决方案,请告诉我们。

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

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