Install
Hy 是一个编译为 Python 的 Lisp 方言, 类似于 Clojure 之于 JVM。 目前 conda 4.5.12 上的 Hy 版本为 0.9.12,只支持 Python 2.7, 所以需要用 pip 直接从 github 上安装:
$ conda create -n hylang python=3.6
$ . activate hylang
$ pip install git+https://github.com/hylang/hy.git
$ hy
hy 0.15.0+64.ga42e17a using CPython(default) 3.6.8 on Linux
=> (print "hello world")
hello world
升级方法是再次运行 pip install git+https://github.com/hylang/hy.git
,
不需要先卸载已经安装的包,参考
pip: pulling updates from remote git repository.
更好的方法是在 IPython console 中使用 Hy (借助 Jupyter kernel Calysto/calysto_hy):
conda create -n piphy python=3.6
. activate piphy
pip install jupyter
pip install git+https://github.com/ekaschalk/jedhy.git
pip install git+https://github.com/Calysto/calysto_hy.git
python -m calysto_hy install --sys-prefix
ipython console --kernel calysto_hy
>>> (-> "abc" (.split "b") (. [1]) (print))
spy
模式
Hy 的 spy
模式打印出转换后的 Python代码再执行:
$ hy --spy
=> (setv result (- (/ (+ 1 3 88) 2) 8))
result = (1 + 3 + 88) / 2 - 8
None
=> (setv r2 (- (/ 92 2) 8))
r2 = 92 / 2 - 8
None
=> (+ result r2)
result + r2
76.0
基本语法
字符串必须使用双引号,但可以包含回车符。 单引号表示 quote,即阻止求值。
常用数据类型写法:
$ hy --spy
=> (setv list1 [1 2 3]) ; this is a list
list1 = [1, 2, 3]
None
=> (setv dict1 {:a 1 :b 2}) ; this is a dict
from hy import HyKeyword
dict1 = {HyKeyword('a'): 1, HyKeyword('b'): 2}
None
=> (setv set1 #{1 2 3}) ; this is a set
set1 = {1, 2, 3}
None
访问对象的属性(缺点是属性名称只能写死):
=> (. df1 shape)
df1.shape
(10980, 3)
访问 DataFrame 某列:
=> (. probes ["fullDesc"]) ; or (get probes "fullDesc")
probes['fullDesc']
0 1000611-SYGD-01-01-01
1 1000612-SYGD-01-01-02
2 1000613-SYGD-01-01-03
3 1000614-SYGD-01-01-04
4 1000615-SYGD-01-01-05
访问复合属性的一个元素:
=> (. df loc [(> (. df [0]) 2)])
df.loc[df[0] > 2]
管道操作(更多细节可参考 dsnote "Pipe Operator in Functional Programming Languages"):
=> (-> probes (. ["fullDesc"]) (.head))
probes['fullDesc'].head()
0 1000611-SYGD-01-01-01
1 1000612-SYGD-01-01-02
2 1000613-SYGD-01-01-03
3 1000614-SYGD-01-01-04
4 1000615-SYGD-01-01-05
slice 相关
在 Hy 中使用 Pandas loc
, iloc
中括号 + 冒号 语法时可参考如下转换:
df.iloc[3:, ::2]
等价于 (. df iloc [(, (slice 3 None) (slice None None 2))])
df.loc[:, "time"]
等价于 (. df loc [(, (slice None) "time")])
df.iloc[2, 0]
等价于 (. df iloc [(, 2 0)])
df[['time', 'value']]
等价于 (. df [["time" "value"]])
或者 (get df ["time" "value"])
df.loc[:, 'xiaoshi': 'value']
等价于 (. df loc [(, (slice None) (slice "xiaoshi" "value"))])
df.loc[df.index > 2, :]
等价于 (. df loc [(, (> (. df index) 2) (slice None))])
或者 (. df loc [(> (. df index) 2)] [(slice None)])
转换规则包括:
-
用
[(, a b)]
或者[a] [b]
代替[a, b]
; -
用
slice
函数代替冒号,可在 IPython 中通过slice?
查询此函数 API; -
用
slice(None)
代替单独的冒号; -
去掉 list 中的逗号;
编译为 pyc
$ cat << EOF > demo.hy
(setv people [{:name "Alice" :age 20}
{:name "Bob" :age 25}
{:name "Charlie" :age 50}
{:name "Dave" :age 5}])
(defn display-people [people filter]
(for [person people] (if (filter person) (print (:name person)))))
(display-people people (fn [person] (< (:age person) 25)))
(defmacro infix [code]
(quasiquote ((unquote (get code 1))
(unquote (get code 0))
(unquote (get code 2)))))
(print (infix (3 * 5)))
EOF
$ hy demo.hy
$ hyc demo.hy
关于上面代码中宏定义相关的函数参考 3.20 Quasiquoting: quasiquote, unquote, and unquote-splicing.
反编译 pyc 文件的方法见 dsnote "Encrypt Python Source Codes" 的 "反编译 pyc" 一节。
Editor Setup
vim
默认配置下 vim 不识别 .hy 类型文件,为了让 vim 插件在 hy 文件中工作,有以下两种方法:
-
定义新的文件类型,要求插件对其进行渲染:优点是定义准确, 缺点是对某些插件无效;
-
将新文件类型映射到已有的某种主流文件类型:优点是适用范围广, 缺点是必须要有一种非常类似的主流文件类型,而且渲染是按主流语言的, 对于新语言来说不是特别准确;
新类型方法
在 $MYVIMRC 中添加:
" Rainbow Parenthesis
au BufRead,BufNewFile *.hy setfiletype hy
let g:rainbow_active = 1
let g:rainbow_conf = {
\ 'ctermfgs': ['yellow', 'red', 'white', 'green', 'lightblue', 'lightred', 'lightgreen'],
\ 'operators': '_,_',
\ 'parentheses': ['start=/(/ end=/)/ fold', 'start=/\[/ end=/\]/ fold', 'start=/{/ end=/}/ fold'],
\ 'separately': {
\ '*': {},
\ 'tex': {
\ 'parentheses': ['start=/(/ end=/)/', 'start=/\[/ end=/\]/'],
\ },
\ 'hy': {
\ 'ctermfgs': ['yellow', 'red', 'white', 'green', 'lightblue', 'lightred', 'darkgreen'],
\ },
\ 'vim': {
\ 'parentheses': ['start=/(/ end=/)/', 'start=/\[/ end=/\]/', 'start=/{/ end=/}/ fold', 'start=/(/ end=/)/ containedin=vimFuncBody', 'start=/\[/ end=/\]/ containedin=vimFuncBody', 'start=/{/ end=/}/ fold containedin=vimFuncBody'],
\ },
\ 'html': {
\ 'parentheses': ['start=/\v\<((area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)[ >])@!\z([-_:a-zA-Z0-9]+)(\s+[-_:a-zA-Z0-9]+(\=("[^"]*"|'."'".'[^'."'".']*'."'".'|[^ '."'".'"><=`]*))?)*\>/ end=#</\z1># fold'],
\ },
\ 'css': 0,
\ }
\}
Plug 'luochen1990/rainbow'
其中文件类型定义参考 :h new-filetype
.
ctermfgs
表示 Console 中 vim 的颜色定义(gvim 的颜色在 guifgs
中定义),
颜色名称取值来自 :h cterm-colors
.
Automatic closing brackets
在 $MYVIMRC 中添加:
" automatic closing brackets
inoremap " ""<left>
inoremap ' ''<left>
inoremap ( ()<left>
inoremap [ []<left>
inoremap { {}<left>
inoremap {<CR> {<CR>}<ESC>O
inoremap {;<CR> {<CR>};<ESC>O
映射类型方法
有些插件(例如 tpope/vim-sexp-mappings-for-regular-people) 用上面的方法指定类型后依然不生效,可能是插件内部对文件类型做了限制, 这时只能使用文件类型映射,将要处理的类型“伪装”成与它类似的另一个类型, 对于 hy 文件来说映射为 clojure 类型比较好,实现方法是在 $MYVIMRC 中添加:
autocmd BufRead,BufNewFile *.hy set filetype=clojure
Plug 'tpope/vim-repeat'
Plug 'guns/vim-sexp'
Plug 'tpope/vim-surround'
Plug 'tpope/vim-sexp-mappings-for-regular-people'
这个插件的具体使用方法见 dsnote "Use vim as Clojure Development Environment", 另外由于它已经包含了括号自动补全,所以上面关于自动补全的配置就不需要了。
上面的 rainbow 插件仍然需要安装。
assoc
特性
Hy 的 assoc
与 Lisp, Clojure 中的 assoc
不同,(assoc foo bar baz)
被翻译成
foo[bar] = baz
,直接改变 foo
本身,而不是保持 foo
不变返回一个新值。
造成这种区别的根本原因在于 Python 不坚持数据的 immutable,
关于这个函数行为的讨论见 issue 238,
Consider making Hy models immutable,
以及 API doc 中 assoc
的 note.
目前的实现虽然不够 immutalbe, 但与 Python 的兼容性比较好, 尤其在数据分析中,如果为一个 DataFrame 添加一列,必须重新生成一个新的 DataFrame, 会导致内存消耗大幅增加。
Ref: