Thursday, December 30, 2021

使用 Luminus 的心得

最近做新專案,需要做一個 POC (proof of concept) 讓使用者可以 read/write database ,於是,我快速地用 Luminus 弄了一個 POC 出來,弄出來之後,就立刻 demo 了。這邊整理一下成果與心得。

常用的 Luminus command 
* 生成專案
 lein new luminus [project-name] +postgres +reagent +shadow-cljs +swagger
* migrate
   lein run migrate


成果部分
 (1) 完成了一個不需要登入,但是可以簡易查資料、寫入資料的 POC。只論功能的話嘛,比 todoMVC 還更少吧?
   (2) 開發時間 16 hours 。
   (3) 50% 時間用於

         - 查 document 研究這個東西怎麼用

         - 撞到一些 bug 然後在 debug 。

         30% 時間用於

     - 理解用戶的問題

         - 判斷與取捨、try and error

    20% 時間才是真正開發的時間

technical stack 的選擇與速度頗有關:

  (a) database 用 postgres 。因為我有與新手合作, Datomic 要整個重教,太麻煩了。

  (b) frontend 選用 reagent 。之前用 re-frame 的經驗,讓我覺得,還是只用 reagent 會快得多。此外,我還用了 lambdaisland fetch 。

ClojureScript 的 REPL-driven development issue
Luminus 如果有搭配使用 ClojureScript 的話,它會啟動兩個 nREPL 。一個是給 Clojure 的,一個是給 ClojureScript 用的。要啟動 ClojureScript 的 repl-driven development 時,要注意要選正確的 nREPL port 

Friday, December 10, 2021

開發 ClojureScript 時,檢查 Promise 裡的值

通常,在開發 ClojureScript 時,如果需要檢查 Promise 裡的值,我會寫成如下的程式碼:
;; Using lambdaisland fetch, kitchen-async (p/let [resp (fetch/get "temp-url")] (def resp resp)) ;; Then, I evaluate resp to know the value.

使用 def 來取得 promise 值。

為什麼會這樣子寫呢?因為 prn 會把值輸出到 browser 的 console log 去,而 browser 的 console log 已經有太多東西了。

其實還有另一個方案,使用 tap>。於是,程式寫成這樣子:
(p/let [resp (fetch/get "temp-url")] (tap> {:response resp}))

使用 tap> 的好處是,我只要把 browser 指向 port 9630 ,就可以很容易地看到 tap> 輸出的值了。



Wednesday, November 24, 2021

使用 Lisp 來開發 neovim 的 plugin

一直以來,使用 neovim ,我都是安裝 plugin 來用,從來沒有考慮自己來開發 plugin 。有改過一兩行,但是,開發一個新的 plugin 的念頭就不太有了。為什麼呢?大概是因為 vim script 實在太難學了,看到 vim script 就覺得心很累。

另一方面,看到 Emacs 的 user ,特別是 Clojure 社群的使用者,總是信手捻來,就開發一下自己的 plugin 。這種時候,我又心生羨慕。要是我也可以有個 lisp 來開發 plugin ,該有多好?

總之,這件事其實也辦得到了,Neovim 提供 lua 做為 embedded language。而 fennel 可以完美地編譯成為 lua 。aniseed 就是這個 compiler 。

我試了一下之後,還發現,居然意外的很簡單上手。做法如下。
  1. 使用 neovim 安裝 Conjure
  2. Olical/aniseed 頁面,找一行 curl 指令
    curl -fL https://raw.githubusercontent.com/Olical/aniseed/master/scripts/seed.sh | sh
    執行完,就可以生出一個 plugin directory
  3. 然後,就可以開始使用 REPL-driven 的方式開發 plugin
  4. 參考文件 => 開啟 neovim 之後, :help aniseed 

注意事項:
0. 教學部分,首先可以先看 aniseed 的網站,開頭的影片是一個範例。
1. REPL-driven 的感覺,會與 Clojure 有很大的不同。
2. aniseed 已經有提供標準的 library functions ,引入 aniseed.core 即可。
3. aniseed 也可以無縫地操作 neovim 的 API ,引入 aniseed.nvim 即可。 aniseed.nvim 可以界接 https://github.com/norcalli/nvim.lua


Friday, July 16, 2021

vim 編輯的成對編輯

用習慣了 vim-sexp-mappings-for-regular-people 裡的幾種快速編輯方式,比方說:

dsf:   splice (delete surroundings of form)
cseb: surround element in parentheses
cse]:  surround element in brackets
cse}:  surround element in braces

後來,我遇到想要把一串字母加上 double quotes 或是 single quotes 的情況,也想下類似的指令來操作。這時候,我才發現,原來我一直都少安裝一個很重要的插件 vim-surround 。vim-surround 提供的重要操作有:

ds"     delete surrounding double quote
cs)]    change surrounding parentheses to brackets
cs'"    change surrounding single quote to double qutoe
csw"   surround word with double quote


vim-surround 的文件

Saturday, June 26, 2021

java 版本

最近遇到了需要在電腦裡頭,安裝多個版本 java 的問題。 解決方案是使用 sdkman ,然後,就輕鬆搞定。

我的問題是什麼呢?我要開發的系統是一個 distributed system 。有的 service 需要 java 8、有的 service 需要 java 11 。所以每啟動一兩個,就要切換一下 java 的版本。還好用了 sdkman 。

Tuesday, May 18, 2021

postwalk

如果說, sequence 是 Clojure 世界裡最常用的「走訪抽象」、而 map 是對於這種抽象最佳的「轉換函數」的話,那如果把 sequence 換成 tree 的時候,那 map 應該要換成什麼呢?應該是 postwalk 。從這個上述的角度來看的話, postwalk 可以視為是一種 map 的 generalized form 。


我最近有遇到一種情況,我需要在 backend side 生成 hiccup form,然後,將這個 hiccup form 送到 frontend side 。然而,我卻又需要在 hiccup form 裡去呼叫 reagent form 1 的函數。所以我的作法就是:在 backend 生成 hiccup form 時,對需要呼叫 reagent form 1 函數的地方,先塞一個「佔位」的 keyword 。等到 hiccup form 被送到 frontend 時,再將該 keyword 轉換成 reagent form 1 。


上述的那個轉換操作,我就是用 postwalk 來做,非常的直覺與容易。

Thursday, May 6, 2021

walking skeleton first

最近學到一個概念: walking skeleton first

什麼是 walking skeleton 呢?
它是以最少量的成本打造的程式碼,並且滿足下列三項條件
  • 自動化構建程式 (build)
  • 自動化布署程式 (deploy)
  • 自動化的端對端測試 (end-to-end test)
在開發新 project 的時候,應該秉持著 walking skeleton first 的概念,先把 skeleton 打造出來,再來補足核心的功能。

Tuesday, May 4, 2021

ClojureScript library 簡單整理

gaiwan.co 工作一陣子了,工作上常用到的 ClojureScript library 其實值得整理一番:

  • lambdaisland/fetch               js fetch 的 wrapper ,而且 default 對 transit 有效
  • athos/kitchen-async            ClojureScript 的 promise library
  • applied-science/js-interop   處理 ClojureScript 與 js 交互溝通的問題
  • lambdaisland/glogi               log 
  • alandipert/storage-atom      提供 atom 的介面,將資料存到 web storage 裡。 

Friday, April 30, 2021

pagination 與 lazy sequence

在開發與 Slack API 整合的模組時,因為部分的 Slack API 會傳回超大量的資料,所以有設計分頁 (pagination) ,而因為需要取得所有的資料,所以設計「走訪所有分頁」的設計,用 loop/recur 來實作。

很巧的是,我後來看到類似的函數,我的老闆 Arne Brasseur 早就寫過了,但是,他是用 lazy-seq 來做。我比較我與他的實作之後,第一個念頭是去想:「是不是很多我本來用 loop/recur 來解的問題,其實也可以用 lazy-seq 來解?」

仔細思考之後,我覺得這個又回到了軟體開發的本質:『軟體開發的本質是對真實的世界建模 (modeling)』

pagination 與 lazy sequence 本來就是一體兩面的東西,都是在建模一個概念:「資料的總量很大,使用者可以先取第一段、視需要再取第二段、再取第三段…,你不需要一口氣就取回所有的資料。」從這個角度去思考之後,可以想到要使用 lazy-seq 去解 pagination 的題目也不奇怪了。

Wednesday, April 28, 2021

Clojure configuration library - aero

Clojure 的 aero library 有兩個我覺得超欣賞的功能:

  • 隱藏 secrets 的 best practice 
  • 可以自行延申 areo reader ,設計自己需要的 configuration semantic

