安装pip, 命令ugt
(pip install urgent, ugt --help)
运行3.6, 编译3.7
(3.7, 3.8上, urgent编译出的字节码可以被3.6加载, 也就是没有使用ROT4和LOAD_METHOD这些新的指令)
跨版本加载用--raw_bytecode flag.
ugt cc <xxx.ugt> <xxx.code> --raw_bytecode --project bootstrap.toml后,
import marshal
code_object = marshal.load(open("xxx.code", 'rb'))
exec(code_object, globals())
let/def和rec.
let x = 1
def x = 1
一个意思.
rec
rec f = x -> (f, x)
是递归, 也就是自己的定义中可以出现自己.
let ..., ...
let x = 1, y = 2, z = ...,
定义时x, y, z互不引用, 他们定义时用到的x, y, z来自外部.
rec ..., ....
rec x = a -> (a, y), y = a -> (a, x) , z = ...,
定义时, x, y, z互相引用.
<expr> ?
<expr>
else
<expr>
我觉得语法还行.
how?
fine
else
emmm 还阔以.
let x =
(1, 2) match
(a, b, c) => a,
(1, 2) => 0可以match的很多, 更多例子见后面的variants.
注意, 二元运算符会被当作解构器.
我们还支持view pattern.
let f = x -> (x, 2 * x)
1 match (f -> (a, b)) => a + b # 1 + 2 * 1
5 match (f -> (a, b)) => a + b # 5 + 2 * 5 项目根目录那个hello.ugt可以拿来试, 打开ugt repl,
Urgent> import Hello
Urgent> print 1
Undef print
Urgent> let x = Hello
Urgent> x.print 1
1
Urgent> open x
Urgent> print 1
1import导入模块但不加载它的成员, open加载成员.
看起来似乎是python的import <mod>和from <mod> import *, 其实不是.
一切都是静态的, 只有module可以被import和open. x之所以可以被open,
是因为x被分析为是模块Hello的alias.
Urgent是pure的, 变量不可以多次赋值(只有绑定), 所以, 上面代码里任何使用x的地方都会索引到模块Hello.
如果说python import *是有运行时开销的, 那么, 已经加载过的模块在urgent里,
无论在哪儿open都是0开销的.
一种来自于ML语言的用法是, 在局部打开模块.
Urgent> import Hello
Urgent> let x = 1
Urgent> print 1
Undef print
Urgent> let x = let y = 2 open Hello in print 1
1
Urgent> print 1
Undef printopen, let, rec, def这些语句后面可以跟一个in, 表示表达式.
Urgent> let x = def x = 1 in open Hello in print <| x + 1
2连续的let, open, 这些语句, 可以不写in.
Urgent> let x = let x = 1 let y = 2 let c = 3 open Hello in print <| x + y + c
6
Urgent> print <| 1 + 2 + 3
Undef operator.<|
Urgent> print (1+2)
Undef operator.++左结合,优先级10.
infixl + 10<|右结合, 优先级0.
infixr <| 0优先级是一个整数, 可以为负.
如果想要使用其他模块定义的运算符优先级和结合性, 可以open该模块. 如果不想污染作用域, 可以局部open该模块.
只有and和or两个是固定的优先级, 他们的优先级都比其他二元运算符低.
and和or也用在pattern matching中.
and表示左右俩都要match, or表示只match一个.
x match
1 or 2 or 3 => 0,
x and (y, z) => (y, x, z) data Nil, Cons(_, _)
infixr :: 1
let :: = Cons
let l1 = Cons 1 Nil
let print_lst = lst ->
lst match
1 :: Cons a b => ...,
Cons a b => ...,
Nil => ...[]语法是Nil的简写, [a, b]这样的语法是Cons a (Cons b Nil)的简写, 所以我们可以
data Nil, Cons(_, _)
infixr :: 1
let :: = Cons
let l1 = [1]
let print_lst = lst ->
lst match
1 :: a :: b => ...,
a :: b => ...,
[] => ...局部定义的variants:
let x = data A, B, C in
(A, B, C)
A
# Undef: A这是用来动态create数据类型的. 注意这对于运行时来说比较缓慢. 一般来说, 还是把数据创建放到模块顶层.
Variants的成员可以按名字访问:
data Either(left, right)
let x = Either(1, 2)
x.left |> printurgent实现了尾递归, 所以, 你可以随意地写递归函数定义.
我们不能说这个速度非常快, 实际上, 比起python循环, 相同情况下我们的尾递归性能只有其一半, 更别说我们还有柯里化.
然而, 既然你在看了, 那我可以开心地向你保证, 这是Python世界最快的, 且货真价实的尾递归实现.
其基本原理相当简单, 你可以在codegen.py的preload方法里找到一个用字节码书写的,
优化到极致的尾递归scheduler.
我会在非尾递归调用点应用这个scheduler,
你能同通过阅读compiler.py的v_tco以及v_call方法, 来进一步理解尾递归优化的实现.
f.(a, b, c)像这样, 调用时加个点的函数, 就是按照Python的调用约定. 这并不和我们的尾递归优化冲突.
下面的代码需要加载项目目录下的base/prelude.ugt.
ref
Urgent> let x = ref 1
=> ()
Urgent> x
=> ref 1
Urgent> x := 2
=> ()
Urgent> x
=> ref 2
Urgent> !x + 2
=> 4for
Urgent> for [1, 2, 3] ( x ->
print x
)
1
2
3
=> ()while
Urgent> let x = ref 1
=> ()
Urgent> while { !x < 10 } {
do print !x
in x := !x + 1
}
1
2
3
4
5
6
7
8
9
=> ()上面的循环暂时是尾递归写的, 之后会用Python字节码重写.
for和while的实现如下:
rec for = seq -> f ->
seq match
[] => (),
hd :: tl =>
do f hd
in for tl f
rec while = cond -> f ->
cond () ?
do f ()
in while cond f
else ()都可以良好地尾递归.
urgent把所有代码编译成单个.pyc文件.
# 编译
sh> ugt cc <主模块.ugt> <a.pyc> --project <项目文件.toml>
# 直接执行
sh> ugt run <主模块.ugt> --project <项目文件.toml>
# 启动REPL
sh> ugt repl --project <项目文件.toml>一个示例的项目文件见bootstrap.toml.
packaging和project building这些方面其实还没设计好, 但先用着了. 做事第一.