【问题标题】:Ruby as a "pure" object oriented language --- inconsistency with Ruby puts?Ruby 作为一种“纯粹的”面向对象语言 --- 与 Ruby puts 不一致?
【发布时间】:2014-02-26 15:19:24
【问题描述】:

我经常读到 Ruby 是一种纯面向对象的语言,因为命令通常作为传递给对象的消息给出。

例如:

在 Ruby 中写道:"A".ord 获取 A0x41.chr 的 ascii 代码以根据其 ascii 代码发出字符。 这与 Python 不同:ord("A")chr(0x41)

到目前为止一切顺利 --- Ruby 的语法是消息传递。

但在考虑字符串输出命令时会出现明显的不一致:

现在有:puts strputs(str) 而不是 str.puts

鉴于对 Ruby 语法的纯面向对象期望,我希望输出命令是传递给字符串对象的消息,即从字符串类调用方法,因此 str.puts

有什么解释吗?我错过了什么吗?

谢谢

【问题讨论】:

    标签: ruby string oop syntax


    【解决方案1】:

    我希望输出命令是传递给字符串对象的消息,即从字符串类调用方法,因此是 str.puts

    这是不正确的期望,让我们从它开始。你为什么要把一个字符串告诉puts 本身?它会将自己打印到什么位置?它对文件、I/O 流、套接字和其他可以打印内容的地方一无所知(而且应该一无所知)。

    当您说puts str 时,它实际上被视为self.puts str(隐式接收器)。即消息被发送到当前对象。

    现在,所有对象都包含Kernel 模块。因此,所有对象在其方法列表中都有Kernel#puts。任何对象都可以puts(包括当前对象,self)。

    正如医生所说,

    puts str
    

    被翻译成

    $stdout.puts str
    

    也就是说,默认情况下,实现委托给标准输出(打印到控制台)。如果要打印到文件或套接字,则必须在文件或套接字类的实例上调用 puts。这完全是OO。

    【讨论】:

    • “这是不正确的期望”是不正确的。应该是puts obj 还是obj.puts 取决于视角。不同的观点可以有不同的看法。
    • @sawa:那么这个具体案例呢?您是否见过一种 OO 语言/lib,您可以在其中告诉字符串打印自己?这是疯狂和违反 SRP。
    • @sawa Sergio 的解释 它应该对文件、I/O 流、套接字……一无所知。 非常合乎逻辑。我找不到String 类应该处理或处理 I/O 的任何原因。
    • Sergio, David:我在回答中添加了更多解释。
    【解决方案2】:

    Ruby 不是完全 OO(例如,方法不是对象),但在这种情况下,它是。 putsKernel#puts,它是 $stdout.puts 的简写。也就是说,您正在调用$stdout 流的puts 方法并将一个字符串作为参数传递给要输出到流的参数。所以,当你打电话时

    puts "foo"
    

    你真的在打电话:

    $stdout.puts("foo")
    

    这与OO完全一致。

    【讨论】:

    • Ruby 是纯面向对象的,因为它没有方法。只有消息(调用私有Method 类的特殊对象)。所以如果你用这个定义来看这个,那么 Ruby 中的 methods 不是第一类对象就没什么特别的了。
    • 方法调用只是消息,当然,但消息处理程序本身(我们称之为“方法”)是语法结构,它们本身并不是直接的对象。您可以获取它们的对象句柄,但它们不是对象(对于块也是如此)。这通常是人们说“Ruby 中几乎所有东西都是对象”时的意思。
    【解决方案3】:

    puts 是输出流上的一种方法,例如

    $stdout.puts("this", "is", "a", "test")
    

    【讨论】:

    • 其他答案更完整。我的留作记录。
    • (+1) 你的反论一目了然。当它有一个更短的例子时会更好,例如$stdout.puts()
    • 谢谢。我将其更改为与文档中的示例匹配;-) IO.puts,如果您正在寻找它。
    【解决方案4】:

    将某些内容打印到某个地方至少涉及两件事:写入的内容和写入的位置。根据您关注的内容,可能会有不同的实现,即使在 OOP 中也是如此。除此之外,Ruby 有一种方法可以让一个方法看起来更像一个函数(即,不像 OOP 中那样与接收器特别绑定),用于在所有地方使用的方法。因此,对于打印等方法,至少可以考虑三个合乎逻辑的选择。

    • 在要打印的对象上定义的 OOP 方法
    • 在应该打印的对象上定义的 OOP 方法
    • 函数式方法

    对于第二个选项,IO#write 是一个例子;接收者是写作的目的地。

    没有显式接收者的puts实际上是Kernel#puts,并且两者都不作为参数;这是第三个选项的一个例子;您正确地指出这不是 OOP,但 Matz 特别提供了Kernel 模块,以便能够执行以下操作:函数式方法。

    第一个选项是您所期望的;这没有错。碰巧没有这种类型的众所周知的方法,但它是由一位开发人员在 Ruby 核心中提出的,但不幸的是,它没有成功。事实上,我和你有同样的感觉,在我的个人图书馆里也有类似的东西,叫做Object#intercept。一个简化的版本是这样的:

    class Object
      def intercept
        tap{|x| p x}
      end
    end
    
    :foo.intercept # => :foo
    

    您可以根据需要将p 替换为puts

    【讨论】:

    • 如果IO#write 对应您的第二个定义,那么IO#puts 肯定也可以,不是吗?而$stdoutIO 的一个实例,那么如果puts 实际上是$stdout.puts 的语法糖,那么为什么puts 属于另一个类别呢?
    • @user846250 请注意,我写了“puts without an explicit receiver”,即Kernel#puts。这与$stdout.putsIO#puts 完全不同。仔细阅读。
    • 好的,我仔细阅读了,我认为分歧源于您的定义本身。在我看来,第二个(至少)过于严格。例如,在系统记录器的情况下,您会说我没有做 OOP,因为我正在打电话,比如 Logger.error("msg"),但它没有描述错误消息的打印位置。
    • 我会说Logger.error("msg") 是第三种类型。在这里,Logger 仅用作命名空间。如果您可以定义Logger 的实例并且可以执行Logger#error,那么这将是第二种类型。
    猜你喜欢
    • 2018-03-31
    • 2013-07-14
    • 1970-01-01
    • 2014-06-26
    • 2011-03-08
    • 2015-04-24
    • 2014-04-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多