编程语法
更新日期:
- 1. 说明
- 2. 语法教程
- 3. 异常处理
说明
本节内容较多,老司机打算用最简洁的语言带你快速飙车,看完保证不翻车^_^
友情提示:先装好icr 以方便验证及练习 https://github.com/crystal-community/icr
语法教程
注释
使用#做单行注释,目前仅支持单行。
1 | # This is a comment |
变量类型
Nil
代表空值, 类似其它语言中的null
布尔值
仅有两值 true, false
整数
分别有4个无符号整型 和4个整型
Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64 , 附表1
2
3
4
5
6
7
8
9Type Length Minimum Value Maximum Value
Int8 8 -128 127
Int16 16 −32,768 32,767
Int32 32 −2,147,483,648 2,147,483,647
Int64 64 −263 263 - 1
UInt8 8 0 255
UInt16 16 0 65,535
UInt32 32 0 4,294,967,295
UInt64 64 0 264 - 1
在不带后缀的情况下,整型匹配的范围是Int32 ~ Int64 和 UInt64 ,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
171
1_i8
1_i16
1_i32
1_i64
1_u8
1_u16
1_u32
1_u64
+10
-20
2147483648
9223372036854775808
后缀前面的 是可选的, 能使代码更具可读性:1
1_000_000 # 好过于 1000000
二进制数以0b开头 :1
0b1101 # == 13
八进制以0o开头:1
0 # == 83
十六进制以0x开头1
20xFE012D # == 16646445
0xfe012d # == 16646445
浮点数
有两种 Float32 和 Float64, 没有符号限制, 可以+/- ,跟整型一样可以加后缀 没有后缀默认为 Float641
2
3
4
5
6
7
8
9
101.0
1.0_f32
1_f32
1e10
1.5e10
1.5e-7
+1.3
-0.5
字符
一个字符代表一个32位的Unicode code point, 使用单引号创建1
2
3
4
5'a'
'z'
'0'
'_'
'あ'
反斜杠是一个特殊的字符, 可以用于转义或正则 也可以用于表达一个16进制整数在unicode codepoint中的字符
A backslash denotes a special character, which can either be a named escape sequence or a numerical representation of a unicode codepoint.
Available escape sequences:1
2
3
4
5
6
7
8
9
10
11'\''
'\\'
'\b'
'\e'
'\f'
'\n'
'\r'
'\t'
'\v'
'\uNNNN'
'\u{NNNN...}'
(此处翻车)A backslash followed by a u denotes a unicode codepoint. It can either be followed by exactly four hexadecimal characters representing the unicode bytes (\u0000 to \uFFFF) or a number of one to six hexadecimal characters wrapped in curly braces (\u{0} to \u{10FFFF}.1
2
3'\u0041'
'\u{41}'
'\u{1F52E}'
字符串
一个字符串代表一个不可更改的UTF8字符序列,使用双引号包含。
转义
A backslash denotes a special character inside a string, which can either be a named escape sequence or a numerical representation of a unicode codepoint.
1 | "\"" # double quote |
A backslash followed by at most three digits denotes a code point written in octal:1
2
3
4"\101" # => "A"
"\123" # => "S"
"\12" # => "\n"
"\1" # string with one character with code point 1
A backslash followed by a u denotes a unicode codepoint. It can either be followed by exactly four hexadecimal characters representing the unicode bytes (\u0000 to \uFFFF) or a number of one to six hexadecimal characters wrapped in curly braces (\u{0} to \u{10FFFF}.1
2
3"\u0041"
"\u{41}"
"\u{1F52E}"
One curly brace can contain multiple unicode characters each separated by a whitespace.1
"\u{48 45 4C 4C 4F}"
插值
字符串在运行时允许嵌入表达式1
2
3a = 1
b = 2
"sum: #{a} + #{b} = #{a + b}" # => "sum: 1 + 2 = 3"
字符串中允许插入任何表达式,但为了保证程序可读性,表达式尽量简短。
在#前加入反斜杠可以禁止插值, 也可以使用表达式: %q()1
2"\#{a + b}" # => "#{a + b}"
%q(#{a + b}) # => "#{a + b}"
通过调用String::Builder以及在每个#{…}之后调用Object#to_s(IO)实现字符串插值。表达式”sum: #{a} + #{b} = #{a + b}”与下方的等价1
2
3
4
5
6
7
8String::Builder.new do |io|
io << "sum: "
io << a
io << " + "
io << b
io << " = "
io << a + b
end
百分符字符串
除了双引号之外,字符串还支持百分号(%)+一双分隔符的形式。可以使用的分隔符有(),[],{},<>,以及|| 。这可以用来处理字符串中带有双引号的情况。1
2
3
4
5%(hello ("world"))
%[hello ["world"]]
%{hello {"world"}}
%<hello <"world">>
%|hello "world"|
%q表示不转义插值, %Q则等价于%1
2
3name = "world"
%q(hello \n #{name}) # => "hello \\n \#{name}"
%Q(hello \n #{name}) # => "hello \n world"
字符串允许换行
1 | "hello |
一个字符串可以被分割成多行, 只需在每行的末尾加上反斜杠1
2
3"hello " \
"world, " \
"no newlines" # same as "hello world, no newlines"
1 | "hello \ |
Heredoc
heredoc以 <<- 紧跟一个标识符开始, 以标识符开头的一个新行作为结束。1
2
3
4
5<<-XML
<parent>
<child />
</parent>
XML
heredoc中允许调用方法或使用函数,变量等1
2
3
4
5
6
7
8
9
10
11<<-SOME
hello
SOME.upcase # => "HELLO"
def upcase(string)
string.upcase
end
upcase(<<-SOME
hello
SOME) # => "HELLO"
如果要在heredoc中禁用转义和插值,可以在定义heredoc的标识符上加上单引号1
2
3<<-'HERE'
hello \n #{world}
HERE # => "hello \n #{world}"
符号
符号是一个被命名的常量,并且不能用整数赋值 。在内部,一个符号被表示成一个 Int32。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37:hello
:good_bye
# With spaces and symbols
:"symbol with spaces"
# Ending with question and exclamation marks
:question?
:exclamation!
# For the operators
:+
:-
:*
:/
:==
:<
:<=
:>
:>=
:!
:!=
:=~
:!~
:&
:|
:^
:~
:**
:>>
:<<
:%
:[]
:[]?
:[]=
:<=>
:===
数组
数组是一个带数字索引的某个数据类型(type T)的值的一个集合。用方括号括起,用逗号分隔 。1
[1, 2, 3]
数组的类型可以是单一的 或多种类型的混合。1
2[1, 2, 3] # => Array(Int32)
[1, "hello", 'x'] # => Array(Int32 | String | Char)
规定单一类型的数组可以在数组定义时的末尾加上 “of type” ,空数组必须指明类型。1
2
3
4
5
6array_of_numbers = [1, 2, 3] of Number # => Array(Number)
array_of_numbers + [0.5] # => [1, 2, 3, 0.5]
array_of_int_or_string = [1, 3, 4] of Int32 | String # => Array(Int32 | String)
array_of_int_or_string + ["foo"] # => [1, 2, 3, "foo"]
[] of Int32 # => Array(Int32).new
带百分号的数组
可以用带百分号的方法来创建字符串数组和符号数组。1
2%w(one two three) # => ["one", "two", "three"]
%i(one two three) # => [:one, :two, :three]
Array风格的数组表示方式
使用大括号表示,元素之间使用逗号分隔。1
Array{1, 2, 3}
这种写法适用于任意类型的数组。
This literal can be used with any type as long as it has an argless constructor and responds to <<.1
2IO::Memory{1, 2, 3}
Set{1, 2, 3}
对于像IO::Memory这样不寻常的类型,等价于1
2
3
4array_like = IO::Memory.new
array_like << 1
array_like << 2
array_like << 3
对于像Set这样的普通类型, the generic type T is inferred from the types of the elements in the same way as with the array literal. The above is equivalent to:1
2
3
4array_like = Set(typeof(1, 2, 3)).new
array_like << 1
array_like << 2
array_like << 3
类型的参数可以作为类型名称的一部分显式的指定。1
Set(Number) {1, 2, 3}
哈希
1 | {1 => 2, 3 => 4} # Hash(Int32, Int32) |
哈希的键和值都可以包含多种数据类型。当创建一个空的哈希表时 ,必须分别指定键和值的数据类型。1
2{} of Int32 => Int32 # 等价于 Hash(Int32, Int32).new
{} # 此种玩法将报错
哈希风格的数据类型
你可以使用哈希数据跟其它数据类型一起玩耍。as long as they define an argless new method and a []= method:1
MyType{"foo" => "bar"}
If MyType is not generic, 上面等价于1
2
3tmp = MyType.new
tmp["foo"] = "bar"
tmp
If MyType is generic,上面等价于1
2
3tmp = MyType(typeof("foo"), typeof("bar")).new
tmp["foo"] = "bar"
tmp
一般数据类型也可以这么搞1
MyType(String, String) {"foo" => "bar"}
范围
1 | x..y # 包含末尾, in mathematics: [x, y] |
正则
1 | foo_or_bar = /foo|bar/ |
正则使用斜杠分隔 ,并且使用 PCRE语法 。
可以跟这些参数1
2
3
4
i: ignore case (PCRE_CASELESS)
m: multiline (PCRE_MULTILINE)
x: extended (PCRE_EXTENDED)
例子1
2
3r = /foo/imx
slash = /\//
r = %r(regex with slash: /) # 这是另一种写法
元组
创建和使用方法如下 , 创建空元组可以使用 Tuple.new1
2
3
4tuple = {1, "hello", 'x'} # Tuple(Int32, String, Char)
tuple[0] #=> 1 (Int32)
tuple[1] #=> "hello" (String)
tuple[2] #=> 'x' (Char)
要规定元组的数据类型,可以这么玩1
2
3
4
5# The type denoting a tuple of Int32, String and Char
Tuple(Int32, String, Char)
# An array of tuples of Int32, String and Char
Array({Int32, String, Char})
带名称的元组
1 | tuple = {name: "Crystal", year: 2011} # NamedTuple(name: String, year: Int32) |
过程定义
一个过程可以理解为一段特定功能的代码。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# A proc without arguments
->{ 1 } # Proc(Int32)
# A proc with one argument
->(x : Int32) { x.to_s } # Proc(Int32, String)
# A proc with two arguments:
->(x : Int32, y : Int32) { x + y } # Proc(Int32, Int32, Int32)
Proc(Int32, String).new { |x| x.to_s } # Proc(Int32, String) #另一种写法
# 接收一个Int32类型的参数, 返回一个String类型
Proc(Int32, String)
# A proc accepting no arguments and returning Void
Proc(Void)
# A proc accepting two arguments (one Int32 and one String) and returning a Char
Proc(Int32, String, Char)
参数的类型必须指定 ,except when directly sending a proc literal to a lib fun in C bindings.
返回值的类型会在proc过程中自动确定。
调用Proc
1 | proc = ->(x : Int32, y : Int32) { x + y } |
赋值
1 | # 赋值给一个本地变量 |
多次赋值
1 | name, age = "Crystal", 1 |
控制流
1 | if some_condition |
文件包含
大的程序一般都会按照一定的分割方式,将代码写到不同的文件中。使用时可以用require “…” 的方式来组合这些程序。
require接受一个字符串的参数 。
require “filename”
这种写法将会在 require 的路径中查找 filename
默认情况下 ,require路径是编译器指定的标准库路径,并且是相对于当前路径的一个叫做“lib”的路径。
查找的规则如下:
如果文件 “filename.cr”在 require路径中存在, 它将被召唤
如果存在一个”filename”的目录并且在此目录中存在 “filename.cr”,它将被召唤
否则 程序抛出异常1
require "foo/bar/baz"
require “./filename”
这种召唤使用的路径是相对于当前调用require的程序文件的。可以使用 ../ 或../../等进行上层目录的require1
require "./foo/bar/baz"
可以使用通配符进行require1
2require "foo/*" #将会require foo目录下的所有 .cr文件
require "foo/**" # 将会 require foo及其子目录下的所有 .cr文件
类和方法
在Crystal中,一切都是对象。
实例化,初始化和内存分配
1 | person = Person.new # 创建一个实例 |
方法和实例属性
Crystal的标准库定义了宏来使getter和setter变的简单。1
2
3
4
5
6
7
8
9
10
11
12class Person
property age
getter name : String
def initialize(@name)
@age = 0
end
end
john = Person.new "John"
john.age = 32
john.age #=> 32
类的方法允许分开写,在编译的时候将会被合并到一个类中1
2
3
4
5
6
7
8
9
10
11class Person
def initialize(@name : String)
@age = 0
end
end
class Person
def become_older
@age += 1
end
end
注:方法重定义时,后面定义的方法将会覆盖之前的方法。
你可以通过调用 previous_def 在调用之前定义的方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Person
def become_older
@age += 1
end
end
class Person
def become_older
previous_def # 不带括号和参数的情况下 ,将接收当前方法调用时的参数 否则使用自定义参数。
@age += 2
end
end
person = Person.new "John"
person.become_older
person.age #=> 3
对象的属性也可以在构造子之外被初始化1
2
3
4
5
6class Person
@age = 0
def initialize(@name : String)
end
end
This will initialize @age to zero in every constructor. This is useful to avoid duplication, but also to avoid the Nil type when reopening a class and adding instance variables to it.
类型推断
看下面的例子1
2
3
4
5class Person
def initialize(@name)
@age = 0
end
end
@age毫无疑问是一个integer,但是@name的类型不好判断了。 编译器会结合所有用到Person类的地方对@name的类型进行判断,但是这么搞会有点问题:
对编译器不友好,也不利于增加代码的可读性 。随着项目代码量的增加,这个问题会变的严重。编译变慢,代码也看不懂了 。。。
Crystal有以下几种方法来杜绝这个问题
1.显式的注明类型1
2
3
4
5
6
7
8class Person
@name : String
@age : Int32
def initialize(@name)
@age = 0
end
end
2.定义时赋于具体的值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50class Person
def initialize
@name = "John Doe" # 用值墳充
@age = 0 # 用值墳充
end
end
class Person
def initialize
@address = Address.new("somewhere") #对象调用赋值
end
end
class Something
def initialize
@values = Array(Int32).new #数组
end
end
class Person
def initialize(name : String) #用参数赋值
@name = name
end
end
class Person
def initialize(@name : String) #同上等价
end
end
class Person
def initialize(name = "John Doe")
@name = name
end
end
class Person
def initialize(@name = "John Doe")
end
end
class Person
def initialize
@age = LibPerson.compute_default_age # 使用库方法的返回值
end
end
lib LibPerson
fun compute_default_age : Int32
end
联合类型
方法重载,多态
1 | class Person |
默认值和 命名参数
1 | class Person |
Splats and tuples
通过使用 * ,方法可以接收可变数量的参数。在方法内部 ,参数转化为元组。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24def sum(*elements)
total = 0
elements.each do |value|
total += value
end
total
end
sum 1, 2, 3 #=> 6
sum 1, 2, 3, 4.5 #=> 10.5
# elements is Tuple(Int32, Int32, Int32, Float64)
sum 1, 2, 3, 4.5
def sum(*elements, initial = 0) # 可变参数的后面还可以跟命名参数
total = initial
elements.each do |value|
total += value
end
total
end
sum 1, 2, 3 # => 6
sum 1, 2, 3, initial: 10 # => 16
Double splats and named tuples
双星号(**) 会匹配命名元组构成的参数中的 没有被匹配到的参数形成的元组1
2
3
4
5
6
7
8
9
10
11
12
13
14
15def foo(x, **other)
# Return the captured named arguments as a NamedTuple
other
end
foo 1, y: 2, z: 3 # => {y: 2, z: 3}
foo y: 2, x: 1, z: 3 # => {y: 2, z: 3}
# 将元组送入参数
def foo(x, y)
x - y
end
tuple = {y: 3, x: 10}
foo **tuple # => 7
静态类的属性
1 | class Parent |
模块
模块的定义有两个用途:
1.为定义新的数据类型,方法和常量提供命名空间
2.作为可以被其它代码块调用的代码片段 (as partial types that can be mixed in other types )
1 | module Curses |
自定义库文件时建议创建命名空间 以防止出现冲突。标准库没有使用命名空间,因为使用比较频繁 这样可以方便书写。
模块允许再调用别的模块。
模块不能被实例化。
作为代码片段时可以通过 include或extend来调用 。include 和extend 允许在代码的顶层调用,to avoid writing a namespace over and over
1 | module ItemsSize |
泛型
结构体
可以使用结构体替代Class来创建新的类型1
2
3
4
5
6struct Point
property x, y
def initialize(@x : Int32, @y : Int32)
end
end
结构体和类的区别
1.在结构体上调用new方法,将在栈上开辟空间而不是堆
2.结构体是传值调用而类是传址
3.结构体无法继承一个非抽象结构体
The last point has a reason to it: a struct has a very well defined memory layout. For example, the above Point struct occupies 8 bytes. If you have an array of points the points are embedded inside the array’s buffer:1
2# The array's buffer will have 8 bytes dedicated to each Point
ary = [] of Point
If Point is inherited, an array of such type must also account for the fact that other types can be inside it, so the size of each element must grow to accommodate that. That is certainly unexpected. So, non-abstract structs can’t be inherited. Abstract structs, on the other hand, will have descendants, so it’s expected that an array of them will account for the possibility of having multiple types inside it.
A struct can also include modules and can be generic, just like a class.
A struct is mostly used for performance reasons to avoid lots of small memory allocations when passing small copies might be more efficient.
So how do you choose between a struct and a class? The rule of thumb is that if no instance variable is ever reassigned, i.e. your type is immutable, you could use a struct, otherwise use a class.
常量
常量可以在任意地方定义 , 首字母必须大写 ,通常整个写成大写。1
2
3
4
5
6
7
8PI = 3.14
module Earth
RADIUS = 6_371_000
end
PI #=> 3.14
Earth::RADIUS #=> 6_371_000
常量允许调用方法,并且可以有复杂的逻辑封装。1
2
3
4
5
6
7
8
9TEN = begin
a = 0
while a < 10
a += 1
end
a
end
TEN #=> 10
内置常量
Crystal中内置了一些常量1
2
3
4
5
6
7__LINE__ 定义了程序中的当前行数,当它被声明为一个方法的参数时 它代表了在该方法中的行号。
__END_LINE__ is the line number of the end of the calling block. Can only be used as a default value to a method parameter.
__FILE__ 代表了当前被调用的程序文件的完整路径
__DIR__ 表示被调用程序文件所在的完整目录
枚举类型
进程和块
方法定义时可以接受带有yield关键字的代码块。1
2
3
4
5
6
7
8
9def twice
yield
yield
end
# 这里将会输出Hello两次
twice do
puts "Hello!"
end
定义时只需要在方法中加入yield关键词,也可以通过 & 符号显式的定义。1
2
3
4def twice(&block)
yield
yield
end
要调用方法和传入块代码时,只需要使用 do … 或 {…}的形式。1
2
3
4
5
6
7
8
9twice() do
puts "Hello!"
end
twice do
puts "Hello!"
end
twice { puts "Hello!" }
两者的区别在于 do … end and { … } is that do … end binds to the left-most call, while { … } binds to the right-most call:1
2
3
4
5
6
7
8
9
10
11foo bar do
something
end
foo(bar) do
something
end
foo bar { something }
foo(bar { something })
yield参数
1 | def twice |
捕获代码块
一段代码可以被捕获并转换成Block
定义一个block1
2
3
4
5
6def int_to_int(&block : Int32 -> Int32)
block
end
proc = int_to_int { |x| x + 1 }
proc.call(1) #=> 2
下面的例子定义一个块,作为回调来使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class Model
def on_save(&block)
@on_save_callback = block
end
def save
if callback = @on_save_callback
callback.call
end
end
end
model = Model.new
model.on_save { puts "Saved!" }
model.save # prints "Saved!"
# 上例没有指明 &block的类型, 用来表明 不接收任何参数 也不返回任何值
# 注:如果没有指明返回类型, 将不返回任何值
def some_proc(&block : Int32 ->)
block
end
proc = some_proc { |x| x + 1 }
proc.call(1) # void
break 和 next
return和break不能用在block中 ,使用next会退出block并返回值
with …yield
在block中无效
定义一个block和Proc,并将block放入proc中执行1
2
3
4
5
6
7
8
9
10def some_proc(&block : Int32 -> Int32)
block
end
x = 0
proc = ->(i : Int32) { x += i }
proc = some_proc(&proc)
proc.call(1) #=> 1
proc.call(10) #=> 11
x #=> 11
一个Proc也可以通过一个现成的方法来创建1
2
3
4
5
6def add(x, y)
x + y
end
adder = ->add(Int32, Int32)
adder.call(1, 2) #=> 3
block的转发(Block forwarding)
Closures
别名 (alias)
异常处理
抛出异常
1 | raise "OH NO!" |
自定义异常
1 | class MyException < Exception |
处理异常
使用 begin … rescue … end 的代码结构来处理异常:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60begin
raise "OH NO!"
rescue
puts "Rescued!"
end
# 可以在rescue后新增参数来接收详细的异常信息
begin
raise "OH NO!"
rescue ex
puts ex.message
end
# 处理具体的异常
begin
raise MyException.new("OH NO!")
rescue MyException
puts "Rescued MyException"
end
begin
raise MyException.new("OH NO!")
rescue ex : MyException
puts "Rescued MyException: #{ex.message}"
end
# 多重异常处理
begin
# ...
rescue ex1 : MyException
# only MyException...
rescue ex2 : MyOtherException
# only MyOtherException...
rescue
# any other kind of exception
end
begin
# ...
rescue ex : MyException | MyOtherException
# only MyException or MyOtherException
rescue
# any other kind of exception
end
# else
begin
something_dangerous
rescue
# execute this if an exception is raised
else
# execute this if an exception isn't raised
end
# ensure ,相当于finally , 不论有无异常 都会执行
begin
something_dangerous
ensure
puts "Cleanup..."
end
类型定义语法参考
当需要
1.指定限制类型
2.指定参数类型
3.声明变量
4.声明别名
5.定义新类型
6.认证 is_a? 的call调用
7.认证as表达式
8.认证sizeof表达式
9.认证instance_sizeof表达式
10.方法的返回值类型
一些常见的类型都有简洁的操作方法。这对于当需要书写C库绑定时很有用,并且也可以用于上面的所有情况。
常规类型的声明写法1
2
3Int32
My::Nested::Type
Array(String)
多种类型联合1
alias Int32OrString = Int32 | String
空值1
2
3alias Int32OrNil = Int32?
alias Int32OrNil = Int32 | ::Nil
指针1
2
3alias Int32Ptr = Int32*
alias Int32Ptr = Pointer(Int32)
静态数组1
2
3alias Int32_8 = Int32[8]
alias Int32_8 = StaticArray(Int32, 8)
元组1
2
3alias Int32StringTuple = {Int32, String}
# 等价于
alias Int32StringTuple = Tuple(Int32, String)
命名元组1
2
3alias Int32StringNamedTuple = {x: Int32, y: String}
# 等价于
alias Int32StringNamedTuple = NamedTuple(x: Int32, y: String)
过程(Proc)1
2
3
4
5
6
7
8
9
10
11alias Int32ToString = Int32 -> String
# 等价于
alias Int32ToString = Proc(Int32, String)
# To specify a Proc without arguments:
alias ProcThatReturnsInt32 = -> Int32
# To specify multiple arguments:
alias Int32AndCharToString = Int32, Char -> String
# Proc嵌套需要使用括号
alias ComplexProc = (Int32 -> Int32) -> String
self
self can be used in the type grammar to denote a self type. Refer to the type restrictions section.
class1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24def foo(x : Int32)
"instance"
end
def foo(x : Int32.class)
"class"
end
foo 1 # "instance"
foo Int32 # "class"
# class也可以用来创建一个class类型的数组
class Parent
end
class Child1 < Parent
end
class Child2 < Parent
end
ary = [] of Parent.class
ary << Child1
ary << Child2
下划线(Underscore)
下划线可以用来表示任何数据类型1
2
3
4
5
6
7# Same as not specifying a restriction, not very useful
def foo(x : _)
end
# A bit more useful: any two arguments Proc that returns an Int32:
def foo(x : _, _ -> Int32)
end
typeof
用于返回传入参数的类型(相同的类型不会重复)1
2typeof(1 + 2) # => Int32
typeof(1, "a") # => (Int32 | String)
类型反射
is_a?
用于判断变量在运行时的类型是否和表达式匹配1
2
3
4
5a = 1
a.is_a?(Int32) #=> true
a.is_a?(String) #=> false
a.is_a?(Number) #=> true
a.is_a?(Int32 | String) #=> true
nil?
判断表达式的值是否为nil1
2
3
4
5a = 1
a.nil? # => false
b = nil
b.nil? # => true
responds_to?
判断一个变量是否有参数指定的方法1
2
3a = 1
a.responds_to?(:abs) #=> true
a.responds_to?(:size) #=> false
as
as 用于约束一个表达式的值类型。参数为一个数据类型。
注意: as不能用于做数据类型转换。Methods on integers, floats and chars are provided for these conversions.1
2
3
4
5
6
7if some_condition
a = 1
else
a = "hello"
end
# a : Int32 | String
在上面 ,a的类型为 Int32 | String。 在if段中可以强制编译器指定一个类型(Int)。as采用运行时检查,如果a不是Int32,将抛出异常。1
2a_as_int = a.as(Int32)
a_as_int.abs # works, compiler knows that a_as_int is Int32
Converting between pointer types
The as pseudo-method also allows to cast between pointer types:1
2ptr = Pointer(Int32).malloc(1)
ptr.as(Int8*) #:: Pointer(Int8)
as?
同as一样, 但是在出现异常时只是返回nil ,并且也不支持指针类型同其它数据类型之间的转换。1
2
3
4value = rand < 0.5 ? -3 : nil
result = value.as?(Int32) || 10
value.as?(Int32).try &.abs
typeof
返回一个表达式值的类型
宏
宏是一些预编译的方法(在编译时被加入到抽像语法树中),这些方法必须是完整有效的Crystal代码。1
2
3
4
5
6
7
8
9
10
11
12
13
14macro define_method(name, content)
def {{name}}
{{content}}
end
end
# This generates:
#
# def foo
# 1
# end
define_method foo, 1
foo #=> 1
作用域
宏被定义在顶层作用域,所有代码中都可用。如果某个宏被标记为private 那么它只在当前文件中可见。
宏也可以定义在类和模块中, 并且在定义它的作用域中可见。程序也会在先祖域中搜索宏(父类 及被包含的模块中)。
For example, a block which is given an object to use as the default receiver by being invoked with with … yield can access macros defined within that object’s ancestors chain:1
2
3
4
5
6
7
8
9
10
11class Foo
macro emphasize(value)
"***#{ {{value}} }***"
end
def yield_with_self
with self yield
end
end
Foo.new.yield_with_self { emphasize(10) } #=> "***10***"
定义在类中或模块中的宏 也可以在外部被调用。1
2
3
4
5
6
7class Foo
macro emphasize(value)
"***#{ {{value}} }***"
end
end
Foo.emphasize(10) # => "***10***"
插值
1 | 使用 { } 在语法树中进行插入值。 |
Note that :foo was the result of the interpolation, because that’s what was passed to the macro. You can use the method ASTNode#id in these cases, where you just need an identifier.
宏调用
你可以在编译时调用一些方法, 这些方法被定义在Crystal::Macros 模块中。
条件语句
1 | # 你可以使用{% if condition %} ... {% end %} 来按条件生成代码。宏的条件语句可以在宏定义之外的地方使用。 |
迭代
迭代语句也可以在宏定义之外的地方使用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36macro define_dummy_methods(names)
{% for name, index in names %}
def {{name.id}}
{{index}}
end
{% end %}
end
define_dummy_methods [foo, bar, baz]
foo #=> 0
bar #=> 1
baz #=> 2
#
macro define_dummy_methods(hash)
{% for key, value in hash %}
def {{key.id}}
{{value}}
end
{% end %}
end
define_dummy_methods({foo: 10, bar: 20})
foo #=> 10
bar #=> 20
#
{% for name, index in ["foo", "bar", "baz"] %}
def {{name.id}}
{{index}}
end
{% end %}
foo #=> 0
bar #=> 1
baz #=> 2
宏方法定义
钩子
在一些场景下,一些特殊的宏被当成钩子来使用。这些场景是 inherited, included, extended 和 method_missing1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
181.inherited在编译时被触发,如果定义了子类。@type is the inheriting type.
2.included在编译时被触发,当一个模块被include时。@type is the including type.
3.extended在编译时被触发,当一个模块被extend时。@type is the extending type.
4.method_missing 在编译时被触发,当方法不存在时.
例子: inherited
class Parent
macro inherited
def lineage
"{{@type.name.id}} < Parent"
end
end
end
class Child < Parent
end
Child.new.lineage #=> "Child < Parent"
例子: method_missing1
2
3
4
5
6macro method_missing(call)
print "Got ", {{call.name.id.stringify}}, " with ", {{call.args.size}}, " arguments", '\n'
end
foo # Prints: Got foo with 0 arguments
bar 'a', 'b' # Prints: Got bar with 2 arguments