Emacs 筆記:Prodigy

Prodigy - Manage external services from within Emacs。

說到 Prodigy 就讓我想到一個 Minecraft 的模組叫 Mobycraft - Mobycraft 讓使用者可以從 Minecraft 內操作各種 Docker container,玩家不需要離開 Minecraft 的環境。

Prodigy 作爲一個控制界面,它可以協助我們迅速的啟動特定的服務。以我來說,例如 hexo server,特定資料夾的 Jupyter notebook server 都可以通過 Prodigy 快速啟動。所以我再也不需要在 iTerm2 切換到當前資料夾並輸入特定指令。

安裝方式請參考各自的 package manager。

macOS 額外設定

主要是軟體在 macOS 上面會進入 nap mode。

defaults write org.gnu.Emacs NSAppSleepDisabled -bool YES

定義服務

有相當多參數可以選擇。文檔在 M-x describe-variable <RET> prodigy-services。完整的定義如下:

(setq prodigy-services
  '((:prop value ...)
    (:prop value ...)))

服務啟動後,會以一個 buffer 的形式來表示,major mode 是 prodigy-viewer-mode,關掉這個 buffer 會連帶的使進程關閉。在 prodigy 的控制面板中,可以控制這些進程。

控制面板,標籤與過濾器

控制面板的操作模式類似 Dired,不過需要使用前綴 c(不是 control)。另外,可以定義標籤及過濾器,方便一次性的啟動服務。

;; (prodigy-define-tag &rest args)
(setq prodigy-tags
 '((:prop value ...)
   (:prop value ...)))

;; (prodigy-add filter &rest args)
(setq prodigy-filters
  '((:tag foo)
    (:name "bar")))

我的使用場景

主要是用來啟動研究專題資料夾的 Jupyter notebook 以及部落格 Hexo server。底下是我一個範例:

(prodigy-define-service
  :name "Research"
  :command "jupyter"
  :args '("lab")
  :cwd "~/Projects/research"
  :tags '(jupyter research)
  :stop-signal 'sigkill
  :kill-process-buffer-on-stop t)

Hexo 的部分有兩個 - 一般性的 localhost server 跟 deploy。

(prodigy-define-service
 :name "Hexo Server"
 :command "hexo"
 :args '("server")
 :cwd "~/Dropbox/Blog"
 :tags '(hexo)
 :kill-signal 'sigkill
 :kill-process-buffer-on-stop t)

(prodigy-define-service
 :name "Hexo Deploy"
 :command "hexo"
 :args '("deploy" "--generate")
 :cwd "~/Dropbox/Blog"
 :tags '(hexo)
 :kill-signal 'sigkill
 :kill-process-buffer-on-stop t)

另外像 Jekyll 也可以定義;我以前會使用 Jekyll 不過現在改用 Hexo 了。

;; You should add env varible when defining a tag.
(prodigy-define-tag
    :name 'jekyll
    :env '(("LANG" "en_US.UTF-8")
           ("LC_ALL" "en_US.UTF-8")))

