(一)Bool值:没啥要说的,就是true和false
(二)数值(Numeric Literals):
支持类型:
当然,作为一门现代编程语言,Pony支持自动类型推导,但有时候它与我们用C++、Swift时的直觉相悖:
let my_explicit_unsigned: U32 = 42_000
let my_constructor_unsigned = U8(1)
let my_constructor_float = F64(1.234)
同Swift一样,数值字面量可以用下划线分割。此外你还注意到,U8(1),F64(1.234)这些字面量带上了类型,有些用户说,这从C的观点来看,这类型推导完全没有技术含量啊,我都已经告诉你类型了,哪里还有推导的余地?其实,大多数(用过之前版本的)Java用户并不同意这样的观点,因为他们之前会写出这样的代码:
AReallyUselessClass anotherUselessObj = new AReallyUselessClass("useless things!")
其中痛楚无需赘言。
除此之外,Pony还支持不同进制(指2 8 10 16进制)的数值字面量(不同于Erlang支持2—36进制)以及科学计数法:
let my_decimal_int: I32 = 1024
let my_hexadecimal_int: I32 = 0x400
let my_binary_int: I32 = 0b10000000000
let my_double_precision_float: F64 = 0.009999999776482582092285156250
let my_scientific_float: F32 = 42.12e-4
(三)字符(Character Literals):
各位在C语言里都见的不爱见了,但是在很多现代语言里,人们似乎更喜欢用string去写业务逻辑,不论是char还是Char似乎只有在foo_str[1]里出现。Pony里除了能用单引号(')括起来一个字符外,还支持多字节字面量(Multibyte Character literals):
let big_a: U8 = 'A' // 65
let hex_escaped_big_a: U8 = '\x41' // 65
let newline: U32 = '\n' // 10
let multiByte: U64 = 'ABCD' // 0x41424344
别把它当Javascript!
(四)字符串(String Literals):
无论如何,string被现代语言处理的已经可以称得上elegant了。大概是从PHP开始吧,人们注意到只用“”并不够用,所以造了一个多行字符串,后来又衍生出原字符串(raw string)和插值语法。总之,从语言层面上,string这个概念已经很完美了。现在让我们看看Pony里字符串是什么样子:
use "format"
actor Main
new create(env: Env) =>
let pony = ""
let pony_hex_escaped = "p\xF6n\xFF"
let pony_unicode_escape = "\U01F40E"
env.out.print(pony + " " + pony_hex_escaped + " " + pony_unicode_escape)
for b in pony.values() do
env.out.print(Format.int[U8](b, FormatHex))
end
自苹果公司发布的iOS 5输入法中加入了emoji后,这种表情符号开始席卷全球。在2015年苹果推出的编程语言Swift,当然是后来的教程中,也明确表示emoji可以被加入到字符串,似乎这就是在提醒我们把emoji应当看作是字符和表情的结合体。字符串之字符的取值范围一步步的扩大,我们有理由相信string除了elegant还能变得even more powerful:
let stacked_ponies = "
"
let u_umlaut = "ü"
let triple_quoted_string_docs =
"""
Triple quoted strings are the way to go for long multiline text.
They are extensively used as docstrings which are turned into api documentation.
They get some special treatment, in order to keep Pony code readable:
* The string literal starts on the line after the opening triple quote.
* Common indentation is removed from the string literal
so it can be conveniently aligned with the enclosing indentation
e.g. each line of this literal will get its first two whitespaces removed
* Whitespace after the opening and before the closing triple quote will be
removed as well. The first line will be completely removed if it only
contains whitespace. e.g. this strings first character is `T` not `\n`.
"""
上述三例均来自官网,意义分别为:1.支持多行字符串(公平对待换行符)2.可以直接打入特殊字符3.三双引号多行字符串。
最后一个被拿来用作大部分现代编程语言的多行字符串处理方案,它还可以更强大!就比如Ceylon就把这种字符串当成了注释方式的一种。
关于比较字符串,Java程序员又要抱怨了:什么是池?什么是哈希值?什么是实例?如果仅仅是比较字符串就要考虑这么多,不禁有点小忧伤:
let pony = ""
let another_pony = ""
if pony is another_pony then
// True, therefore this line will run.
end
(五)数组(Array Literals):
关于数组,STL的容器思想和Java的Collection逼得人们不得不从一个更高的层面去思考这样一个数据结构。在Pony中,things got a little different:
let my_literal_array =
[
"first"; "second"
"third one on a new line"
]
1.类型推导:
以下介绍会涉及部分Reference Capability的知识,建议囫囵吞枣
let my_heterogenous_array =
[
U64(42)
"42"
U64.min_value()
]
默认数组的元素可以改变,所以通常我们声明出来的数组类型为:Array[T] ref,其中T是泛型语法。上面这个数组的类型就是Array[(U64|String)] ref。当然了,我们也可以显式指明数组类型:
let my_stringable_array: Array[Stringable] ref =
[
U64(0xA)
"0xA"
]
如果我们想要一个不可改变的安全的数组:
let my_immutable_array: Array[Stringable] val =
[
U64(0xBEEF)
"0xBEEF"
]
2.As表达式:
有时候,我们需要限制类型推导但同时不失灵活性:
let my_as_array =
[ as Stringable:
U64(0xFFEF)
"0xFFEF"
U64(1 + 1)
]
否则,它将被推导为Array[(U64|String)] ref
(一)局部变量:
同Swift,var用来声明可变量,let用来声明常量:
var x: String = "Hello, world!"
let name = "PECman"
出于安全考虑(依旧同Swift),局部常量在声明之初必须赋值(从并发的思想去思考为什么需要这样的限制),而变量是没有此限制的:
var z: String
z = "Hello" //
let y: U32 // Error, can't declare a let local without assigning to it
y = 6 // Error, can't reassign to a let local
当然,局部意思是存在其作用域(scope):
if a > b then var x = "a is bigger" env.out.print(x) // OK end env.out.print(x) // Illegal
(二)字段(Fields):
和其他OOP语言类似,Pony对字段的改动并不大,只不过要注意以下划线开头的字段是私有字段。除此之外,每一个字段的生命周期都与实例对象绑定。上面我们提到过一个局部变量的赋值限制,字段有一点点不同:
如果字段没有在声明时或构造方法里赋值,就会报错:
class Wombat
let name: String
var _hunger_level: U64
new ref create(name': String, level: U64) =>
name = name'
set_hunger_level(level)
// Error: field _hunger_level left undefined in constructor
fun ref set_hunger_level(hunger_level: U64) =>
_hunger_level = hunger_level
嗯哼?
(三)嵌入字段(Embedded Fields):
与局部变量不同,某些类型的字段可以使用embed声明。具体来说,字段只能嵌入类或结构而不能嵌入接口、特征、原生类和数字类型。使用embed声明的字段类似于使用let声明的字段,但在实现(implementation)级别,嵌入类的内存直接在外部类中布局。与let或var相比,实现使用指针来引用field类。嵌入字段可以以与let或var字段完全相同的方式传递给其他函数。嵌入字段必须从构造函数表达式初始化。
为什么要使用嵌入?嵌入避免了访问字段时的指针间接寻址和创建该字段时的单独内存分配。默认情况下,如果可能,建议使用嵌入。但是,由于嵌入字段与其父对象一起分配,因此对该字段的外部引用禁止对父对象进行垃圾收集,如果字段比其父对象长,则可能导致更高的内存使用率。如果这恰好是你关心的问题,请使用let。
(四)全局变量和重名变量:
Some programming languages have global variables that can be accessed from anywhere in the code. What a bad idea! Pony doesn’t have global variables at all.
同时,很多编程语言里都允许你利用遮蔽(shadowing)覆盖变量,如:
fn main() {
let mut mutable = 200;
let mutable = true //利用遮蔽更改类型和值
}
然而:
If you accidentally shadow a variable in Pony, the compiler will complain.
If you need a variable with nearly the same name, you can use a prime '
.
(一)操作符别名:
下面两个语句是等效的:
x + y
x.add(y)
依此,我们可以改变一些二元操作符的业务逻辑:
// Define a suitable type
class Pair
var _x: U32 = 0
var _y: U32 = 0
new create(x: U32, y: U32) =>
_x = x
_y = y
// Define a + function
fun add(other: Pair): Pair =>
Pair(_x + other._x, _y + other._y)
// Now let's use it
class Foo
fun foo() =>
var x = Pair(1, 2)
var y = Pair(3, 4)
var z = x + y
和C++一样,咱们不能逮着一个操作符就把它给改了,这里有一份列表,以示范围:
(二)一元操作符:
以下两行代码等价:
-x
x.neg()
同上,也有一张表:
官网还有短路运算、优先级等知识:https://tutorial.ponylang.io/expressions/ops.html,此处从略
算术(Arithmetic)相关知识和操作符关联很大:https://tutorial.ponylang.io/expressions/arithmetic.html,请自行阅读。
(一)条件判断:
一个例子搞清基础语法:if then elseif then ... else
if a == b then
env.out.print("they are the same")
else
if a > b then
env.out.print("a is bigger")
else
env.out.print("b bigger")
end
end
此外,不同于C中可以用1或0或任意一个整数或指针代替条件,Pony的条件表达式返回值就是布尔值,不能是其他值.
(二)重要理念——万物皆表达式(而表达式就有值):
这一点在很多现代编程语言里很常见,你有充分理由把它理解为三元运算符的延申:
var x: (String | Bool) =
if friendly then
"Hello"
else
false
end
当然,对于空类型(None)各大现代编程语言也有所支持,例如Scala中有Nothing等,Swift和Ceylon也各有方案。主要是想萃取null或nullptr的功能:
var x: (String | None) =
if friendly then
"Hello"
end
记住,表达式一定要返回值,哪怕是空值!
(三)循环语句:
1.While循环:
var count: U32 = 1
while count <= 10 do
env.out.print(count.string())
count = count + 1
end
按照万物皆表达式的原则,这个while表达式会返回什么呢?答案是10.(注:Pony中表达式永远返回旧值,例如:
a=b=a <=> swap = a; a = b; b = a)
2.Break和Continue语句:
这两个语句的重要性就不用多说了,最著名的是那个意大利面程序(goto语句带来的灾难)。刚刚说过,万物皆表达式,而表达式则需要返回值。让我们认真分析一下break和continue的应用场景:
(1)Break语句意味着跳出循环,象征着循环表达式的结束,这样就暗示需要返回值。在Pony中,我们通常需要这样使用break:
var name =
while moreNames() do
var name' = getName()
if name' == "Jack" or name' == "Jill" then
break name'
end
name'
else
"Herbert"
end
有的读者见过带标签的循环/break语句(Swift代码):
outerloop:
for i in 0 ..< 10
{
innerloop:
for j in 0 ..< 10
{
if (j > 3)
{
break;
}
if (i == 2)
{
break innerloop;
}
if (i == 4)
{
break outerloop;
}
print("i = \(i) and with j = \(j)" )
}
}
在Pony中这是禁止的。Pony认为如果你使用了类似的语法,说明你的代码存在算法级别问题,也就是说你需要重构你的代码。
(2)continue语句通常代表继续循环,可能循环没结束,也有可能下一轮循环就会结束(条件不符时),这个时候你并不需要像break语句一样附带一个值。循环中的else代码块会起到大作用。
2.For循环:
和Swift中一样,Pony放弃了C风格的For循环,采用了迭代For循环:
for name in ["Bob"; "Fred"; "Sarah"].values() do
env.out.print(name)
end
同时,Pony也支持一种STL用户熟悉的语法(注意迭代器被声明为常量):
let iterator = ["Bob"; "Fred"; "Sarah"].values()
while iterator.has_next() do
let name = iterator.next()?
env.out.print(name)
end
其中?代表可选值的展开,swift中有类似概念:Swift中的问号?和感叹号! Charly_Zheng
3.Repeat语句:
其实它非常像do while语句,只不过要有现代编程语言的一点样子:
var counter = U64(1)
repeat
env.out.print("hello!")
counter = counter + 1
until counter > 7 end
记住,即使条件不符合这个循环也会执行一遍。尽管如此,你有时候仍然需要为这个循环加上一个else代码块:
A
continue
in the last iteration of arepeat
loop needs to get a value from somewhere and anelse
expression is used for that.
1.函数:
Pony functions are quite like functions (or methods) in other languages.
但是要注意,如果不指明返回值类型,一律按None处理:
class C
fun add(x: U32, y: U32): U32 =>
x + y
fun nop() =>
add(1, 2) // Pointless, we ignore the result
不少人听说过甚至可能是重载(overload)概念的狂热爱好者,可惜,Pony并不允许这样的事情发生。
2.构造器:
在类及其他类型中,我们使用new关键字声明一个构造函数。如果你需要非常多的构造器,你可以用new指明哪些是构造器,但是,你必须要有一个create构造器(这是语法糖需要,详情请看:语法糖)。依代码逻辑所需,你可以提前退出构造器——使用return语句(你一定想到了!)
3.默认参数(Default arguments):
这是当年很多人为之发愁的特性。很多同学在初学C语言时比较善思——printf是怎么实现的呢?C语言里对这方面处理的有点复杂,在一个合格的现代编程语言那里应该这么做:
class Coord
var _x: U32
var _y: U32
new create(x: U32 = 0, y: U32 = 0) =>
_x = x
_y = y
class Bar
fun f() =>
var a: Coord = Coord.create() // Contains (0, 0)
var b: Coord = Coord.create(3) // Contains (3, 0)
var c: Coord = Coord.create(3, 4) // Contains (3, 4)
但是,另一个问题油然而生了。就拿C++中的Win32基础开发,在注册窗口类的时候,有很多参数实际上是不需要我们来打理的,这就意味着需要体现程序的自动优越性。在简单的封装之后,比如:
void createMyWindow(..., string title = "Title", int height = 80, int width = 100, ...)
假设我想用默认的标题但是我想改一改窗口的高度或宽度,我就必须先填好第一个参数,之后才有权更改之后的参数。很烦,不是吗?
4.命名参数:
Pony使用where语法解决了上述问题:
class Foo
fun f(a: U32 = 1, b: U32 = 2, c: U32 = 3, d: U32 = 4, e: U32 = 5): U32 =>
0
fun g() =>
f(6, 7 where d = 8)
// Equivalent to:
f(6, 7, 3, 8, 5)
记住,如果你的输入数据起初是按顺序的,那么就先按顺序来,之后再用where语句。
5.链语法:
primitive Printer
fun print_two_strings(out: StdStream, s1: String, s2: String) =>
out.>print(s1).>print(s2)
// Equivalent to:
out.print(s1)
out.print(s2)
out
object.>method(arg1: ...) <===> object.method(arg1:...) ; object
如果是这样的链,则等于:
object.>method1(arg1:...).method2(arg1:...) <===> object.method1(arg1:...) ; object.method2(arg2:...)
细细思考,这是很自然的:
interface Factory
fun add_option(o: Option)
fun make_object(): Object
primitive Foo
fun object_wrong(f: Factory, o1: Option, o2: Option): Object =>
f.>add_option(o1).>add_option(o2).>make_object() // Error! The expression returns a Factory
fun object_right(f: Factory, o1: Option, o2: Option): Object =>
f.>add_option(o1).>add_option(o2).make_object() // Works. The expression returns an Object
6.匿名函数(闭包):
use "collections"
actor Main
new create(env: Env) =>
let list_of_numbers = List[U32].from([1; 2; 3; 4])
let is_odd = {(n: U32): Bool => (n % 2) == 1}
for odd_number in list_of_numbers.filter(is_odd).values() do
env.out.print(odd_number.string())
end
更详细的阐述见:Object Literals section
7.其他注意事项:
凡是完备的语言都需要错误处理,这方面比较出色的是Java,比较逊色则是Go和C++。这和代码风格和语言传统有很大关系,就和大括号到底写在哪是一个道理。Pony这方面我还并没有看到我想要的那种错误处理,毕竟刚刚0.9,出了1.0再说。
1.抛出和处理错误:
try
callA()
if not callB() then error end
callC()
else
callD()
end
记住,error不是泛指,而是特指一个名为“error”的命令(command)。如果程序有哪里让你不满意了,你就可以做一些处理,再使用error命令抛出错误。而关于try表达式的返回值,就是它的代码块之一的最后一句(你肯定又猜到了!)
我们在Java里见到过finally块,那么它在Pony里是怎么被引入的呢?
try
callA()
if not callB() then error end
callC()
else
callD()
then
callE()
end
即使你在try代码块里返回了某个值或者None,then块还是会执行的。
2.偏函数:
资料链接:Partial Functions(偏函数)
阅读完后请读者自己按其中要义自己写出一个偏函数,之后可以和官网例子对照看看:
fun factorial(x: I32): I32 ? =>
if x < 0 then error end
if x == 0 then
1
else
x * factorial(x - 1)?
end
3.With语法:
with被用于简化try块(特别是当需要声明变量时)和清除变量的操作,这一点和Python十分类似(在Python中被广泛用于文件操作):
with obj = SomeObjectThatNeedsDisposing(), other = SomeOtherDisposableObject() do
// use objs
else
// only run if an error has occurred
end
如果你想要你的类也支持with语法:
class SomeObjectThatNeedsDisposing
// constructor, other functions
fun dispose() =>
// release resources
*延伸阅读:
剩下的部分我认为只有一个Object Literals section需要好好学习学习:https://tutorial.ponylang.io/expressions/object-literals.html。其他的话诸君尽可泛读过去。为了我们下面Reference Capability的学习,大家再加把劲,加油!