題目中雖提到了 Emacs
,但其實本文主要講述是一個Emacs
的神級插件---org-mode
的故事。 org-mode
不了解的,請自行Google!
在未使用 org-mode
前,學習和實驗Go的特性(例如 context )的步驟如下:
- 打開終端
- 跳轉到學習示例目錄下
- 創建子目錄,例如 context , 然后跳入子目錄, 創建 main.go 文件
- 編寫簡單的示例代碼
- 編譯和運行代碼示例,若出錯調試,返回上個步驟
上面的過程有些可精簡,例如在IDE中,目錄跳轉可在對話框中完成,代碼的編寫,編譯和運行都可在IDE中完成。 在IDE中流程如下:
- 在學習示例目錄下,創建一個新的示例工程
- 編寫簡單的示例代碼, 編譯和運行
在IDE中,整個流程精簡了很多。 作為語言使用者,我們可能需要把實驗過的示例代碼留存,以便能應用在日后的開發中。 上面的兩個流程,都會根據實驗的包或者語言特性,創建相應的子目錄,經過這樣的處理,代碼示例都聚合到了一起,方便了翻閱和修改。 示例代碼的執行結果,大多是直接輸出到終端的,你要驗證原有示例代碼的執行結果,你必須要重新執行代碼,因為執行結果并沒有留存。 若是這個示例代碼,是很久之前的,可能是一年以前的, 這個時候你看到代碼的時候,可能會時一臉懵逼。 這個時候你會想,為什么當時沒有多留些信息,讓我知道,為什么做這個示例,以及這段代碼完成了什么功能。
Org-mode
簡單配置后, 可完美的解決以上問題,代碼留存,執行結果留存,文學編程(在編碼過程中書寫文檔)。
上圖展示了一個子問題的各個部分,問題描述,解決方案,代碼,以及代碼的執行結果。 子問題對應 Org-mode
中的子目錄,然后所有問題都可聚合在一個 Org-mode
文檔中。 現在流程可簡化為:
- 跳轉到
Org-mode
文檔中 - 創建子目錄,書寫解決方案,編碼實現
借助 Org-mode
強大的特性,可很容易的復用代碼以及查詢。
對于 Org-mode
不熟悉的,可先瀏覽Org-mode的主頁。 Org-mode
通過 Babel
執行代碼,對此不熟悉的讀者, 可閱讀我之前翻譯的文檔。
ob-go
Babel
可執行多種語言的代碼, 但官方的收錄的語言支持中,沒有Go。 但Go是如此的炙手可熱,沒有官方支持,肯定也會有先行者實現支持。 感謝 pope 在 ob-C 實現了ob-go , 實現了 Babel
對于Go的支持。
簡介
ob-go 使 Org-babel 可執行Go代碼。 與解釋語言可直接執行不同,Go需要先編譯為可執行文件,然后運行。 Go代碼通過 go run
命令編譯和運行。 若代碼中沒有 main
函數,默認情況下,代碼會被包裹在簡單的 main
函數中。 若 :package
選項沒設置并且代碼中沒有聲明 package
, 則 main
package 被聲明。 示例如下,代碼被執行,執行結果被回寫到 buffer
中。
#+BEGIN_SRC go :imports "fmt"
fmt.Println("Hello, org-mode")
#+END_SRC
#+RESULTS:
: Hello, org-mode
使用快捷鍵 C-c C-v v
可查看被擴展后的代碼。
Go特定的頭參數(Header Arguments)
除了 Babel
的常規頭參數之外,下面是一些Go語言特定的頭文件。
-
:args
: 傳遞命令行參數到代碼編譯后的可執行文件,傳遞超過多參數,需要使用list
。 -
:flags
: 傳遞給go run
或者go build
的flags(未使用成功)。 -
:main
: 若沒設置no
, 代碼將會被包裹在main
函數中。默認yes
。 -
:imports
: 為代碼提供 imports 的快捷支持。 當處理main
函數時,應該使用這個選項。 要 import 多個包,請使用list
。 -
:package
: 設置當前代碼塊 tangle 時的包名。 需要::main no
。 若沒設置,同時代碼中沒有包名聲明,main
包將被聲明。 -
:var
:ob-go
支持Babel
的變量,不過目前還不完備。
配置
這里假設Go的開發環境已經配置完畢,同時你的機器上的 Emacs
和 Org-mode
都是最近的版本,對于 Babel
都是完整支持的。 由于 ob-go
沒有并入 Org-mode
所以需要單獨配置。 步驟如下:
M-x find-library ob-C
, 找到的目錄org-plus-contrib
新建 ob-go.el , 然后把 github中代碼 復制到新建的文件中
-
配置
org-babel-load-languages
, 如下:(org-babel-do-load-languages 'org-babel-load-languages '((python . t) (C . t) (go . t) (emacs-lisp . t) (shell . t)))
編碼流程
最近在 LeetCode 上做編程訓練,這里就以解決上面的問題的流程來做說明。
在LeetCode頁面上,仔細閱讀和理解問題,這里以 Merge Two Binary Trees 為例
跳轉到 leetcode.org , 創建 merge two binary trees 子目錄
創建名為 merge-two-bt 的Go代碼塊
-
通過快捷鍵
C-c '
打開Go語言特定的編輯模式buffer中,然后編碼。 在編碼過程完成后,C-c '
完成并關閉編輯buffer;或者對自己編輯不滿意,C-c C-k
取消編輯并關閉編輯buffer。 -
把定義好的代碼塊整合到一起,然后執行(完整代碼)。
使用示例
導入多個包
#+BEGIN_SRC go :imports '("fmt" "time")
fmt.Println("當前時間:", time.Now())
#+END_SRC
#+RESULTS:
: 當前時間: 2017-06-12 18:04:20.562355811 +0800 CST
命令行參數傳遞
#+BEGIN_SRC go :imports '("fmt" "os") :args '("bable" "golang")
fmt.Println(os.Args[1], os.Args[2])
#+END_SRC
#+RESULTS:
: bable golang
多入參
#+NAME: sum
#+BEGIN_SRC go :imports "fmt" :var a=12 b=13
fmt.Println(a+b)
#+END_SRC
#+RESULTS:
: 25
#+call: sum(a=22,b=23)
#+RESULTS:
: 45
代碼組織
#+NAME: min
#+BEGIN_SRC go
func min(a, b int) int {
if a > b {
return b
} else {
return a
}
}
#+END_SRC
#+NAME: get-min
#+BEGIN_SRC go :var a=0 b=0 :imports "fmt" :noweb strip-export
<<min>>
func main() {
fmt.Println(min(a,b))
}
#+END_SRC
#+call: get-min(27, 23)
#+RESULTS:
: 23
總結
Org-mode于我來說,就是一個神器,打破了我對一個編輯器的認知,打破了我對一個信息收集器的認知。 Org-mode依托于強大的 Emacs, 借助于 Babel, 使文檔和代碼無縫的結合在了一起, 一會編碼,一會記錄, 一會調試,一會執行。 不失為,學習語言,嘗試新工具的不二神器。 再來一段Go代碼:
#+BEGIN_SRC go :imports "fmt"
fmt.Println("Goodbye, Gopher!")
#+END_SRC