我最近学习了Ruby编程语言,总而言之,它是一门好语言。但是令我惊讶的是,它并不像我期望的那么简单。更准确地说,“最小惊喜规则”在我看来并不十分受人尊敬(当然,这是非常主观的)。例如:
x = true and false
puts x # displays true!
和著名的:
puts "zero is true!" if 0 # zero is true!
您会警告Ruby新手的其他“陷阱”是什么?
true and false
返回true?
我最近学习了Ruby编程语言,总而言之,它是一门好语言。但是令我惊讶的是,它并不像我期望的那么简单。更准确地说,“最小惊喜规则”在我看来并不十分受人尊敬(当然,这是非常主观的)。例如:
x = true and false
puts x # displays true!
和著名的:
puts "zero is true!" if 0 # zero is true!
您会警告Ruby新手的其他“陷阱”是什么?
true and false
返回true?
Answers:
从文章:
$
和@
并不表示Perl中的变量数据类型,而是用作范围解析运算符。99.0
)或显式转换(99.to_f
)。附加点(99.
)是不够的,因为数字容易受到方法语法的影响。0
,""
并且[]
都被评估为true
。在C中,表达式的0 ? 1 : 0
计算结果为0
(即false)。但是,在Ruby中,1
由于所有数字求和,因此它会产生true
;只有nil
和false
评估为false
。该规则的一个推论是,按惯例,Ruby方法(例如,正则表达式搜索)在成功但nil
失败(例如,不匹配)时返回数字,字符串,列表或其他非假值。此约定还用于Smalltalk,在Smalltalk中,只有特殊对象,true
并且false
可以在布尔表达式中使用。char
字符类型)。在切片字符串时,这可能会引起意外:"abc"[0]
yields 97
(一个整数,表示字符串中第一个字符的ASCII码);获得"a"
使用"abc"[0,1]
(长度为1的子字符串)或"abc"[0].chr
。statement until expression
与其他语言的等效语句(例如,do { statement } while (not(expression));
在C / C ++ / ...中)不同,该表示法实际上从不运行该表达式(如果该表达式已经存在)true
。这是因为statement until expression
实际上是语法糖
until expression
statement
end
,等效其中在C / C ++是while (not(expression)) statement;
就像statement if expression
是一个相当于
if expression
statement
end
但是,表示法
begin
statement
end until expression
实际上,即使表达式已经为true,在Ruby中,语句也会运行一次。
Greeting << " world!" if Greeting == "Hello"
不会生成错误或警告。这类似于final
Java中的变量,但是与Java不同,Ruby也具有“冻结”对象的功能。一些与其他语言明显不同的功能:
条件表达式and
和的通常运算符or
不遵循正常的优先级规则:and
绑定不比严格or
。Ruby也有表达运营商||
和&&
其正常工作。
def
内部def
并没有Python程序员所期望的:
def a_method
x = 7
def print_x; puts x end
print_x
end
这给出了关于x
未定义的错误。您需要使用Proc
。
语言特征
()
,以避免代码含义不明确。不使用()
仍然是一种常见的做法,将Ruby与称为的方法一起用作人类可读的领域特定的编程语言本身尤其好method_missing()
。新手将无法使用平等方法:
这些示例应阐明前三种方法:
a = b = "joe"
a==b # true
a.eql? b # true
a.equal? b # true (a.object_id == b.object_id)
a = "joe"
b = "joe"
a==b # true
a.eql? b # true
a.equal? b # false (a.object_id != b.object_id)
a = 1
b = 1.0
a==b # true
a.eql? b # false (a.class != b.class)
a.equal? b # false
注意==,eql?和相等?应该总是对称的:如果a == b,则b == a。
还要注意==和eql?都在Object类中实现为相等的别名?,因此,如果您创建一个新类并想要==和eql?意味着除了普通身份以外的其他内容,那么您需要同时覆盖它们。例如:
class Person
attr_reader name
def == (rhs)
rhs.name == self.name # compare person by their name
end
def eql? (rhs)
self == rhs
end
# never override the equal? method!
end
该===方法的行为不同。首先它是不是对称的(一=== B则不是意味着A在B ===一)。如我所说,您可以将a === b读为“ a matchs b”。这里有一些例子:
# === is usually simply an alias for ==
"joe" === "joe" # true
"joe" === "bob" # false
# but ranges match any value they include
(1..10) === 5 # true
(1..10) === 19 # false
(1..10) === (1..10) # false (the range does not include itself)
# arrays just match equal arrays, but they do not match included values!
[1,2,3] === [1,2,3] # true
[1,2,3] === 2 # false
# classes match their instances and instances of derived classes
String === "joe" # true
String === 1.5 # false (1.5 is not a String)
String === String # false (the String class is not itself a String)
该案件陈述是基于===方法:
case a
when "joe": puts "1"
when 1.0 : puts "2"
when (1..10), (15..20): puts "3"
else puts "4"
end
等效于此:
if "joe" === a
puts "1"
elsif 1.0 === a
puts "2"
elsif (1..10) === a || (15..20) === a
puts "3"
else
puts "4"
end
如果定义一个新类,其实例表示某种容器或范围(如果它具有诸如include?或match?方法之类的东西),那么您可能会发现覆盖这样的===方法很有用:
class Subnet
[...]
def include? (ip_address_or_subnet)
[...]
end
def === (rhs)
self.include? rhs
end
end
case destination_ip
when white_listed_subnet: puts "the ip belongs to the white-listed subnet"
when black_listed_subnet: puts "the ip belongs to the black-listed subnet"
[...]
end
以下代码使我感到惊讶。我认为这是一个危险的陷阱:既容易遇到,又难以调试。
(1..5).each do |number|
comment = " is even" if number%2==0
puts number.to_s + comment.to_s
end
打印:
1
2 is even
3
4 is even
5
但是如果我在块之前添加comment =
任何内容 ...
comment = nil
(1..5).each do |number|
comment = " is even" if number%2==0
puts number.to_s + comment.to_s
end
然后我得到:
1
2 is even
3 is even
4 is even
5 is even
基本上,当仅在块内定义变量时,然后在块末尾销毁该变量,然后nil
在每次迭代时将其重置为。通常就是您所期望的。但是如果变量是该块之前所定义,然后将外变量用于块内,并且它的值是因此迭代之间持久的。
一种解决方案是改为编写此代码:
comment = number%2==0 ? " is even" : nil
我认为很多人(包括我在内)倾向于写“ a = b if c
”而不是“ a = (c ? b : nil)
”,因为它更具可读性,但显然有副作用。
a = (b if c)
以得到所需的效果,而无需三进制。这是因为b if c
如果c
为假,则评估为nil 。
当super
不使用任何参数调用时,实际上使用与该覆盖方法相同的参数来调用覆盖方法。
class A
def hello(name="Dan")
puts "hello #{name}"
end
end
class B < A
def hello(name)
super
end
end
B.new.hello("Bob") #=> "hello Bob"
要实际super
不带任何参数的调用,您需要说super()
。
B#hello
在name = 42
之前有super
,则说“你好42”。
我在理解类变量,类属性和类方法时遇到很多麻烦。这段代码可能对新手有帮助:
class A
@@classvar = "A1"
@classattr = "A2"
def self.showvars
puts "@@classvar => "+@@classvar
puts "@classattr => "+@classattr
end
end
A.showvars
# displays:
# @@classvar => A1
# @classattr => A2
class B < A
@@classvar = "B1"
@classattr = "B2"
end
B.showvars
# displays:
# @@classvar => B1
# @classattr => B2
A.showvars
# displays:
# @@classvar => B1 #Class variables are shared in a class hierarchy!
# @classattr => A2 #Class attributes are not
理解区块非常重要,它们在任何地方都被使用。
您不需要在方法参数周围加括号。是否使用它们取决于您。有人说你应该经常使用它们。
使用抬高和抢救进行异常处理,而不是抛出和捕获。
您可以使用,;
但不必这样做,除非您想将多件事情放在一行上。
我对包含实例方法和类方法的mixin遇到了麻烦。这段代码可能对新手有帮助:
module Displayable
# instance methods here
def display
puts name
self.class.increment_displays
end
def self.included(base)
# This module method will be called automatically
# after this module is included in a class.
# We want to add the class methods to the class.
base.extend Displayable::ClassMethods
end
module ClassMethods
# class methods here
def number_of_displays
@number_of_displays # this is a class attribute
end
def increment_displays
@number_of_displays += 1
end
def init_displays
@number_of_displays = 0
end
# this module method will be called automatically
# after this module is extended by a class.
# We want to perform some initialization on a
# class attribute.
def self.extended(base)
base.init_displays
end
end
end
class Person
include Displayable
def name; @name; end
def initialize(name); @name=name; end
end
puts Person.number_of_displays # => 0
john = Person.new "John"
john.display # => John
puts Person.number_of_displays # => 1
jack = Person.new "Jack"
jack.display # => Jack
puts Person.number_of_displays # => 2
刚开始,我以为我可以通过简单地执行以下操作同时拥有实例方法和类方法的模块:
module Displayable
def display
puts name
self.class.increment_displays
end
def self.number_of_displays # WRONG!
@number_of_displays
end
[...]
end
不幸的是,方法number_of_displays永远不会被包含或扩展,因为它是“模块类方法”。只能将“模块实例方法”包含在类中(作为实例方法)或扩展到类中(作为类方法)。这就是为什么需要将mixin的实例方法放入模块中,并将mixin的类方法放入另一个模块中的原因(通常将类方法放入“ ClassMethods”子模块中)。由于包括 magic方法,您可以轻松地在一个简单的“ include Displayable”调用中同时包含实例方法和类方法(如上例所示)。
这个混入将基于每个类对每个显示进行计数。该计数器是一个类属性,因此每个类都有自己的属性(如果您从Person类派生一个新类,则您的程序可能会失败,因为派生类的@number_of_displays计数器将永远不会初始化)。您可能需要将@number_of_displays替换为@@ number_of_displays以使其成为全局计数器。在这种情况下,每个类层次结构将有其自己的计数器。如果您想要一个全局且唯一的计数器,则应将其设为模块属性。
当我开始使用Ruby时,所有这些对于我来说绝对是不直观的。
我仍然不知道如何干净地将其中的某些混合方法设为私有或受保护的(仅应将display和number_of_displays方法作为公共方法包括在内)。
(至少,比我更关注最初!)
0..10
(两个点)和0...10
(三个点)之间存在差异。
我非常喜欢Ruby。但这点对点与点对点的东西使我感到烦恼。我认为这种微妙的双重语法“功能”即为:
应该不会在我的程序中造成毁灭性的一次性错误。
for (i=0; i<max; i++)
与for (i=0; i<=max; i++)
我认为Perl是“ and
”和“ or
”的代表,Perl是Ruby最明显的“父母”之一(最著名的是Smalltalk)。它们的优先级都比(要低于分配,实际上是指行为出自何处)低,&&
和||
这是你应该使用的运营商。
要注意的其他事情并不是立即显而易见的:
尽管看起来有点像,但您实际上并没有调用方法/函数。而是,如在Smalltalk中一样,您将消息发送到对象。所以method_missing
真的更像message_not_understood
。
some_object.do_something(args)
相当于
some_object.send(:do_something, args) # note the :
符号已被广泛使用。那就是那些开始的事情:
它们并不是立即显而易见的(嗯,对我来说不是),但是您越早掌握它们就越好。
Ruby秉承“鸭式”的原则,遵循“如果它像鸭子一样走路而像鸭子一样嘎嘎...”这样的原理,它允许使用通用的方法子集非正式替换对象,而无需任何显式继承或混合关系。
如果您使用attr_writer
或attr_accessor
(或def foo=
)声明了一个setter(aka mutator),请小心从类内部调用它。由于变量是隐式声明的,因此解释程序始终必须解析foo = bar
为声明名为foo的新变量,而不是调用method self.foo=(bar)
。
class Thing
attr_accessor :foo
def initialize
@foo = 1 # this sets @foo to 1
self.foo = 2 # this sets @foo to 2
foo = 3 # this does *not* set @foo
end
end
puts Thing.new.foo #=> 2
这也适用于Rails ActiveRecord对象,该对象获取基于数据库中的字段定义的访问器。由于它们甚至不是@样式的实例变量,因此单独设置这些值的正确方法是使用self.value = 123
或self['value'] = 123
。
过去让我感到\n
困惑的一个原因是,单引号内的字符串不支持换行符()以及其他字符。反斜杠本身可以逃脱。您必须使用双引号使转义按预期工作。
x = (true and false) # x is false
正如您指出的,0和”是正确的。
您可以具有相同名称的方法和模块/类(这很有意义,因为该方法实际上已添加到Object并因此具有其自己的名称空间)。
没有多重继承,但是经常使用“ mixin模块”将通用方法添加到多个类。
false
and nil
是错误的。所有其他都是真实的价值观。
在您找到原因之前,可以重新定义方法,并且可以使方法陷入困境。(诚然,如果错误地重新定义了Ruby on Rails控制器的操作,此错误可能会有点“难”!)
#demo.rb
class Demo
def hello1
p "Hello from first definition"
end
# ...lots of code here...
# and you forget that you have already defined hello1
def hello1
p "Hello from second definition"
end
end
Demo.new.hello1
$ ruby demo.rb
=> "Hello from second definition"
$ ruby -w demo.rb
demo.rb:10: warning: method redefined; discarding old hello1
=> "Hello from second definition"
我是红宝石的新手,在第一轮中,我遇到了一个有关将浮点数/字符串更改为整数的问题。我从花车开始,将所有内容编码为 f.to_int。但是,当我继续使用相同的方法处理字符串时,运行该程序时我弯了腰。
显然,字符串没有to_int方法,但浮点数和整数都有。
irb(main):003:0* str_val = '5.0'
=> "5.0"
irb(main):006:0> str_val.to_int
NoMethodError: undefined method `to_int' for "5.0":String
from (irb):6
irb(main):005:0* str_val.to_i
=> 5
irb(main):007:0> float_val = 5.0
=> 5.0
irb(main):008:0> float_val.to_int
=> 5
irb(main):009:0> float_val.to_i
=> 5
irb(main):010:0>
随意的括号一开始也把我扔了。我看到有一些代码,有一些没有代码。我花了一段时间才意识到两种风格都可以接受。
与monkut的回应有关,Ruby to_foo
方法暗示了他们将执行多严格的转换。
诸如之类的短代码to_i
,to_s
要使其懒惰,即使它们无法以该格式正确表示,也应将它们转换为目标类型。例如:
"10".to_i == 10
:foo.to_s == "foo"
较长的显式函数(如to_int
)to_s
表示可以将对象本地表示为该类型的数据。例如,Rational
该类表示所有有理数,因此可以通过调用将其直接表示为Fixnum(或Bignum)整数to_int
。
Rational(20,4).to_int == 5
如果您不能调用更长的方法,则意味着该对象不能以该类型自然地表示。
因此,基本上,在转换时,如果您对方法名称不满意,Ruby将对转换不满意。
从在Ruby中,为什么不foo = true unless defined?(foo)
进行分配?
foo = true unless defined?(foo) #Leaves foo as nil
因为foo
定义为何nil
时defined?(foo)
被调用。
红宝石哈希的迭代不能保证以任何特定顺序发生。(这不是一个错误,这是一个功能)
Hash#sort
如果您需要特定的订单,则很有用。
这个让我发疯了一次:
1/2 == 0.5 #=> false
1/2 == 0 #=> true
1/2
计算结果为0
,这不等于0.5
不分类型。但是Rational(1, 2) == 0.5
,和1.0 == 1
。