其中,隱藏 secrets 的這個 idea ,算是挑戰了 12 factor app 裡的,「要把 secrets 放在 environment variables 」的 best practices 。我質疑 12 factor app 的這一項作法很久了,但是卻遲遲沒有找到好的替代方案,終於找到 areo library 了!

Sunday, April 18, 2021

Clojure 與 java beans

在 Java 的世界裡,要封裝資訊,常常不是使用 java.util.HashMap 或是 java.util.Arraylist ,而是使用 java bean 或是 POJO 。總之,如果遇到 Clojure 與 Java 要 interoperate 的時候,這些都是麻煩。

Clojure 的標準 library 已經提供了一個 bean 函數,可以轉換 Java bean 變成 Clojure 的 Map collection 。而,如果遇到多層的巢狀的 Java bean 時,就得考慮使用 java.data 這個 library 。裡頭有 from-java-deep 這個函數可以深層轉換。

Thursday, April 8, 2021

從 backend 傳送「初始化資料」到 frontend

開發 fullstack application 時,從 backend 要送資料到 frontend ,這是很常見的事,而這件事有三種不同的作法:

  1. API
  2. HTML 頁面初始化
  3. 編譯期初始化


通常就是讓 backend 提供 API ,然後 frontend 去送出 http request 來取得資料。然而,有一種情況:「 frontend App 要初始化的時候,就需要從 backend 取得幾個變數來做初始化,由於只有不到十個變數、資料量不大、資料也不會變化,似乎為此特別做一個 API 也太麻煩了。」

於是,對於上述的問題就可以考慮直接透過 html document 來夾帶資料的作法:

a. 當 backend 在生成 page content 時,在 hiccup format 的 html 裡加入下方這一行。注意:MIME type 要設定為 application/json,如此一來,瀏覽器就不會去執行它。

[:script#data-tag {:type "application/json"}
   (json/write-str {:key-a data-a :key-b data-b})]

b. 在 frontend 的 ClojureScript 裡,可以透過如下的程式碼來取得資料:

(defn init-config []
   (let [data (->> (js/document.getElementById "data-tag")
                        (.-innerHTML)
                        (.parse js/JSON))]
     (js->clj data :keywordize-keys true)))


還有一種情況是:「如果需要初始化的資料,根本一開始就只是放在一個靜態的檔案裡」這種情況,就可以考慮最後一種作法,讓 ClojureScript 利用 macro 在編繹期去讀取檔案。 

Tuesday, April 6, 2021

Clojure combinator

偶然看到一個 Eric Normand 的課程,它把 Clojure 的一些 high order functions 集中成一套課程,叫做 Clojure combinator 。哪些是 Clojure combinator 呢?有八個函數:

  • identity
  • constantly
  • complement
  • partial
  • fnil
  • comp
  • juxt
  • apply

妥善地運用這八個函數,可以讓語意更清晰。個人覺得 constantly 與 juxt 算是相對難學。
 
 constantly 是有一回,我要使用 alter-var-root 的時候,才發覺它的妙用。
 juxt 則是在三種情況下特別好用:
  1.  搭配使用 sort-by, group-by
  2.  修改既有的 hash map、生成新的 hash map --- 參考 lambdaisland 的 idiom 
  3.  取代 (comp vals select-keys) 
如果把上述的八個函數做個簡單的分類的話,我會分成四類:
1. 修改函數 (input arg 是函數, output 是 input arg 修改後得到的新函數) - fnil, complement, partial 
2. 組合函數 (input arg 含有超過一個函數, output 是將 input arg 重組而成的新函數) - comp, juxt 
3. 純生成函數 (input arg 可以不是函數,但 output 是一個新函數) - constantly
4. 非生成函數 (output 是某個結果,不是函數) - apply, identity 

在做了上述的這個分類之後,我有一個小小的新發現:其實組合函數還有一些不在這八個 combinator 裡的,分別是 some-fn 與 every-pred

Sunday, March 28, 2021

脊柱模型 (spinemodel)

Spinemodel 是我最近看到的一個思考工具,一開始只是覺得『圖』滿漂亮的。後來,我在寫一篇文章的時候,應用了這個 spinemodel 來寫作,算是解決了我長久以來寫作的一個難題。長久以來,我有一個題目總是寫不出來:「如何把 Clojure programming language 對一般經營管理階層的好處寫出來?

後來,看了 spinemodel 之後,總算想出了寫法 --- Clojure programming language 算是一種 tools ,所以我要寫它的好處,可以先把它對於 practices 的影響做出連結,這樣子就可以了。考慮經營管理階層與軟體開發者的知識落差之後,這應該是最有效的寫法。

