面向 Ruby 开发者的 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
将列出可与该命令一起使用的所有标志)来检查其他命令和标志。或者,您可以阅读 手册。
类型¶
Bool¶
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"
数组没有自动 splat 以及强制最大代码块参数数量¶
[[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
元组有自动 splat
[{1, "A"}, {2, "B"}].each do |a, b|
pp a
pp b
end
将返回您期望的结果。
您还可以显式解包以获得与 Ruby 的自动 splat 相同的结果
[[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 gems 已在 Crystal 中移植或重写。 以下是 Ruby Gems 的等效 Crystal Shards 列表。
有关 Ruby 和 Crystal 之间差异的其他问题,请访问 常见问题解答。