(prodigy-define-service
    :name "Jekyll server"
    :command "jekyll"
    :args '("serve" "-w")
    :cwd "~/Dropbox/Blog"
    :tags '(jekyll)
    :kill-signal 'sigkill)

Emacs 筆記:編譯 Emacs

很多年沒有編譯 Emacs 了。以前會寫一個 crontab 腳本從 GNU Savannah 拉原始碼下來編譯。後來都是用系統版(從 apt / brew / pacman 安裝)。最近在搞我的 PyTorch 機器學習,需要連線到我的 Workstation(安裝了 Ubuntu 18.04.02),但是系統預設的 Emacs 是 25 版;而我用的幾個套件需要 26+ 以上,我又不太想用別人的 PPA,所以只好自己編譯。

幾個注意事項

  • GNU 現在從 mercurial 換成 git,方便很多。如果不想要把一大坨的歷史代碼也拉下來,可以只下載 zip 檔案。
  • Ubuntu 需要安裝 build-essentialtexinfo
  • 需要先執行 autogen
  • ./configure 有很多 no-x 的選項;我的 workstation 沒有安裝 GUI,所以不需要 X-window 支援,可以去掉;configure 如果發現系統沒有安裝圖片相關的 lib 也會提示是否移除相關的支援。

編譯過程是標準的 GNU GCC / make 工作流,幾乎不需要額外調整:

./autogen.sh
./configure
make
sudo make install

其中,make 可以開 make -j 4 之類的選項開併行編譯,速度會快很多。當我重新開始編譯自己的 Emacs 時,我感到一種久違的、靈魂的復甦。The fabulous hacker - Harold Wu - is back!


用 Racket 刷題

在 online judge 領域中,大家最熟悉的就是 LeetCode 刷題 - 把上面的題目做懂是進 FLAG 的一個必要條件。我其實一直想做些演算法的題目來增進實力;不過,在本機寫完,貼上去跑測試、再回頭修 code 其實不是很方便,我希望有一套工具可以快速搞定驗證、上傳的部分,我只要在本機端按下幾個按鈕就好。LeetCode 支持的程式語言種類不是很多 - 只有主流的幾種,例如 Python / C++ 等。如果想用小語種,例如我最近在深入的 Racket 跟 Julia,則要考慮另一個刷題選擇 - HackerRank

其他幾個刷題平臺包括:UVaProject Euler。我自己覺得 OJ 最重要的點在於檢討,並學習其他人的解法。

HackerRank 介紹

HackerRank 這一類型的刷題網站都是由題目、未公開的題目與答案資料集組成。程式有一個既定的讀寫框架,解題者主要是在題目與答案之間構建一個函數來解決問題。主要的題目類別包含「Algorithm」,以及「Distributed system」「Security」「Functional programming」等幾種。

Racket 介紹

Racket 是一個以 S-expression 爲語法的 Scheme 衍生語言。前身是教學用語言 PLT-Scheme。後來則衍生爲泛用語言,並且擁有強大的 macro / pattern matching 以及出色的運行速度。根據 The Benchmarks Game 中,在某些特別的演算法上,Racket 的速度可以在 Java 的兩倍時間內達成!而在與 Python 的對比上,更是幾乎大勝。

另外,Racket on Chez 也在緊鑼密鼓的開發中。Chez 是一款能將 Scheme 編譯成乾淨且高效,速度不亞於 C 的強力工具(參考王垠的文章 《Chez Scheme 的傳說》

我自己對 S-expression (a.k.a. Lisp 家族) 都很喜歡,例如 R7Rs Scheme / Clojure / Common Lisp / Emacs Lisp 以及類 Lisp 語言像 Julia 跟 R。

Emacs 介紹

我的主力編輯器是 Emacs。相關的設定很多,而 Racket 相關的主要就是 Racket-mode 跟 Paredit。目前正在測試 Geiser 實際上,如果只是要寫 Racket,我覺得 DrRacket 也不錯,不過我已經習慣 Emacs 了。有關 Emacs 的入門,可以參考臺灣 Emacs 社羣的 《Emacs 101 新手求生指南》。然後可以參考王垠關於 Scheme 編輯環境的設定

解題框架

在 HackerRank,官方沒有辦法直接對函數推參數,所有的東西都必須通過 stdin / stdout。在某些題目 - 例如讓我卡關在讀寫的 List Replication - 明明解答非常容易搞定(如果用 threading macro 會很漂亮),然而如何把輸入讀進去 list 卻成了一大問題!最後保留了我的主體函數,抄襲了別人的框架才搞定(然後我發現別人用遞迴跟 cond 做出了很漂亮的結果,我自己用 threading 反而不是很好看,有好幾個醜陋的 lambda)。

以前期簡單的題目 filter 舉例, 用 for/list,就可以建構出一個基本的 filter 函數出來。

;; the algorithm
(define (filter l n condition)
  (for/list ([i l]
             #:when (condition i n))
    i))

;; the I/O framework
(define s (read))
(define l (sequence->list (in-port)))
(define (show ans) (for ([i ans]) (println i)))

;; the output
(show (filter l s <))

需要修改的地方就是開頭的函數,以及最後顯示解答的部分。當然,還是要根據題目的要求進行調整。這裏的框架就是一個前期題目通用的解決方案。注意不要把 list 直接輸出,這樣是沒有分數的!(我犯了好幾次,直到框架穩定下來 XD)

  • LeetCode

Matlab 中的函數與檔案管理

說到 Matlab 專案的資料夾結構,不少程式語言都是把類似用途的函數放在同一個檔案內,然後通過 TDD(test-driven development)的流程來控管,並且在檔案互相呼叫下,可以有效以 namespace 的特性避免衝突。但是,Matlab 似乎沒有這種特性?於是使得目錄下面有數十個 Matlab 的 .m 檔案。這篇嘗試深入瞭解一下這個問題及是否有 namespace 的替代方案。

Local Functions

以官方指引 local functions 爲例,文章舉了下面的例子:

function [avg, med] = mystats(x)
  n = length(x);
  avg = mymean(x,n);
  med = mymedian(x,n);
end

function a = mymean(v,n)
  % MYMEAN Example of a local function.
  a = sum(v)/n;
end

function m = mymedian(v,n)
  % MYMEDIAN Another example of a local function.
  w = sort(v);
  if rem(n,2) == 1
    m = w((n + 1)/2);
  else
    m = (w(n/2) + w(n/2 + 1))/2;
  end
end

在上面的例子中,三個函數均存在於 mystats.m 這個檔案內。其他檔案想使用的話,不需要有 import / using / include 等動作,只需要直接呼叫 mystats 即可!加上說明,可知道:

  1. Matlab 會將每個 .m 檔案讀入
  2. 只有其中第一個會被讀取,剩下來的都是供第一個函數使用的 local functions

Nested Functions

接著我注意到 Matlab 的另外一個特性:nested functions (說明見官方文件 nested functions)。

function parent
  disp('This is the parent function')
  nestedfx

  function nestedfx
    disp('This is the nested function')
  end
end

其實只是複雜函數內部的程式碼重複使用的一些注意事項。對於解決我的問題其實幫助不大。官方文件後面則是討論作用域(稱爲 workspace)的問題。

Namespace 的替代方案

其實 Matlab 的設計上沒有 namespace。不過,還是有很不錯的解決方案。參考來源是 What is the closest thing Matlab has to namespaces?

步驟如下:

  1. 創建資料夾,這個資料夾需要有 + 在最前面,例如 +mypkg
  2. 接着,所有在這個資料夾內的函數,在母目錄的資料夾內將需要以 mypkg.calculate 的形式被呼叫
  3. 需要使用 import 引入
namespace_matlab
├── main.m
└── +mypkg
    └── calculate.m

這是資料夾結構如上,注意 + 一定要存在!

import mypkg.*
% main.m

mypkg.calculate('yfwu')
function result = calculate(name)
 % calculate,m
 disp(sprintf('hello %s', name))
end

這樣,就可以把東西歸類在一起了。但是,原本的 local function 的問題,其實沒有解決啦。這個方法只是一個利用 package 的方式來讓大量程式碼更好管理罷了。

  • Matlab

Matlab in Emacs

Matlab in Emacs

安裝 matlab-mode 後能於 Emacs 中執行 Matlab 交互介面(簡稱為 matlab-shell,也就是一般常見一進入 Matlab 那個 » 提示符號的視窗),以及開啟副檔名為 .m 的 Matlab 程序存檔(一個 major-mode:matlab-mode )。

安裝 Matlab

  1. 首先掛載 iso 檔案,並且執行腳本 install.sh (若無法執行,請 chmod +x install.sh,會需要輸入驗證伺服器文件(license.dat)以及鑰匙(key),一般可在光碟內其他資料夾找到。
  2. 會出現 java 寫成的安裝程式,安裝到指定的位置(沒記錯的話他的目標是 /usr/local/MATLAB/R2013a,你可以自己更改成喜歡的沒差)
  3. (此步驟重要) 將 /usr/local/MATLAB/R2013a/bin/matlab 建立 symlink 到 /usr/bin,不然會出現 Searching for program: no such file or directory, matlab
  4. 之後 Emacs 的 mode 會從這個 symlink 連到 Matlab 解釋器。
  5. 需要注意的是:可能是官方支援度不好?我的 Matlab 的 java 介面主程式啟動之後會自己關掉,然而 emacs 裏面用 matlab-shell 倒是非常穩定。

補充

Matlab 吃舊版本的 libncurses,需要設定

sudo ln -s /usr/lib/libncursesw.so.6 /usr/lib/libncursesw.so.5

安裝 matlab-mode

  1. 直接使用 el-get 安裝!
  2. 重新啟動或者 eval-current-bufffer 之後,開啟一般的檔案如:function.m,會進入 matlab-mode 可以編輯。
  3. M-x matlab-shell 可以啟動交互畫面,就跟主程式一樣沒有差別!
  4. 補充說明:shell-command 設定下去之後,C-c C-c 可以把整個 buffer 送進去執行,而 C-c C-r 則是將選定的區域送入執行
  5. 需要確定 Matlab 啟動腳本存在於 $PATH 內.
  • Matlab