因为我对不一样的编程语言涉足不广,所以文中我认为是好玩的东西可能在其余语言中早已存在。能够这样理解我说的『好玩』,因为 Vala 语言是编译到 C 的,所以凡是 C 语言中没有的东西,在我看来均可能是好玩的……这个基线可能真的是过低了,C 语言彷佛除了指针以外,彷佛没有什么特性 :)html
已经搞了 9 年的东西,到如今也没有一份像样的文档。现有的文档里,常常遇到过期的代码。算法
Vala 编译器能够将 Vala 代码编译为 C 代码。这些 C 代码调用了 GLib 与 GObject 库中的函数,所以系统中须要安装这些库,才能用 C 编译器将 C 代码编译为可执行文件。编程
Vala 语言能够用于编写 C 库,由于 Vala 编译器能生成库的头文件,而后由 C 编译器产生库文件。另外,Vala 语言也能生成 Vala 的 API 文件,这样一来,用 Vala 语言写的库能够直接在 Vala 代码中调用。c#
对于现有的 C 库,要想在 Vala 代码中调用它们,须要手写 Vala API,也就是一组类或函数的声明,这个任务对于 C 程序猿而言,不算太繁重。例如:segmentfault
[CCode (cheader_filename = "mylib.h")] public class MyLib : GLib.Object { public MyLib (); public void hello (); public int sum (int x, int y); }
若是 C 库支持 GObject-Introspection(GIR)规范,借助 Vala 提供的一个工具,能够将 GIR 文件转换为 Vala API 文件。设计模式
对于那些对本身控制内存有信心的程序猿,Vala 为他们保留了指针:bash
int i = 42; int* i_ptr = &i; // address-of int j = *i_ptr; // indirection Foo f = new Foo(); Foo* f_ptr = f; // address-of Foo g = f_ptr; // indirection unowned Foo f_weak = f; // equivalent to `Foo* f_ptr = f' Object* o = new Object(); o->method_1(); o->data_1; delete o;
Vala 支持泛型,采用的技术与 Java 类似,即类型擦除。他人以为这是伪泛型,而对于我这种死不悔改的 C 语言爱好者而言,这是真的不能再真的泛型。相关讨论见『谁是真泛型』一文。markdown
示例:网络
public interface With <T> { public abstract void sett(T t); public abstract T gett(); } public class One : Object, With <int> { public int t; public void sett(int t) { this.t = t; } public int gett() { return t; } } public class Two <T, U> : Object, With <T> { public T t; public void sett(T t) { this.t = t; } public T gett() { return t; } public U u; } public class Test : GLib.Object { public static void main(string[] args) { var o = new One (); o.sett(5); stdout.printf("%d\n", o.t); var t = new Two <int, double?> (); t.sett(5); stdout.printf("%d\n", t.t); t.u = 5.0f; stdout.printf("%f\n", t.u); } }
得益于类型擦除,以致于下面的代码居然是正确的。session
class TestClass : GLib.Object { } void accept_object_wrapper(Wrapper<Glib.Object> w) { } var test_wrapper = new Wrapper<TestClass>(); accept_object_wrapper(test_wrapper);
Vala 实现了一些经常使用的泛型容器,例如单向列表、双向列表、并发式列表、队列、哈希表、红黑树等。这些容器是以库的形式实现的,这个库叫 Gee,其 API 文档见 http://valadoc.org/#!wiki=gee-0.8/index
在上述的泛型示例中的模板实例化代码 Two <int, double?>
中,double
后面跟随了一个 ?
号。这是由于模板参数只能是指针或者与指针等宽的值类型(例如整型、布尔型、引用等类型)。只要某种类型名称具备 ?
后缀,那么 Vala 编译器会自动将其转化为 C 指针类型。除此之外,Vala 编译器会将全部的 Class 视为指针类型。因为你们都很恐惧 C 指针,因此能够将我说的『指针』理解为『引用』。
若是从 C++ 的角度来理解,能够认为 Vala 自动将占用不少字节的那些类型转换为引用,也就是说在代码中,你不须要显示的去将某种类型设定为引用。对于一个类的实例,也就是所谓的对象,即便你不想去引用它,只是想让它以一个值的形式存在,可是 Vala 说 no,它必须是引用。例如,有一个 Vala 类 TestClass
,要将它实例化,Vala 只提供一种途径:
TestClass t = new TestClass();
Vala 编译器会将这行代码翻译为如下 C 代码:
TestClass* t = NULL; TestClass* _tmp0_ = NULL; _tmp0_ = testclass_new (); t = _tmp0_;
若是你声明一个函数 foo
,它的参数是一个类类型:
void foo(TestClass t) { ... ... ... }
Vala 会将其转换为如下 C 代码:
void foo (TestClass* t) { g_return_if_fail (t != NULL); ... ... ... }
你要注意的是,Vala 不只会自动的将函数参数中的类类型转换为指针类型,并且还会检测指针是否为 NULL
(下文还要重提此事)。
Vala 将 C 指针掩盖的很是好。这样作的好处就是,将对象做为参数传递给函数或赋给同类,不须要显式的进行引用设定(还记得 C++ 代码中漫天飞的 &
么?),也不须要考虑 Copy 构造函数的那堆破事。对象就是用来被引用的,而对象的基本成分(它们的类型每每是语言内建的那些基本类型)能够被复制,这相似于你能够 clone 人体器官,可是人类禁止你去 clone 一我的。
没什么好说的,我认识的比 C 高级一点的语言差很少都有这个空间。Vala 的命名空间也没什么特殊之处,它是这样的:
namespace NameSpaceName { ... ... ... }
要使用某个命名空间,能够这样:
using NameSpaceName;
命名空间能够有效下降用户自定义标识符对全局命名空间的污染。Vala 的命名空间也能够嵌套:
namespace NameSpaceName_0 { namespace NameSpaceName_1 { ... ... ... } }
没什么好说的,要将匿名函数做为值来用,须要配合 delegate
。总之之后能够玩高阶函数抽象了。
delegate int IntOperation(int i); IntOperation curried_add(int a) { return (b) => a + b; // 'a' is an outer variable } void main() { stdout.printf("2 + 4 = %d\n", curried_add(2)(4)); }
Vala 的面向对象,单纯从类型的角度看,实际上是面向引用或面向指针。由于每一个对象都是在堆中分配的内存,在栈中引用。
在面向对象的世界里,函数就不叫函数了,而是叫方法。面向对象编程范式与泛型编程范式对立之处就在函数应该是函数,仍是方法?将函数称为方法,就是在认可有一类东西,它有一些处理事务的方法。将函数称为函数,就是认可有一些公式,可将数据代入公式里,而后由公式产生相应的数据,这些公式能处理不一样类型的数据。我总以为所谓的泛型编程,只不过是在静态类型语言上发展起来的一种不支持高阶函数的函数式编程范式……再也不谈泛型编程了。
Vala 为面向对象编程范式提供了类、信号、单重继承、抽象类与抽象方法、虚方法、接口、运行时类型识别等元素。其实没啥好说的,这个时代谁还不知道面向对象那些事……值得一提的是 Vala 支持 Mixin 的方式来模拟『多重继承』。
再值得提的是一个小小的语法糖——运行时类型转换:
class Foo : Glib.Object { public void my_method() {stdout.printf("foo\n");} } class Bar : Foo { public new void my_method() {stdout.printf("bar\n");} } void main() { var bar = new Bar(); bar.my_method(); (bar as Foo).my_method(); }
其中 bar as Foo
就是将 bar
的类型转换为其父类类型 Foo
。因为 Vala 具备运行时类型识别的功能,所以可在必定程度上保证类型转换的合理性。再看个例子:
Button b = (widget is Button) ? (Button) widget : null;
在语法层面上支持契约,要比在函数体内写上一堆条件判断语句优雅一些。
double method_name(int x, double d) requires (x > 0 && x < 10) requires (d >= 0.0 && d <= 1.0) ensures (result >= 0.0 && result <= 10.0) { return d * x; }
Vala 会对函数的引用类型的参数自动进行 null
检测。用 C 语言来讲,就是 Vala 能够自动检测指针类型的参数是否为 NULL
指针。那些带 ?
后缀的类型,Vala 不会检测它们是否为 null
。所以,怎么利用 Vala 的这个 null
检测特性,本身看着办。
Vala 中的函数能够接受 0 个或多个参数,值类型的参数会被复制,引用类型的参数不会被复制。函数参数的这种默认机制会被用两个修饰符 out
与 ref
修改。例如:
void method(int a, out int b, ref int c) { ... }
int b
原本是值类型的参数,被 out
改为引用类型,并且 b
能够是未初始化的变量,在 method
函数内部能够对 b
进行赋值,这样 b
就是 method
的一个结果。int c
原本是值类型的参数,被 ref
改为了引用类型,但它必须是已经初始化了的变量,method
能够修改它。
Vala 集成了 D-Bus,看下面的示例:
[DBus(name = "org.example.DemoService")] public class DemoService : Object { /* Private field, not exported via D-Bus */ int counter; /* Public field, not exported via D-Bus */ public int status; /* Public property, exported via D-Bus */ public int something { get; set; } /* Public signal, exported via D-Bus * Can be emitted on the server side and can be connected to on the client side. */ public signal void sig1(); /* Public method, exported via D-Bus */ public void some_method() { counter++; stdout.printf("heureka! counter = %d\n", counter); sig1(); // emit signal } /* Public method, exported via D-Bus and showing the sender who is is calling the method (not exported in the D-Bus interface) */ public void some_method_sender(string message, GLib.BusName sender) { counter++; stdout.printf("heureka! counter = %d, '%s' message from sender %s\n", counter, message, sender); } } void on_bus_aquired (DBusConnection conn) { try { // start service and register it as dbus object var service = new DemoService(); conn.register_object ("/org/example/demo", service); } catch (IOError e) { stderr.printf ("Could not register service: %s\n", e.message); } } void main () { // try to register service name in session bus Bus.own_name (BusType.SESSION, "org.example.DemoService", /* name to register */ BusNameOwnerFlags.NONE, /* flags */ on_bus_aquired, /* callback function on registration succeeded */ () => {}, /* callback on name register succeeded */ () => stderr.printf ("Could not acquire name\n")); /* callback on name lost */ // start main loop new MainLoop ().run (); }
将这个示例编译为可执行文件 vala-dbus-demo
,而后在终端中运行它:
$ valac --pkg gio-2.0 vala-dbus.vala $ ./vala-dbus-demo
vala-dbus-demo
运行后不会中止,由于它借助 Vala 提供的主事件循环变成了一个始终在后台运行的程序。
这时,再打开一个终端,执行如下命令:
$ dbus-send --type=method_call \ --dest=org.example.DemoService \ /org/example/demo \ org.example.DemoService.SomeMethodSender \ string:'hello world'
那个正在运行 vala-dbus-demo
的终端便会做出响应:
eureka! counter = 1, 'hello world' message from sender :1.514
摩尔定律失效了,多核时代了,大数据时代了,并行/并发计算是王道了,函数式编程的机遇来了……这些口号已经喊了多少年了?
个人多核是这样用的,一个核心在刷网页,两三个核心有时在更新个人 Gentoo 系统(我设置了 make -j3
)。谁说多核时代就必须让每一个程序都是多线程,同时运行几个进程不行么?多线程的程序已经运行了几十年了,早在 CPU 单核时代它们已经在运行了……
这个世界,也许任何一个行业不会比 IT 业更善于吹牛,并且吹牛的人本身都信了……即便每一个 CPU 都是单核的,不少台机器构成的分布式网络计算也已经搞了几十年了……若是你用 Gentoo,很容易就用到分布式计算了,例如 distcc,你可让不少个别人的机器为你的机器编译软件包。
Haskell 依然没多少人用,Lisp 依然还停留在 SICP 里,你们还在一边说着 Rust 与 Nim 的爹不怎么样,一边在黑爹很厉害的 go 语言……
无论怎么样,C11 标准中的多线程,如今 GCC 依然不支持。C++ 11 的多线程彷佛已经支持了,可是支持与不支持还有什么区别么?连 Vala 这芝麻大的语言都支持多线程:
using GLib; public class MyThread : Object { public int x_times { get; private set; } public MyThread (int times) { this.x_times = times; } public int run () { for (int i = 0; i < this.x_times; i++) { stdout.printf ("ping! %d/%d\n", i + 1, this.x_times); Thread.usleep (10000); } // return & exit have the same effect Thread.exit (42); return 43; } } public static int main (string[] args) { // Check whether threads are supported: if (Thread.supported () == false) { stderr.printf ("Threads are not supported!\n"); return -1; } try { // Start a thread: MyThread my_thread = new MyThread (10); Thread<int> thread = new Thread<int>.try ("My fst. thread", my_thread.run); // Wait until thread finishes: int result = thread.join (); // Output: `Thread stopped! Return value: 42` stdout.printf ("Thread stopped! Return value: %d\n", result); } catch (Error e) { stdout.printf ("Error: %s\n", e.message); } return 0; }
这个世界上,没有任何一个行业会比 IT 业善于吹牛!整个机械设计制造及其自动化只是沉默于牛顿力学与电学基本定律的基础之上,而 IT 业有过程式编程,面向对象编程,设计模式,泛型编程,函数式编程,并行/并发编程,同步/异步编程,还有神经网络,遗传算法,机器学习,特征识别,数据挖掘,还有分布式计算,网格计算,云计算,GPU 计算……整个世界非 IT 业的名词术语加起来不见得比 IT 业多。
总之,Vala 很容易的就实现了多线程编程……由于 GLib 封装了 pthread 与 Windows 的多线程机制,GLib 与 GObject 就是 Vala 世界的牛顿力学与电学基本定律。
当一个线程调用的一个异步函数时,该函数会当即返回尽管其规定的任务尚未完成,这样线程就会执行异步函数的下一条语句,而不会被挂起。
异步函数所规定的工做是如何被完成的呢?固然是经过一个新的线程完成的。那么这个新的线程是从哪里来的呢?咱们能够在异步函数中新建立一个线程。例如:
async void list_dir() { var dir = File.new_for_path (Environment.get_home_dir()); try { var e = yield dir.enumerate_children_async( FileAttribute.STANDARD_NAME, 0, Priority.DEFAULT, null); while (true) { var files = yield e.next_files_async( 10, Priority.DEFAULT, null); if (files == null) { break; } foreach (var info in files) { print("%s\n", info.get_name()); } } } catch (Error err) { warning("Error: %s\n", err.message); } } void main() { var loop = new MainLoop(); list_dir.begin((obj, res) => { list_dir.end(res); loop.quit(); }); loop.run(); }
BTW,Vala 提供了异常机制,即 try ... cactch
。
Vala 充分利用了 GObject 的内存引用计数机制,实现了垃圾自动回收,亦即 GC。
没有任何悬念,凡是基于引用计数的垃圾内存回收机制都没法处理循环引用状况,而解决这个问题的主流办法就是借助『弱引用』。不解决这个问题可不能够?
我以为没什么不能够的,就连 C++ 领域的大神(微软的人且身兼 C++ 标准委员会的委员)都以为内存泄漏一点也是无所谓的,详见:http://flyingfrogblog.blogspot.co.uk/2013/10/herb-sutters-favorite-c-10-liner.html
可是,Vala 依然中规中矩的借助弱引用来解决这个问题。
若是人类都不想本身去作垃圾回收这种脏活,那就意味着世界上最好的 GC 算法也没法回收它没法回收的一部份内存。
与内存的紧俏相比,多核时代……即便有 10000 个核也不如弱引用管用。
我打算洗洗睡了,不管有多少个核,不管有多少种内存回收算法,也没法解决 Linux 世界的中文输入法问题。
你知道我要打这些字的前提是什么吗?我要 pkill fcitx
,而后 source ~/.xprofile
,而后再 fcitx &
, 最后再 emacs my.markdown
。别问我为何,如今我不这样作我就没法在 GNOME 3 环境中的 Emacs 里输入中文。
没有任何悬念,中文输入永远都是中文 Linux 用户的痛,除非内核层面就原生的支持各国语言的输入法。Linus 能够对 Nvidia 竖中指,我却不敢对 Linus 竖中指,我甚至不敢对英语竖中指。好久之前,我花了好几顿饭钱去考英文四六级,却不懂中国的文言文。
酒喝多了,就不会再有逻辑。