也因為如此,又多看了看這個 spinemodel 的其它應用方式,目前有看到兩個其它的應用方式 ,我覺得算是不錯的,網站上已經有把這兩個用法寫得相對清楚了。

Thursday, March 4, 2021

non-ascii character

我不小心寫出了一個 bug ,這個 bug 是在程式碼中錯誤地使用了 non-ascii character 。

我需要寫一個字串 "1280x720" ,正常來講,在打程式碼的時候,因為要使用 ascii characters ,所以中間的乘號會使用英文的 x 字母。然而,因為我是從網路找來的字串貼上的,於是,這個字串中間的乘號,就真的不小心變成了 code 215 ,也就是真正的「乘號」。最麻煩的地方是,這種 bug ,肉眼還幾乎看不出來!

事後去反省,該如何可以快速地去找出這種錯誤,大概研究了兩種可以輔助除錯的工具:


(1) 在 source code 資料夾下指令 

ack "[^\x00-\x7F]"

如此就會立刻找出 source code 資料裡,包含 non-ascii code 的地方。

(2) vim 的指令 ga 或是 :as 可以顯示 cursor 處的字元編碼。

Wednesday, March 3, 2021

開發 Clojure 搭配使用的 ack 設置檔 (.ackrc)

ack-grep 我一直覺得滿好用的,不用特別設定就會自動忽略 node_modules 這種可以忽略的資料夾。然而,開發 Clojure ,尤其又使用 deps.edn &clj 的話,還是要透過 .ackrc 來設定一些資料夾,讓 ack 視為自動忽略。

--------分隔線 ----------------


# Tips:
# using ack to show only the clojure files for further processing
# ack -f --clojure
#
--ignore-dir=resources
--ignore-dir=.shadow-cljs
--ignore-dir=.cpcache

在 macbook 下使用的 .tmux.conf 檔案

我有時候很佩服一些朋友,他們會把一些工具玩轉到極致,我的話,通常還是採取 purpose-driven 的方式去研究工具。確定會派得上用場的,才去做設定。下方是我平常用的 tmux config ,可以搞定我覺得對我來說比較重要的兩個功能:
  • 可以用滑鼠捲動。
  • 複製貼上可以與系統的剪貼簿整合。
------- 分隔線 -------

# Enable mouse scrolling
set -g mouse on

# Vim style navigation in copy mode
setw -g mode-keys vi
bind P paste-buffer
bind-key -T copy-mode-vi v send-keys -X begin-selection
bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel 'pbcopy'
bind-key -T copy-mode-vi r send-keys -X rectangle-toggle


# Tutorial: copy-pase through tmux buffer
#
# default <prefix> is <C-b>
# using `<C-b> + [` to enter copy mode
# using `v` to start selection
# using `y` to copy selection
# it will get the text stored at two places: tmux copy buffer and system clipboard
# using `ESC` to clear selection
# using `q` to leave copy mode
# using `<C-b> + P` to paste at another tmux session

Sunday, February 28, 2021

vim 的「搜尋」相關技巧

我研究了自己在 vim 裡的搜尋行為之後,發現了自己有兩個使用習慣,很明顯地沒有最佳化:

  • 有「無意義的反白高亮度顯示」,卻沒有關閉它。
  • 反覆搜尋同一個字串。
什麼是「無意義的反白高亮度顯示」呢?因為我有設定搜尋字串高亮度顯示,所以一旦我搜尋了 AAA,整個檔案裡所有的 AAA 字串都會變成『反白高亮度』。然而,當我按下 Enter 之後,也就意味著,我已經打算在某一個特定的 AAA 位置開始做編輯了。即使如此,這個時間點,『反白高亮度』卻不會自動消失,有時到達甚至造成干擾的程度。這時候,應該下的指令是 :noh ,這樣一來,就可以順利地關閉反白高亮度。

什麼時候會反覆搜尋同一字串呢?這個低效率的行為常發生在開發程式時,有時候要寫第 50 行的地方,卻需要同時參考第 15, 75, 95 行這三處的寫法。由於行號是會變動的,所以我通常是去記下某一行的關鍵字,比方說,第 15 行有一個關鍵詞是 upload ,我就透過搜尋 upload ,如此就可以從其它地方很快地回到該行。

