为什么Ruby中的>>不能用于字符串拼接?

20
在 Ruby 中,您可以使用 << 来追加字符串:
>> "Hello" << "World"
=> "HelloWorld"

那么为什么不能使用>>在前面添加它们?

>> "Hello" >> "World"
NoMethodError: undefined method `>>' for "Hello":String

我意识到String没有定义>>方法,但是背后的原因是什么?


4
这个方法将属于谁?是“Hello”还是“World”的? - Mladen Jablanović
1
我想不出为什么你不能这样做,但是方法的功能不是完全相同吗?你可以把它看作是前置操作,但 Ruby 只会将等式右侧的字符串附加到左侧字符串的末尾。 - Doug
1
我认为这是因为这种用法不常见,而 >> 的通常含义是“右移位”,所以避免混淆;另外编写自己的代码也很容易。 - Zabba
2
@Zabba - "<<" 也常被称为左移, 所以我不确定那个逻辑成立。" - Simon
1
@Mladen:它将会在Hello上。 - Simon
显示剩余3条评论
5个回答

26
Ruby 1.9.3新增了一个String#prepend方法。
添加prepend的提案[1]还包括">>"方法,并且在该线程中有关于该实现[2]的讨论:
Matz说:“>>是有趣的符号,我没有考虑过它。”
sorya说:“这个补丁已经被讨论过几次了。”
然而,在该线程的结尾处,得出的结论是接受String#prepend,并且“其他包括String#>>的提案仍在等待中。”
尽管如此,我找不到其他任何关于它的讨论...还有其他人吗?
就个人而言,我喜欢它,而且添加它很简单:
class String
  def >> (s)
    s.prepend(self)
  end
end

结果如下:
>> "Hello" >> "World"
=> "HelloWorld"

[1] http://bugs.ruby-lang.org/issues/3675 是一个关于Ruby编程语言的错误报告。
[2] http://translate.google.com/translate?hl=en&sl=ja&tl=en&u=http%3A%2F%2Fbugs.ruby-lang.org%2Fissues%2F3675 是一个将上述错误报告从日语翻译成英语的谷歌翻译链接。

3
这与 << 的作用完全相同,只是它没有原生的优化。如果你说 >>prepend 的别名,那么它应该将参数实际上放在对象的前面。如果你真正想表达的意思是希望 :>> 按源代码中字符串出现的顺序连接,那么最好只使用 class String; alias :>> :<<; end 而不是使用本地的 prepend 方法。 - coreyward
@coreyward,那不是真的。每个人都在使用字符串字面量,这一点有点模糊。如果你将>>别名为<<,显然就没有前缀了。操作的返回值是相同的,但如果我们使用变量,我们得到的是我们想要的相反结果。如果我们有a =“Hello”和b =“World”,a >> b和a << b意味着不同的东西。 - Anthony DeSimone
@AnthonyDeSimone 我认为你的评论可以更清晰一些,但是有关字符串字面量与变量的观点是正确的。不过,我怀疑我们很快就不会在Ruby/ActiveSupport中看到这种情况,因为它违反了你期望发生的事情,只是为了满足人类想要平衡的愿望(即希望 >> 与我们的 << 相辅相成)。 - coreyward
@coreyward,你是对的。我想强调的是,在一个情况下我们永久修改a,而在另一个情况下我们永久修改b。 - Anthony DeSimone

20

Ruby的语法不允许像>>这样的方法按照你的期望实现。

"Hello" << "World"中的<<等同于以下内容:

"Hello".<<("World")

如果您想创建一个prepend(插入式添加)方法 >>,我希望在"Hello" >> "World"中的"Hello"将成为字符串"World"的参数:

("Hello")>>."World" 这不是有效的Ruby语法。

如果"World"是"Hello"的参数,那么您只是像fl00r演示的那样附加一个字符串。将>>别名设置为与<<相同会导致混淆。

这就是为什么您可以使用insert方法。如果您想在"World"前面插入"Hello",那么应该在"World"上调用insert方法:

"World".insert(0, "Hello ")

重点是要记住,<<是一个方法调用,就像字符串方法(例如length)一样,因此您必须保持类似于常规方法调用的排序方式。


5
为什么你会有这种期望?难道不应该是“你好”吗?>>(“世界”)? - Simon
4
对我来说,使用像 << 这样的速记符号的部分目的是表示该字符串正在被“推送”到另一个字符串上。实际上,它是一种箭头,表示“将该字符串添加到另一个字符串中”。如果你从这个角度考虑它,使用 >> 就会读作“将‘Hello’添加到‘World’中”。然后,说"World"是你将"Hello"添加到其中的对象,这在语法上现在没有意义(操作符左边的参数)。 - McStretch
有道理。这个想法之前没想到过,正如我对Mladen所说的,可能正是这两种矛盾的解释导致它还没有被实现。 - Simon
1
如果是 "Hello".>>("World"),那么你所做的就是重新定义了 <<"Hello".>>("World") 会产生 "HelloWorld",与 "Hello".<<("World")(也会产生 "HelloWorld")有什么不同呢?无论哪种方式,在这种情况下都会操作 "Hello" 字符串,而不是 "World",因为正如我之前提到的,"World" 是参数,而不是使用该语法进行操作的对象。 - McStretch
它们不同的原因是String#<<不仅返回一个值,还修改了你调用它的字符串。使用a << ba现在包含其先前的内容和b的内容。如果存在a >> b,它将修改b,使其包含其先前的内容和a的内容。此外,“参数排序”的推理毫无意义。当您在对象上调用方法并传递参数时,该方法对两者都有引用。没有理由需要它们以特定的排列方式来实现此功能,正如Patrick所演示的那样。 - Strigoides
显示剩余4条评论

6

Ruby总是从右到左赋值。

但你可以自己实现它。

class String
  def >>(s)
    s << self
  end
end

"Hello" >> "world"
#=> "worldHello"

3
他说“prepend”,因此我认为预期的输出仍将是“HelloWorld”。 - Michael Kohl
2
@Michael - 追加会得到“HelloWorld”,所以我期望在前面添加会得到“WorldHello”。 - Simon
6
问题在于,Ruby的方法通常不会改变传递进来的参数对象,而是改变调用该方法的对象。这就是为什么 String#>> 方法不存在的原因了 :) - Mladen Jablanović
1
@Mladen Jablanović,实际上我们可以返回 self = s + self,这样参数就不会被改变。 - fl00r
1
不完全是(因为self不能被赋值),但是你可以使用insert(0, s)。但无论如何,那个特定的>>会让人感到违反直觉,因为每个人都期望'Hello' >> 'World'的结果是'HelloWorld',不是吗? - Mladen Jablanović
显示剩余5条评论

1

我不认为我会这样做,但如果有人强迫我:

class String
  def prepend(string)
    insert(0, string)
  end
end

我认为>>不是一个好主意——原因是<<很好的地方在于它看起来像即将发生的事情。>>对我来说看起来会产生与<<相同的结果,只是调用对象被添加到参数之前。


0

我认为这是一个好问题,而且这个问题比McStretch的回答中所建议的符号<<的问题更普遍,因为在字母表中给出的方法也存在同样的情况。也就是说,有String#concat方法,它将参数字符串附加self,但没有方法可以在参数字符串前添加


有一个方法可以在字符串前面添加参数,它被称为 String#insert - McStretch
1
不,它不会这样做。你必须强制将0作为第一个参数才能使其作为前置工作。 - sawa
是的,我同意,但是说没有prepends方法仍然是错误的,在我看来。可以使用插入(Insert)和prepends,只是没有一个好的快捷方式。 - McStretch
2
原始问题的要点是没有 << 的相反对应项(尽管你可以用不同的方法来处理它)。我写过,concat 也没有对应项。你不能认为“insert”是与 concat 配对的对应项。它们有不同的语法。你可以将 insert 像特殊情况下的 prepend 一样工作,但 insert 本身并不是 prepend - sawa
1
正如Patrick Smith所指出的那样,在1.9.3版本中确实有一个prepend方法。 - brookr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接