Crystal 生日快樂!
昨天是 Crystal 一歲生日。耶! :-)
自從我們開始這個相當有雄心的專案以來,發生了很多事情,而且還有很多事情要做。
雖然它的語法與 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 中,方法可以多載。多載來自引數的數量、類型限制和方法是否 *yield*。
# 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
正規表示式
正規表示式目前使用 C 綁定到 PCRE 程式庫來實作。同樣,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,且沒有任何額外負擔:不涉及配置或閉包。
可以使用 instance_eval(&block) 在 Ruby 中實現類似的功能,但目前我們發現以這種方式實現它更容易,而且可能更容易使用。
規格
我們建立了一個 非常小的 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 中編程的感覺以及需要改進的地方(除錯和分析出現在我們的腦海中)。他們稱之為「自我體驗」:-)