Julia, Julia!

我最近几个月来使用 Julia 替代 Python ,切实感受到了它的优缺点。

优点包括语法美观易用、性能更高、自带的 REPL 和包管理。

缺点是生态不完善,包括:许多数据服务商只提供 Python 版本的 SDK ;周边工具没 Python 的多、好用;许多包数年不更新,缺少维护。

比如 Lsp ,目前成熟的只有 LanguageServer.jl ,但它启动速度慢,占用内存高,而 JETLS.jl 仍处于早期开发阶段,功能不完善,无法与 Python 相比(其有 Pyright 、 ty 等)。为了提高启动速度, Julia 提供 JulicC 和 sysimage ,但编译过程非常吃机器性能与内存,且耗费时间,我的一个 2C4G 的机器总是在这个过程中卡死。另一个需要编译的过程是首次下载或更新 package 时,对于一些大型包,小机器同样吃不消。

于是为了在 emacs 中为 Julia 提供 lsp ,我需要在配置中添加如下代码1

(defconst my/julia-lsp-script-content
  "import Pkg;
   try
       using LanguageServer
   catch
       Pkg.add(\"LanguageServer\")
       using LanguageServer
   end;
   project_path = pwd();
   depot_path = get(ENV, \"JULIA_DEPOT_PATH\", \"\");
   symbol_server_path = joinpath(homedir(), \".cache\", \"emacs\", \"julia_lsp_symbol_store\");
   mkpath(symbol_server_path);
   server = LanguageServer.LanguageServerInstance(stdin, stdout, project_path, depot_path, nothing, symbol_server_path, true)
   server.runlinter = true;
   run(server);"
  "The startup script for Julia LanguageServer.")
(add-to-list 'eglot-server-programs
 `(julia-mode . ("julia" "--project=@eglot" "--startup-file=no" "--history-file=no" "--quiet" "--depwarn=no" "-e"
                             ,my/julia-lsp-script-content)))

现在许多语言都支持了 tree-sitter , emacs 在这方面有一个社区支持版本 julia-ts-mode ,目前已经一年没有更新。只能使用 julia-mode

emacs 有许多包(julia-repljulia-vterm)为 Julia 提供了与 REPL 的交互,比如打开 REPL 并激活当前 .jl 文件的 Project ,发送当前行或当前区域的代码到 REPL ,执行整个 .jl 文件等。我使用 vterm 作为后端,但总是无法继承 dirnev 与 envrc 激活的环境变量。为此我定义了几个函数替代这些包,实现与 REPL 的交互:

(use-package julia-mode
 :ensure t
 :mode ("\\.jl\\'" . julia-mode)
 :hook ((julia-mode . corfu-mode)
         (julia-mode . eglot-ensure))
 :bind (:map julia-mode-map
         ("C-c C-z" . mj/julia-create-repl)
         ("C-c C-c" . mj/julia-send-region-or-line)
         ("C-c C-b" . mj/julia-send-buffer)
         ("C-c C-f" . mj/runic-format-buffer))
 :config
 (defun mj/runic-format-buffer ()
   (interactive)
   (let ((p (point)))
     (shell-command-on-region
      (point-min) (point-max)
      "runic --stdin-filename=-"
      (current-buffer) t)
     (goto-char p)))

 (defun mj/julia-create-repl ()
   (interactive)
   (let* ((buf-name "*julia*")
          (julia-buf (get-buffer buf-name)))
     (if julia-buf
         (pop-to-buffer julia-buf)
        (save-selected-window
       (let ((target-win (or (window-in-direction 'right)
                             (window-in-direction 'left)
                             (split-window-right))))
         (with-selected-window target-win
           (vterm buf-name)
            (vterm-send-string "clear\n")
           (vterm-send-string "julia\n")
            (mj/julia-send-code "using Pkg; Pkg.activate(Base.current_project())")
            ))))))

 (defun mj/julia-send-code (code)
   (let ((the-buffer-name "*julia*"))
     (if-let* ((buf (get-buffer the-buffer-name)))
         (with-current-buffer buf
           (vterm-send-string code t)
           (vterm-send-return))
        (message "Buffer does not exist"))))

 (defun mj/julia-send-buffer ()
   (interactive)
   (if-let* ((file (buffer-file-name)))
        (mj/julia-send-code (format "include(\"%s\")" file))
     (message "Buffer is not visiting a file")))

 (defun mj/julia-send-region-or-line ()
   (interactive)
   (let (code)
     (if (use-region-p)
         (setq code (buffer-substring-no-properties (region-beginning) (region-end)))
        (setq code (thing-at-point 'line t)))
     (when code
        (let ((trimmed-code (string-trim-right code)))
          (mj/julia-send-code trimmed-code)))))
 )

format 使用 Runic 替代了 Lsp 自带的 format 功能。

Footnotes:

Date: 2026-03-03 Tue 08:51

Author: Mian Jie

Created: 2026-03-03 Tue 10:13