給 Rubyist 的 Crystal¶
儘管 Crystal 的語法類似 Ruby,但 Crystal 是一種不同的語言,而不是另一個 Ruby 實作。基於這個原因,而且主要是因為它是一種編譯過的、靜態型別語言,與 Ruby 相比,該語言有一些很大的差異。
Crystal 作為編譯語言¶
使用 crystal
指令¶
如果您有一個程式 foo.cr
# Crystal
puts "Hello world"
當您執行以下其中一個指令時,您將得到相同的輸出
$ crystal foo.cr
Hello world
$ ruby foo.cr
Hello world
它看起來像是 crystal
解譯檔案,但實際發生的情況是,檔案 foo.cr
首先被編譯成一個臨時可執行檔,然後執行這個可執行檔。這種行為在開發週期中非常有用,因為您通常會編譯一個檔案並希望立即執行它。
如果您只想編譯它,您可以使用 build
指令
$ crystal build foo.cr
這會建立一個 foo
可執行檔,然後您可以使用 ./foo
執行它。
請注意,這會建立一個未經最佳化的可執行檔。若要最佳化它,請傳遞 --release
標誌
$ crystal build foo.cr --release
當撰寫基準測試或測試效能時,請始終記得在發佈模式下編譯。
您可以使用不帶引數的 crystal
或使用帶指令但不帶引數的 crystal
(例如,crystal build
會列出可用於該指令的所有標誌)來檢查其他指令和標誌。或者,您可以閱讀手冊。
型別¶
布林值¶
true
和 false
的型別是 Bool
,而不是 TrueClass
或 FalseClass
類別的實例。
整數¶
對於 Ruby 的 Fixnum
型別,請使用 Crystal 的整數型別 Int8
、Int16
、Int32
、Int64
、UInt8
、UInt16
、UInt32
或 UInt64
中的一個。
如果 Ruby Fixnum
上的任何運算超出其範圍,則該值會自動轉換為 Bignum
。Crystal 將會在溢位時引發 OverflowError
。例如
x = 127_i8 # An Int8 type
x # => 127
x += 1 # Unhandled exception: Arithmetic overflow (OverflowError)
Crystal 的標準程式庫提供具有任意大小和精度的數字型別:BigDecimal
、BigFloat
、BigInt
、BigRational
。
請參閱有關 整數的語言參考。
正規表示式¶
不支援全域變數 $`
和 $'
(但存在 $~
和 $1
、$2
、...)。請使用 $~.pre_match
和 $~.post_match
。閱讀更多。
簡化的實例方法¶
在 Ruby 中,有幾個方法可以執行相同的操作,而在 Crystal 中,可能只有一個。具體來說
Ruby 方法 | Crystal 方法 |
---|---|
Enumerable#detect |
Enumerable#find |
Enumerable#collect |
Enumerable#map |
Object#respond_to? |
Object#responds_to? |
length 、size 、count |
size |
省略的語言結構¶
當 Ruby 有一些替代結構時,Crystal 只有一個。
- 缺少後置
while
/until
。但請注意,作為後綴的 if 仍然可用 and
和or
:請改用&&
和||
,並使用適當的括號來表示優先順序- Ruby 有
Kernel#proc
、Kernel#lambda
、Proc#new
和->
,而 Crystal 使用Proc(*T, R).new
和->
(請參閱 此處 作為參考)。 - 對於
require_relative "foo"
,請使用require "./foo"
陣列無自動展開且強制執行最大區塊引數數量¶
[[1, "A"], [2, "B"]].each do |a, b|
pp a
pp b
end
將會產生類似以下的錯誤訊息
in line 1: too many block arguments (given 2, expected maximum 1)
但是,省略不需要的引數是可以的(就像在 Ruby 中一樣),例如
[[1, "A"], [2, "B"]].each do # no arguments
pp 3
end
或者
def many
yield 1, 2, 3
end
many do |x, y| # ignoring value passed in for "z" is OK
puts x + y
end
元組有自動展開
[{1, "A"}, {2, "B"}].each do |a, b|
pp a
pp b
end
將會傳回您預期的結果。
您也可以明確解壓縮以獲得與 Ruby 的自動展開相同的結果
[[1, "A"], [2, "B"]].each do |(a, b)|
pp a
pp b
end
以下程式碼也可以運作,但最好使用前者。
[[1, "A"], [2, "B"]].each do |e|
pp e[0]
pp e[1]
end
#each
回傳 nil¶
在 Ruby 中,.each
會針對許多內建集合(如 Array
和 Hash
)傳回接收器,這允許從該接收器鏈接方法,但這可能會導致 Crystal 中的一些效能和程式碼產生問題,因此不支援該功能。或者,可以使用 .tap
。
Ruby
[1, 2].each { "foo" } # => [1, 2]
Crystal
[1, 2].each { "foo" } # => nil
[1, 2].tap &.each { "foo" } # => [1, 2]
反射與動態求值¶
省略 Kernel#eval()
和奇怪的 Kernel#autoload()
。也省略了物件和類別內省方法 Object#kind_of?()
、Object#methods
、Object#instance_methods
和 Class#constants
。
在某些情況下,可以使用巨集進行反射。
語意差異¶
單引號與雙引號字串¶
在 Ruby 中,字串文字可以用單引號或雙引號分隔。Ruby 中的雙引號字串會受到文字內的變數插值影響,而單引號字串則不會。
在 Crystal 中,字串文字僅用雙引號分隔。單引號的作用類似於 C 語言之類的字元文字。如同 Ruby 一樣,字串文字內存在變數插值。
總之
X = "ho"
puts '"cute"' # Not valid in crystal, use "\"cute\"", %{"cute"}, or %("cute")
puts "Interpolate #{X}" # works the same in Ruby and Crystal.
不支援 Ruby 或 Python 的三引號字串文字,但字串文字可以嵌入換行符號
"""Now,
what?""" # Invalid Crystal use:
"Now,
what?" # Valid Crystal
Crystal 支援許多百分比字串文字。
[]
和 []?
方法¶
在 Ruby 中,如果找不到該索引/鍵的元素,[]
方法通常會傳回 nil
。例如
# Ruby
a = [1, 2, 3]
a[10] #=> nil
h = {a: 1}
h[1] #=> nil
在 Crystal 中,以下情況會拋出例外。
# Crystal
a = [1, 2, 3]
a[10] # => raises IndexError
h = {"a" => 1}
h[1] # => raises KeyError
之所以會有這個改變,是因為如果每次存取 Array
或 Hash
都可能回傳 nil
作為潛在值,那麼程式撰寫將會非常麻煩。這樣是行不通的。
# Crystal
a = [1, 2, 3]
a[0] + a[1] # => Error: undefined method `+` for Nil
如果您希望在找不到索引/鍵時取得 nil
,可以使用 []?
方法。
# Crystal
a = [1, 2, 3]
value = a[4]? # => return a value of type Int32 | Nil
if value
puts "The number at index 4 is : #{value}"
else
puts "No number at index 4"
end
[]?
只是個普通的方法,您可以(也應該)為類似容器的類別定義它。
另一件要知道的事是,當您這樣做時:
# Crystal
h = {1 => 2}
h[3] ||= 4
程式實際上會被轉換成這樣:
# Crystal
h = {1 => 2}
h[3]? || (h[3] = 4)
也就是說,[]?
方法是用來檢查索引/鍵是否存在。
正如 []
不會回傳 nil
一樣,某些 Array
和 Hash
方法也不會回傳 nil,如果找不到元素則會拋出例外:first
、last
、shift
、pop
等。針對這些方法,也提供了問號方法來取得 nil
的行為:first?
、last?
、shift?
、pop?
等。
慣例是 obj[key]
會回傳一個值,如果 key
不存在(「不存在」的定義取決於 obj
的類型)則會拋出例外;而 obj[key]?
會回傳一個值,如果 key
不存在則回傳 nil。
對於其他方法,則取決於情況。如果一個類型有名為 foo
的方法,並且另一個方法名為 foo?
,則表示 foo
在某些條件下會拋出例外,而 foo?
在相同條件下會回傳 nil。如果只有 foo?
變體而沒有 foo
,則它會回傳真值或假值(不一定是 true
或 false
)。
以下是上述所有情況的範例:
Array#[](index)
在超出範圍時會拋出例外,Array#[]?(index)
在這種情況下會回傳 nil。- 如果鍵不在雜湊中,
Hash#[](key)
會拋出例外,Hash#[]?(key)
在這種情況下會回傳 nil。 - 如果陣列為空(沒有「第一個」,因此「第一個」不存在),則
Array#first
會拋出例外,而Array#first?
在這種情況下會回傳 nil。pop/pop?、shift/shift?、last/last? 也是如此。 - 有
String#includes?(obj)
、Enumerable#includes?(obj)
和Enumerable#all?
,它們都沒有非問號變體。先前的方法確實會回傳 true 或 false,但這不是必要條件。
for
迴圈¶
不支援 for
迴圈。相反地,我們鼓勵您使用 Enumerable#each
。如果您仍然需要 for
,您可以透過巨集新增它們。
macro for(expr)
{{expr.args.first.args.first}}.each do |{{expr.name.id}}|
{{expr.args.first.block.body}}
end
end
for i ∈ [1, 2, 3] do # You can replace ∈ with any other word or character, just not `in`
puts i
end
# note the trailing 'do' as block-opener!
方法¶
在 Ruby 中,以下程式碼會拋出參數錯誤:
def process_data(a, b)
# do stuff...
end
process_data(b: 2, a: "one")
這是因為在 Ruby 中,process_data(b: 2, a: "one")
是 process_data({b: 2, a: "one"})
的語法糖。
在 Crystal 中,編譯器會將 process_data(b: 2, a: "one")
視為以具名引數 b: 2
和 a: "one"
呼叫 process_data
,這與 process_data("one", 2)
相同。
屬性¶
Ruby 的 attr_accessor
、attr_reader
和 attr_writer
方法被具有不同名稱的巨集取代。
Ruby 關鍵字 | Crystal |
---|---|
attr_accessor |
property |
attr_reader |
getter |
attr_writer |
setter |
範例
getter :name, :bday
此外,Crystal 還為可為空值或布林值的實例變數新增了存取器巨集。它們的名稱中都有問號 (?
)。
Crystal |
---|
property? |
getter? |
範例
class Person
getter? happy = true
property? sad = true
end
p = Person.new
p.sad = false
puts p.happy?
puts p.sad?
即使這是用於布林值,您也可以指定任何類型。
class Person
getter? feeling : String = "happy"
end
puts Person.new.feeling?
# => happy
在文件中閱讀更多關於 getter? 和/或 property? 的資訊。
一致的點表示法¶
例如,Ruby 中的 File::exists?
在 Crystal 中變為 File.exists?
。
Crystal 關鍵字¶
Crystal 新增了一些新的關鍵字,這些關鍵字仍然可以用作方法名稱,但需要使用點號明確呼叫:例如 self.select { |x| x > "good" }
。
可用的關鍵字¶
abstract do if nil? select union
alias else in of self unless
as elsif include out sizeof until
as? end instance_sizeof pointerof struct verbatim
asm ensure is_a? private super when
begin enum lib protected then while
break extend macro require true with
case false module rescue type yield
class for next responds_to? typeof
def fun nil return uninitialized
私有方法¶
Crystal 要求每個私有方法都必須加上 private
關鍵字前綴。
private def method
42
end
從 Ruby 到 Crystal 的雜湊語法¶
Crystal 引入了 Ruby 中沒有的資料類型,即 NamedTuple
。
通常在 Ruby 中,您可以使用幾種語法定義雜湊:
# A valid ruby hash declaration
{
key1: "some value",
some_key2: "second value"
}
# This syntax in ruby is shorthand for the hash rocket => syntax
{
:key1 => "some value",
:some_key2 => "second value"
}
在 Crystal 中,情況並非如此。需要使用 Hash
火箭符號 =>
來在 Crystal 中宣告雜湊。
但是,Ruby 中的 Hash
簡寫語法會在 Crystal 中建立 NamedTuple
。
# Creates a valid `Hash(Symbol, String)` in Crystal
{
:key1 => "some value",
:some_key2 => "second value",
}
# Creates a `NamedTuple(key1: String, some_key2: String)` in Crystal
{
key1: "some value",
some_key2: "second value",
}
NamedTuple
和常規的 Tuple
具有固定大小,因此它們最適合用於在編譯時已知大小的資料結構。
偽常數¶
Crystal 提供了一些偽常數,這些常數提供有關正在執行的原始碼的反思資料。
Crystal | Ruby | 說明 |
---|---|---|
__FILE__ |
__FILE__ |
目前正在執行的 Crystal 檔案的完整路徑。 |
__DIR__ |
__dir__ |
目前正在執行的 Crystal 檔案所在的目錄的完整路徑。 |
__LINE__ |
__LINE__ |
目前正在執行的 Crystal 檔案中的目前行號。 |
__END_LINE__ |
- | 呼叫區塊的結尾行號。只能用作方法參數的預設值。 |
提示
關於 __DIR__
與 __dir__
的進一步閱讀
適用於 Ruby Gems 的 Crystal Shards¶
許多流行的 Ruby gem 已移植或在 Crystal 中重寫。 這裡有一些等效於 Ruby Gems 的 Crystal Shards。
有關 Ruby 和 Crystal 之間差異的其他問題,請造訪 FAQ。