方法
§ 调用方法时传递关键字参数
Crystal 中,当定义一个方法时,无需特殊的语法来声明关键字参数类型,因为他和普通的位置参数是一样的。
当调用一个方法并传递实参进来时,既可以使用正常的位置参数方式调用,也可以使用关键字参数方式调用
1
2 def say_hello(recipient)
3 puts "Hello #{recipient}!"
4 end
5
6 say_hello("Crystal") # => "Hello Crystal!"
7 say_hello(recipient: "Crystal") # => "Hello Crystal!"
8
Crystal 中提供了一个特殊的语法强制某些参数只能使用关键字参数方式调用。
1
2 def foo(x, *, y)
3 end
4
5 foo 1, y: 2 # OK
6 foo y: 2, x: 3 # OK
7 foo 1, 2 # => Error: missing argument: y
8
§ 定义方法名同名的方法
即使方法名相同的方法,可以通过 参数名不同、参数个数不同、参数类型 以及 是否需要代码块 & 进行区分。
参数名不同:(这里添加 *,强制要求必须使用关键字参数方式调用, 才能正确区分)
1
2 def foo(*, a)
3 puts "foo(a)"
4 end
5
6 def foo(*, b)
7 puts "foo(b)"
8 end
9
10 foo(a: 100) # => foo(a)
11 foo(b: 100) # => foo(b)
12
参数个数不同
1
2 def foo(a,b)
3 puts "foo(a, b)"
4 end
5
6 def foo(a)
7 puts "foo(a)"
8 end
9
10 foo(100,200) # => foo(a, b)
11 foo(100) # => foo(a)
12
参数类型不同
1
2 def foo(a : String)
3 puts "foo(a : String)"
4 end
5
6 def foo(a : Int32)
7 puts "foo(a : Int32)"
8 end
9
10 foo(100) # => foo(a : Int32)
11 foo("Hello") # => foo(a : String)
12
是否包含代码块
1
2 def foo
3 puts "foo()"
4 end
5
6 def foo(&)
7 puts "foo(&)"
8 end
9
10 foo # => "foo()"
11 foo {} # => "foo(&)"
12
§ 方法名称的变更
方法名称的变更非常多,这里不一一列举,这里只是简要按照类别介绍下:
§ Ruby 中很多存在别名的方法,Crystal 仅保留其中一个。
Ruby 为了追求代码的可阅读性,存在很多适合于不同上下文的别名,例如:
Array#lengh => Array#size
Enumerable#detect => Enumerable#find
Enumerable#collect => Enumberable#map
但是这反而会造成新手的困惑,增加了负担,我认为这个改动是合理的。
§ 一些方法名称的语法(习惯)变更,更符合西方人说话的习惯,
例如:
Object#respond_to? => Object#responds_to?
Array#include? => Array#includes?
File#exist? => File#exists?
§ 单纯的一些名称换了,换成 Crystal 开发者认为更合适的名字,
例如:
attr_reader => getter
attr_writer => setter
attr_accessor => property
Hash#key => Hash#key_for
__dir__ => __DIR__
类似这样的变更,使用脚本替换相对简单,可以参考 port_ruby_to_crystal 脚本。
这是一个使用 Ruby 正则表达式编写的脚本,用来对一些 常见的方法名变更 做一些简单且安全地替换, 这减少了从 Ruby 迁移到 Crystal 的摩擦。
§ 看起来啥都没变,但是方法的部分行为改变了,这可能是个大坑,尤其值得注意
Ruby | Crystal | |
---|---|---|
Enumerable#each(&) | 总是返回 self | 总是返回 nil |
Array#fetch | 接受一个参数的形式 | 需提供第二参数或 block 指定默认值 |
下面是一个更狡黠的例子。
例如,下面的是完全合法的 Ruby 代码, 它创建了一个新文件 out.txt,并以 a 模式写入字符串 "content"
1
2 File.write("out.txt", "content", mode = "a")
3
但是,如果在 Crystal 下执行它,会抛出一大堆错误。
点击查看 backtrace
1
2 error in line 0
3 Error: expanding macro
4
5
6 In <top-level>:1:6
7
8 1 | File.write("out.txt", "content", mode = "a")
9 ^----
10 Error: instantiating 'File.write(String, String, String)'
11
12
13 In /home/zw963/.asdf/installs/crystal/1.15.0/share/crystal/src/file.cr:823:3
14
15 823 | def self.write(filename : Path | String, content, perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, mode = "w", blocking = true)
16 ^----
17 Error: instantiating 'write(String, String, String, Nil, Nil, String, Bool)'
18
19
20 In /home/zw963/.asdf/installs/crystal/1.15.0/share/crystal/src/file.cr:824:5
21
22 824 | open(filename, mode, perm, encoding: encoding, invalid: invalid, blocking: blocking) do |file|
23 ^---
24 Error: instantiating 'open(String, String, String, encoding: Nil, invalid: Nil, blocking: Bool)'
25
26
27 In /home/zw963/.asdf/installs/crystal/1.15.0/share/crystal/src/file.cr:741:12
28
29 741 | file = new filename, mode, perm, encoding, invalid, blocking
30 ^--
31 Error: instantiating 'new(String, String, String, Nil, Nil, Bool)'
32
33
34 In /home/zw963/.asdf/installs/crystal/1.15.0/share/crystal/src/file.cr:175:53
35
36 175 | fd = Crystal::System::File.open(filename, mode, perm: perm, blocking: blocking)
37 ^
38 Error: expected argument 'perm' to 'Crystal::System::File.open' to be (File::Permissions | Int32) or File::Permissions, not String
39
40 Overloads are:
41 - Crystal::System::File.open(filename : String, mode : String, perm : Int32 | ::File::Permissions, blocking)
42 - Crystal::System::File.open(filename : String, flags : Int32, perm : ::File::Permissions, blocking _blocking)
43
新手会完全懵逼,不知道发生了什么!看上面第 15 行,会发现一些端倪。
1
2 def self.write(filename, content, perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, mode = "w", blocking = true)
3
我们发现,File.write 方法的签名中第三个位置参数是 perm = DEFAULT_CREATE_PERMISSIONS, 这是不同于 Ruby 实现的。 这个参数要求的类型必须是 DEFAULT_CREATE_PERMISSIONS 而不是 String, 我们通过 mode = "a",传递字符串 "a" 给 perm, 因此报错,正确的写法应该是使用位置无关的关键字参数:
1
2 File.write("out.txt", "content", mode: "a")
3
§ 同名,但是完全不同的含义
例如:
Ruby | Crystal | |
---|---|---|
alias | 创建方法的别名 | 为(通常比较复杂的)类型定义一个别名 |
$0 | 当前程序的名称 | 前一个正则匹配的内容字符串 (Ruby 中同样的东西是 $&) |
&. | 短路运算符 | block 调用简写形式 |