5红宝石成语: 诗歌模式、方块、鸭子打字
编写一行 Ruby 代码,检查字符串 s 是否为回文字符串,即向后读与向前读是否相同。提示:使用图 2.11 中的方法,不要忘记大写与小写并不重要: ReDivider 是一个回文字符串。
请自行检查
s.downcase == s.downcase.reverse
你可能认为可以说 s.reverse=~Regexp.new(s),但如果 s 恰好包含 regexp 元字符(如 $),这样做就会失败。
假设将 Enumerable 混合到一个不提供 each 方法的类 Foo 中。调用 Foo.new.map { |elt| puts elt } 时会出现什么错误?
自己检查
Enumerable 中的 map 方法将尝试在其接收器上调用 each,但由于新的 Foo 对象没有定义 each,Ruby 将引发未定义方法错误。
哪种说法是正确的,为什么?( a ) include 'enumerable' ( b ) include Enumerable
自己检查
( b ) 是正确的,因为 include 期望的是模块名,而模块名(与类名一样)是一个常量而不是字符串。
Translated with DeepL
编程习语是指在特定编程语言的资深用户编写的代码中经常出现的一种操作或表达方式。虽然可能还有其他方法来完成相同的任务,但习惯用法是最容易向语言的其他资深用户揭示意图的方法。在学习一门新语言时,您的目标应该是通过理解并很好地使用该语言的习语,学会 "用 "该语言 "思考",或者换句话说,避免 "您可以用任何语言编写 FORTRAN "这一众所周知的误区。在本节中,我们将探讨三个关键的 Ruby 习语:向方法传递参数("诗歌模式 "和命名参数)、代码块和 duck 类型。
诗歌模式和命名参数。图 2.7 和 2.8 展示了与 Ruby 方法调用相关的两个普遍习语。第一种是诗歌模式,它允许在解析明确的情况下省略方法调用参数周围的括号。此外,当方法调用的最后一个参数是散列时,可以省略散列字面量周围的大括号。
图 2.7
源代码:ch_ruby/code/poetrymode.rb
link_to('Edit', {:controller => 'students', :action => 'edit'})
link_to '编辑', :controller => 'students', :action => 'edit' }.
link_to '编辑', 控制器:'学生', 动作:'编辑
对 link_to(我们将在第 4.4 节中介绍)方法的三个合法且等价的调用需要一个字符串参数和一个哈希参数。第一个是完全括号化的,第二个省略了调用参数周围的括号和最后哈希参数周围的大括号,第三个使用了另一种(Ruby ≥2.0
)语法来处理第二个参数中的哈希键。
在 Ruby 的早期版本中,散列参数通常用于模仿 Python、C# 等语言中的命名参数功能(也称为关键字参数)。例如,图 2.7 中使用的 link_to 方法的文档告诉我们,:controller 和 :action 只是可以作为散列中的键传递给该方法的众多附加(可选)值中的两个。如图 2.8 所示,真正的命名参数在 Ruby 2.0 中可用;然而,在 Ruby 2.0 之前编写的大量 Ruby 代码仍然使用哈希值来传递可选参数或为参数提供默认值。
图 2.8
源代码:ch_ruby/code/optional_args_example.rb
# 使用 "命名关键字 "参数
def greet(name, last_name: "", greeting: "Hi")
"#{greeting}, #{name} #{last_name}! #{last_name}!"
结束
greet("Dave") # => "嗨,Dave!"
greet("Dave", last_name: "Fox") # => "Hi, Dave Fox!"
greet("Dave", greeting: "Yo") # => "Yo, Dave!"
greet("戴夫", greeting: "Hey", last_name: "Patterson")
# => "嘿,戴夫-帕特森!" - 关键字参数顺序无关
greet(greeting: "Yo") # 论点错误,因为第一个参数是必需的
通过使用关键字参数或命名参数,可以定义某些参数为可选参数或默认值的方法。命名参数可以提高接受多个参数的方法的清晰度,不过我们将在第 9 章中看到,通常应该尽量减少方法接受的参数数量。
块。Ruby 对块的用法与其他语言有所不同。在 Ruby 中,块只是一个没有名称的方法,用编程语言的术语来说就是匿名 lambda 表达式。与普通的命名方法一样,它也有参数并可以使用局部变量。
如图 2.9 所示,代码块最常见的用途之一是实现数据结构遍历。所有类集合的 Ruby 类中都有实例方法 each,它接收一个由块(匿名 lambda)组成的参数,集合中的每个成员都将被传递给这个块。Ruby 爱好者们喜欢说 Ruby 集合 "管理自己的遍历",因为每个集合的接收者都可以决定如何实现该方法以产生每个集合元素。(事实上,在图 2.9 中,我们甚至看不出 movie_list 的底层类型是什么)。
内部迭代器首次出现在研究语言 CLU 中,这是研究数据隐藏的早期工具,芭芭拉-利斯科夫因此获得了图灵奖。
图 2.9
源代码:ch_ruby/code/block_example.rb
def print_movies(movie_list)
movie_list.each do |m|
puts "#{m.title} (评分:#{m.rating})" end
结束
结束
each 接受一个参数--代码块--并依次将集合中的每个元素传递给代码块。代码块的括号是 do 和 end,代码块所期望的任何参数都用 | 管道符号| 括在 do 后面。每次通过代码块时,m 都会被设置为 movie_list 的下一个元素。
图 2.10 展示了这样一个集合操作符的简单示例,它可以用于任何实现了 each 作为遍历自身方式的集合。请再次注意,我们并不知道该集合是如何实现的:我们只需知道它实现了 each 的实例方法,以枚举其元素。Ruby 提供了各种各样的此类集合方法;图 2.11 列出了一些最有用的方法。通过一些练习,您将自动开始用这些函数式惯用法而不是命令式循环来表达对集合的操作。尽管 Ruby 允许在集合中使用 i,但每种方法都能让我们更好地利用 duck 类型(我们很快就会看到)来提高代码的重用性。
图 2.10
源代码:ch_ruby/code/collection_example.rb
# 查找集合中最大的元素
def maximum(collection)
result = collection.first
collection.each do |item|
result = item if item > result
结束
result
结束
maximum([3,4,2,1]) # => 4
max(["a", "x", "b"]) # => "x"
max([RomanNumeral.new('XL'), RomanNumeral.new('LI')] # => 'LI'.
类 RomanNumeral
include Comparable
def initialize(roman_numeral_string)
@orig_string = roman_numeral_string
@value = RomanNumeral.convert_from_roman(roman_numeral_string)
结束
def <=>(other)
@value <=> other
结束
def to_s
@orig_string
end
def self.convert_from_roman(str)
# ... 从字符串转换罗马数字的代码...
结束
结束
这个示例可以在任何集合中找到响应每个元素的最大值元素,并且与集合中元素的类型无关,只要它们响应 > 即可。如果我们有一个 RomanNumeral 类,明确定义了 >,或者定义了 <=> 并混合了 Comparable 模块以定义 <、> 等,那么它甚至可以用于罗马数字。
方法块? 返回一个新的集合,其中包含
c.map 1 对 c 的每个元素应用 block 得到的元素
c.select 1 block 值为 true 的 c 子集
c.reject 1 删除 block 判断为 "true "的元素后得到的 c 子集
c.uniq 删除重复的 c 的所有元素
c.reverse c 中顺序相反的元素
c.compact c 中所有非零元素
c.flatten c 及其任意子数组的元素,递归扁平化后只包含非数组元素
c.partition 1 两个集合,第一个集合包含代码块求值为 "true "的 c 元素,第二个集合包含代码块求值为 "false "的 c 元素
c.sort 2 c 的元素根据一个接收 2 个参数的代码块进行排序,如果第一个元素应更早排序,则返回-1;如果第二个元素应更早排序,则返回+1;如果两个元素可以按任意顺序排序,则返回 0。
以下方法要求集合元素响应 <=>;参见第 2.4 节。
c.sort 如果不带代码块调用 sort,则根据元素响应 <=> 的方式进行排序。
c.sort_by 1 将代码块应用于 c 的每个元素,并对结果进行排序。例如,movies.sort_by { | | | | | | m
m |
m.title } 根据标题对 <=> 的响应程度对电影对象排序。
c.max、c.min 集合中最大或最小的元素
鸭子类型。不过,你可能会惊讶地发现,图 2.11 中总结的集合方法(以及图中没有的其他几个方法)并不属于 Ruby 的 Array 类。事实上,它们甚至不是 Array 和其他集合类型所继承的任何超类的一部分。相反,它们利用了一种更强大的重用机制: 混入(mix-in)是相关方法的命名集合,可以添加到任何满足与混入方法的 "契约 "的类中。模块是 Ruby 将一组方法打包成混合方法的方法。类定义中的 Ruby 语句 include ModuleName 将模块的实例方法、类方法和变量混合到类中。图 2.11 中的集合方法是在一个名为 Enumerable 的模块中定义的,该模块是 Ruby 标准库的一部分,并被混合到所有 Ruby 集合类中。正如其文档所述,Enumerable 要求混入它的类提供一个 each 方法,因为 Enumerable 的集合方法是以 each 为单位实现的。只要该类定义了 each 实例方法,那么将它混入什么类中并不重要,该类和混入的类都不必事先声明它们的意图。例如,Ruby 的 Array 类中的 each 方法遍历数组元素,而 IO 类中的 each 方法遍历文件或其他 I/O 流的行。因此,混合插件允许在原本不相关的类中重复使用整个行为集合。
同样,一个定义了 "飞船运算符"<=>的类,会根据其第二个参数是否小于-1、0、1 而返回-1、0、1。
的类可以混合使用 Comparable 模块,该模块用 <=> 定义了 <、<=、>、>=、== 和 between? 例如,Time 类定义了 <=> 并在 Comparable 中混合,允许你写 Time.now.between?(Time.parse("19:00"), Time.parse("23:15")).
"鸭子类型 "一词是对这种能力的流行描述,因为 "如果一个东西看起来像鸭子,叫起来像鸭子,那它可能就是一只鸭子"。从 Enumerable 的角度来看,如果一个类有 each 方法,那么它就可能是一个集合,从而允许 Enumerable 提供以 each 方法为基础实现的其他方法。当 Ruby 程序员说某个类 "呱呱叫得像个数组 "时,他们通常是指它不一定是数组,也不是数组的后代,但它响应的大多数方法都与数组相同,因此可以在使用数组的地方使用它。
总结
诗歌模式允许省略方法调用参数周围的括号,并在哈希字面量是方法调用的最后一个参数时省略其周围的大括号。哈希参数以前曾被用来模拟命名参数或关键字参数,但从 2.0 版开始,Ruby 支持真正的命名参数,因此现在不鼓励这种做法。
Ruby 从函数式编程中借鉴了很多思想,尤其是块的使用--参数化的代码块被称为 lambda 表达式,这些表达式随身携带它们的作用域,使它们成为闭包。
由于 Ruby 的动态类型,只要接收者响应了方法,那么调用对象上的方法就是合法的,无论接收者的类是什么,这种行为有时被称为鸭式类型。
例如,Enumerable 只要求包含类响应每个行为。在类的 ClassName 语句后加上 include ModuleName 就可以将模块混合到类中。
与 Java 中的接口不同,混入不需要正式声明。但由于 Ruby 没有静态类型,因此你有责任确保包含混入模块的类满足混入模块文档中规定的条件,否则你将在运行时出错。
块是闭包
Ruby 从函数式编程中借鉴了块和迭代器(如每个迭代器)的组合技术。Ruby 代码块是一个闭包:当代码块执行时,它可以 "看到 "代码块在程序文本中出现的整个词法范围。换句话说,代码块的存在就好像创建了一个即时的范围快照,以后只要执行代码块,就可以重新创建这个快照。Rails 的许多功能都利用了这一事实来提高 DRYness,包括视图渲染(我们将在第 4.4 节中看到)、模型验证和控制器过滤器(第 5.1 节),因为它们允许将要发生的事情的定义与时间和在应用程序结构中的位置分开。
浙公网安备 33010602011771号