上述的作法是很低效率的習慣。相對有效率的作法則是,使用 vim 的 marks 功能。比方說,如果我需要之後回到第 15 行的話,我就在該地方,先下個指令 ma ,讓 a 這個 mark 記住該位置。之後,如果我要回到那一行時,我只要下 `a ,就可以快速地回到 a mark 的位置。

Wednesday, February 24, 2021

vim 查找關鍵字/函數引用 vs 開啟多重檔案

vim 有兩組功能,我覺得很適合一起使用

  • 查找關鍵字/函數引用
  • 開啟多重檔案

查找關鍵字的功能,我是安裝 ack.vim 。設定好之後,就可以下 :Ack 指令來找。有比對到關鍵字的檔案,就會在 Quickfix List 裡列出,而且可以用上下鍵來選擇開啟。於是,這時候下列的一組 Quickfix List 專用的指令,就值得記憶了。

搭配指令
:copen                  =>  打開 Quickfix 窗口,顯示所有結果
:cclose, :ccl           =>  關閉 Quickfix 窗口

如果 vim 配合 language server protocol 來使用的話,可以有查找「函數引用」的功能,我使用的 vim plugin 會將函數引用列在 Location list 裡。於是,這時候下列的一組 Location List 專用的指令,就值得記憶了。

搭配指令
:lopen                  =>  打開 Location 窗口,顯示所有結果
:lclose, :lcl            =>  關閉 Location 窗口

上述的功能,就很容易開啟許多的 buffers (即開啟後正在編輯的檔案),於是我們如果想要看到,現在開啟了哪些 buffers ,就可以下 :ls 來看,目前有哪些檔案被開啟。

搭配指令
:ls                          => 看有哪些檔案被開啟成為 buffer
:b [數字id]               => 顯示對應該 id 的檔案
:e [檔名]                  => 開啟特定檔案 
:bd                         => 關閉目前的 buffer

Monday, February 22, 2021

塑造工作 (ShapeUp)

我朋友很欣賞 37 signals 公司的工作哲學 ---「塑造工作」,推荐給我看。(37 signals 公司是間一流的 web application 公司)

該書對於軟體工作倡議的分工方式,我認為相當的有道理。杜拉克曾經有一篇文章探討如何提高知識工作者的產出? 該文提到了幾個重點:

 1. 界定任務

 2. 專注於核心工作

 3. 界定績效

 4. 管理者與知識工作者建立夥伴關系


杜拉克來自實務的觀察自然有其道理,但是,要怎麼應用在軟體開發上呢?對於軟體開發的實務來講,十次中有九次,往往都牽扯到未知的實驗與探索,換言之,軟體開發的實務之中,往往帶有一定的科學理論建構性質。有了未知性質的工作,任務難以界定;而邊界難以界定清楚的工作,績效又該如何界定呢?我在過往工作的經驗之中,往往只能使用「專注核心工作」的原則,對於其它三項原則,往往只聽樓梯響,不見人下樓。


塑造工作」一書提出的解決之道相當有特色:將團隊拆分成「資深團隊」與「循環團隊」。「資深團隊」負責塑造工作,塑造完成的工作,應該是已經將高風險與未知的部分移除、可以在 6 週之內完成的明確開發工作。塑造完成的工作,交給「循環團隊」去開發,而循環團隊的任務則是要在 6 週內完成開發任務。


從界定任務與績效的角度來看,資深團隊的任務是要正確地塑造工作。而循環團隊的工作則是準時地把塑造完成的工作加以交付。這正也符合了杜拉克說過的,「規畫」與「執行」應該分開來做。有了明確的任務界定,界定明確的績效也因此可能發生。(註:杜拉克同時也說:『分開來做,是指在不同的時間做,但是,不一定要交給不同的人來做。』)

從夥伴關系的角度來看,上述的資深團隊,一方面帶有中階管理階層的屬性,因為他們的任務要決定『去做什麼與不做什麼』。更重要的是,他們也是該領域中極為卓越的知識工作者,否則,勢必沒有足夠的能力,來為循環團隊塑造可以達成的工作。管理者與知識工作者兩個不同的身分放在同一個腦子裡,自然可以形成夥伴關系。

Tuesday, February 16, 2021

從 leiningen 換成 deps.edn

Clojure 的官方出了自己的 build tool 。老實說,我一直都覺得,比起 leiningen 來得沒有那麼容易上手。經過一些調整之後,總算是覺得還可以啦,就是多學一些東西嘛。主要有幾個需要調整的地方:

  • 如何生成新的 project ?
  • 如何產生 nREPL 的開發環境?
  • 如何在專案的資料夾之下做 grep 

第一個問題,我的解法是安裝 clj-new 這個 alias 。裝完之後,就可以用下列的指令來生成 project

clojure -X:new :name myname/myapp ;; 生成專案
cd myapp
clojure -M -m myname.myapp      ;; 啟動


第二個問題,我的解法是設定對應的 alias 。網路上我找到相對完整豐富的範例是 practicalli/clojure-deps-edn

第三個問題:我用 ack-grep 時,常常都會掃瞄一堆暫存檔,妨礙我取得關鍵的資訊。於是,我設定了 ack-grep 的 config ,直接設定不搜尋這些暫存檔。我的 .ackrc 如下

# Tips:
# using ack to show only the clojure files for further processing
# ack -f --clojure
#

--ignore-dir=resources
--ignore-dir=.shadow-cljs
--ignore-dir=.cpcache

減少不必要的 reflection

在 Clojure 與 Java 的交界處,會產生大量的 reflection 。這種時候,可以用 type hints 來減少不必要的 reflection 。有效的作法是開啟一個特定的變數,將它的值改成 true ,然後 Clojure compiler 就會產生 reflection warning ,讓我們可以在開發的時候,很清楚明確地知道「哪些地方產生了 reflection」,於是就可以針對這些地方,去加上 type hint 。

這個重要的變數是 *warn-on-reflection*

Clojure 程式開發的依賴注入

Clojure 的世界裡,常用的依賴注入 (dependency injection) 手法主要有五種:

  • 依賴注入應用於函數 (function) 
  • 依賴注入應用於命名空間 (namespace)
  • 依賴注入應用於攔截器 (interceptor)
  • 依賴注入應用於網站處理器 (web handler)
  • 依賴注入應用於 Java program 的物件工廠 (factory)

這五種手法都頗為重要:

  • 第一種非常的常見,可以算是基本的要求。
  • 第二種,在使用 REPL workflow 就會應用到,Luminus 搭配的 mount 就是例子。
  • 第三種可以提高程式碼的可讀性、可預測性。因為條件限制 (constraints) 多,可讀性就變好。通常是將 web handler 之後會依賴的 component 透過攔截器注入到 request 裡。
  • 第四種可以看成是第三種的變型:在生成 web handler 的時候,注入依賴。
  • 第五種則應用於 Clojure/Java interoperation 。當要利用 java library 的時候,有時候既有的 java library 有預留可以注入的 factory pattern 。這時候,如果覺得既有的 java library 不夠好用,想做一些微調整的話,就可以考慮使用 Clojure 的關鍵字 reify 去生成新的 factory 來給 java library 使用。於是,Clojure wrapper 包覆的 java library 就是有微調過的版本。

Mac 筆電的快捷鍵整理

由於新工作的資安規定,硬碟要加密,我決定全心全意地來改用 Mac book 。研究了一陣子之後,發現還真的滿多需要且值得記憶的:

1. 截圖類

cmd + shift + 3 --- screen shot 
cmd + shift + 4 --- screen shot with area
cmd + shift + 5 --- screen record 

2. Finder 類

cmd + <up>     --- 往上移一層資料夾
cmd + [           --- 回到前一個資料夾  // 同時也適用於 browser 
cmd + alt + L   --- 到「下載資料夾」    // 同時也適用於 browser

3. 文字編輯器

cmd + <left>      --- 移動到行首
cmd + <right>    --- 移動到行尾
cmd + <up>       --- 移動到文章首
cmd + <down>   --- 移動到文章尾
cmd + shift + T   --- 切換 .txt 與 .rtf  (存檔的格式)
cmd + T             --- 顯示字型
cmd + shift + C   --- 顯示色碼
cmd + V                  --- 貼上 with formatting
cmd + alt + shift + V --- 貼上 without formatting


4. 一般應用程式

cmd + <Comma>    --- 偏好設定
cmd + Q                --- 關閉應用程式


5. 移動類

fn + <up>      ---   Page Up
fn + <down>  ---   Page Down 
fn + <left>     ---   Home
fn + <right>   ---   End


6. 其它

cmd + <Space>       ---  Spotlight search
cmd + shift + ESC   --- 強制關閉應用程式
cmd + ctrl + Q        --- lock screen