水晶生日快乐!
昨天水晶迎来了一周岁生日。耶!:-)
自从我们开始这个雄心勃勃的项目,发生了很多事情,还有很多工作要做。
虽然它的语法与 Ruby 非常相似,但有很多区别,而且每一天差距都在拉大。
以下是我们目前语言中包含内容的摘要。
高效的代码生成
Crystal **不是** 解释型的。它没有虚拟机。代码通过使用 LLVM 编译成原生机器码。
你不需要像在静态编译语言中那样指定变量、实例变量或方法参数的类型。相反,Crystal 尽可能聪明,为你推断类型。
基本类型
基本类型映射到原生机器类型。
true # Bool
1 # Int32
1_u64 # UInt64
1.5 # Float64
1.5_f32 # Float32
'a' # Char
ASCII 字符串
它们有多种形式,与 Ruby 中一样,也支持插值。
a = "World"
b = "Hello #{a}" #=> "Hello World"
我们仍然需要决定处理不同编码的最佳方式,所以这只是一个临时的实现。
你知道 String 是用 Crystal 本身实现的吗?只需要一小段神奇的代码来为它设置大小和指向字符缓冲区的指针,但其他所有内容都是建立在它的基础之上的。
符号
:foo
在运行时,每个符号都由一个唯一的整数表示。一个整数到字符串的表被构建用来实现 Symbol#to_s(但目前没有方法来执行 String#intern)。
联合类型
你不需要指定变量的类型。如果变量被赋值多种类型,它将在编译时具有这些类型。在运行时,它将只具有其中一种。
if some_condition
a = 1
else
a = 1.5
end
# Here a can be an Int32 or Float64
a.abs # Ok, both Int32 and Float64 define the 'abs' method without arguments
a.succ # Error, Float64 doesn't have a 'succ' method
你可以使用 “is_a?” 来检查类型
if a.is_a?(Int32)
a.succ # Ok, here a can only be an Int32
end
你甚至可以使用 “responds_to?”
if a.responds_to?(:succ)
a.succ # Ok
end
方法
在 Crystal 中,方法可以重载。重载来自方法的参数数量、类型限制和yieldness。
# foo 1
def foo(x, y)
x + y
end
# foo 2
def foo(x)
end
# foo 3
def foo(x : Float)
end
def foo(x)
yield
end
foo 1, 1 # Invokes foo 1
foo 1 # Invokes foo 2
foo 1.5 # Invokes foo 3
foo(1) { } # Invokes foo 4
将它与需要在运行时检查方法参数数量、是否提供了块或参数类型进行对比:我们认为这更易读且更高效。
此外,运行时不会抛出 “参数数量错误” 异常:在 Crystal 中,这是一个编译时错误。
最后一点,基于方法是否 yield 的重载可能会改变,需要重新考虑。
类
不需要指定实例变量的类型,但分配给实例变量的所有类型将使该变量具有联合类型。
class Foo
# We prefer getter, setter and property over
# attr_reader, attr_writer and attr_accessor
getter :value
# Note the @value at the argument: this is similar to Coffeescript
# and we think it's a nice syntax addition.
def initialize(@value)
end
end
foo = Foo.new(1)
foo.value.abs # Ok
# At this point @value is an Int32
foo2 = Foo.new('a')
# Because of the last line, @value is now an Int32 or Char.
# Char doesn't have an 'abs' method, so a compile time error is issued.
如果你确实需要具有不同 @value 类型的不同 Foo 类,你可以使用泛型类
class Foo(T)
getter :value
def initialize(@value : T)
end
end
foo = Foo.new(1) # T is inferred to be an Int32, and foo is a Foo(Int32)
foo.value.abs # Ok
foo2 = Foo.new('a') # T is inferred to be a Char, and foo2 is a Foo(Char)
foo2.value.ord # Ok
# You can also explicitly specify the generic type variable
foo3 = Foo(String).new("hello")
Array 和 Hash 也是泛型类,但它们也可以使用字面量进行构建。当指定元素时,泛型类型变量会被推断出来。如果没有指定元素,你必须告诉 Crystal 泛型类型变量。
a = [1, 2, 3] # a is an Array(Int32)
b = [1, 1.5, 'a'] # b is an Array(Int32 | Float64 | Char)
c = [] of String # c is an Array(String), same as doing Array(String).new
d = {1 => 2, 3 => 4} # d is a Hash(Int32, Int32)
e = {} of String => Bool # e is a Hash(String, Bool), same as doing Hash(String, Bool).new
没错,Array 和 Hash 完全是用 Crystal 实现的。这使得任何人都可以轻松地参与到这些类的开发中。
我们真的想避免必须指定类型变量。实际上,我们想避免必须区分泛型类和非泛型类。我们花了很长时间(也许三个月?)试图让它高效地工作,但我们做不到。也许它没有有效的解决方案。至少我们没有找到任何能够做到的人。泛型类型是一个小小的牺牲,但作为回报,我们获得了更快的编译时间。
模块
当然,Crystal 中也存在模块,它们也可以是泛型的。
块
目前,块无法保存到变量或传递给另一个方法。这意味着我们仍然缺乏闭包。如果我们想要智能类型推断,这并不容易,所以我们需要一些时间来仔细考虑。
与 C 的绑定
你可以在 Crystal 中声明与 C 的绑定,无需使用 C、制作包装器或使用其他语言。例如,这是 SDL 绑定的部分内容
lib LibSDL("SDL")
INIT_TIMER = 0x00000001_u32
INIT_AUDIO = 0x00000010_u32
# ...
struct Rect
x, y : Int16
w, h : UInt16
end
# ...
union Event
type : UInt8
key : KeyboardEvent
end
# ...
fun init = SDL_Init(flags : UInt32) : Int32
end
value = LibSDL.init(LibSDL.INIT_TIMER)
指针
你可以分配内存并通过将指针作为语言中的类型来与 C 交互。
values = Pointer(Int32).malloc(10) # Ask for 10 ints
正则表达式
正则表达式目前使用与 PCRE 库的 C 绑定来实现。同样,Regexp
完全是用 Crystal 编写的。
"foobarbaz" =~ /(.+)bar(.+)/ #=> 0
$1 #=> "foo"
$2 #=> "baz
范围
再次,用 Crystal 实现。
异常
你可以引发和捕获异常。它们使用 libunwind 实现。我们仍然缺乏堆栈跟踪中的行号和文件名。
导出 C 函数
你可以声明要导出到 C 的函数,这样你就可以编译 Crystal 代码并在 C 中使用它(虽然仍然没有生成目标文件的编译器标志,但应该很容易实现)。
fun my_c_function(x : Int32) : Int32
"Yay, I can use string interpolation and call it #{x} times from C"
end
宏
这是语言的一个实验性功能,你可以从 AST 节点生成源代码。
macro generate_method(name, value)
"
def #{name}
#{value}
end
"
end
generate_method :foo, 1
puts foo # Prints: 1
getter、setter 和 property 宏以类似的方式实现,但我们一直在考虑一种更强大、更简单的方法来实现相同的功能,因此该功能可能会消失。
带作用域的 yield
类似于 yield,但会更改块的隐式作用域。
def foo
# -1 becomes the default scope where methods
# are looked up in the given block
-1.yield(2)
end
foo do |x|
# Invokes "abs" on -1
puts abs + x #=> 3
end
这允许编写零开销的强大 DSL:不涉及分配或闭包。
类似的功能可以通过 Ruby 中的 instance_eval(&block) 来实现,但目前我们发现用这种方式实现更简单,也许更容易使用。
规范
我们已经构建了一个非常小的 RSpec 克隆,我们正在使用它来测试标准库以及新的编译器。以下是用 Array 类编写的规范示例
require "spec"
describe "Array" do
describe "index" do
it "performs without a block" do
a = [1, 2, 3]
a.index(3).should eq(2)
a.index(4).should eq(-1)
end
it "performs with a block" do
a = [1, 2, 3]
a.index { |i| i > 1 }.should eq(1)
a.index { |i| i > 3 }.should eq(-1)
end
end
end
因此,Crystal 使编写测试变得极其容易,同时为你提供了类型安全性。两全其美。
路线图
还有很多事情需要实现,我们还有很多想法要尝试。
- 我们仍然需要一个垃圾收集器,我们想要一个高效的并发垃圾收集器。
- 我们希望拥有类似于 Erlang 或 Go 的并发原语。
- 我们希望拥有更好的元编程。
- 我们可能会拥有结构体,不仅用于 C 绑定,还用于编写高效的包装器并分配更少的内存。
- 我们想要元组、命名元组和命名参数。
但我们现在最想要的是用 Crystal 来编写编译器。一旦我们做到了,我们就再也不需要 Ruby 了。我们不需要维护编译器的两个实现。编译时间将大幅度缩短(我们希望!)。
而且,由于这个原因,语言很可能会有很大发展,我们也将学到很多关于用 Crystal 编程的感觉以及需要改进的地方(调试和分析浮现在脑海中)。他们称之为狗食法:-)