【问题标题】:Consequences of implementing to_int and to_str in Ruby在 Ruby 中实现 to_int 和 to_str 的后果
【发布时间】:2009-11-11 21:02:20
【问题描述】:

I have a class 公开了一个字符串值和一个 int 值(分别是命令输出和退出代码)。除了通过to_sto_i 暴露它们之外,我还使用to_strto_int,像这样:

class Status
  def to_s
    @output
  end
  alias :to_str :to_s

  def to_i
    @status.exitstatus
  end
  alias :to_int :to_i
end

我的想法是能够在尽可能多的情况下使用这个对象。将其强制转换为字符串或 int 会增加可用性。例如,我可以将对象与字符串连接起来:

a_string = "Output was: " + results

(我想以此作为 int 强制的示例,但 Fixnum.+ 不喜欢它,因此它实际上不起作用:)

an_int = 1 + results

到目前为止,我所读到的所有内容都表明这可能是一件“坏”的事情。常见的主题是这样的:“当您的对象可以表示为字符串/int 时使用to_s/to_i,但只有当您的对象从根本上字符串/整数”。

毫无疑问,我的课程“根本上”不是字符串或 int。然而 我对这条规则有一些问题:

  1. 它使我的课程不太灵活/不可用。例如:如果我没有 Status.to_str,我无法使用 String.+ 将 Status 输出与另一个字符串连接起来。
  2. 这似乎违反了鸭式打字的精神。对象的用户(即:获取它作为参数的方法)不应该关心这个对象是什么,它应该只关心它可以做什么。 (在这种情况下,“do”表示“可以表示为字符串/int”。)
  3. “基本上是一个字符串/整数”的论点对我来说非常模糊。例如,您会看到Float.to_int 被多次提及。故事是这样的,因为浮点数总是有一个整数部分,to_int 是一个有效的方法。但是,我认为这是虚假的:浮点数 not 是一个整数(因为它有一个非整数分量),因此试图将它们的“类型”等同起来没有多大意义。您可以合法地将浮点数转换为整数(通过截断),但我可以说我也可以将我的状态转换为整数(通过“截断”所有的非退出代码信息)。

所以,我的问题是:在实施to_strto_int 时有什么真正的(即:实际的)危害


更新:Jörg W Mittag 举了一个让我想到一些事情的例子。换个说法:当您已经拥有to_s/to_i 时,是否真的需要拥有to_str/to_int? (除了特定方法已经期待 to_str 而不是 to_s

例如,在 Jörg 的 Array.join 示例中,数组成员通过 to_s 转换,而分隔符通过 to_str 转换。但这真的有必要吗?如果 Array.join 改为调用 separator.to_s,那么您可以成功地将更多对象传递给它(例如:整数、符号等)并获得更大的灵活性。 Ruby 是否会从这种分离中受益?

【问题讨论】:

    标签: ruby duck-typing


    【解决方案1】:

    它使我的课程不太灵活/不可用。例如:如果没有Status#to_str,我无法使用String#+ 将状态输出与另一个字符串连接起来。

    这是一个不好的例子,因为连接字符串是单一的 Ruby。字符串插值是首选方式:

    a_string = "Output was: #{results}"
    

    而这只是工作™,因为字符串插值实际上是在插值表达式的结果上调用to_s

    这似乎违反了鸭式打字的精神。对象的用户(即:获取它作为参数的方法)不应该关心该对象是什么,它应该只关心它可以做什么。 (在这种情况下,“do”的意思是“可以表示为字符串/int”。)

    我认为“可以表示为字符串/整数”并不是真正的行为。 IOW:“对象可以做什么”是关于特定上下文中有趣的行为,而“可以表示为字符串/int”并不是真正有趣的行为。

    如果您说“Status IS-A Integer”(本质上是to_int 的意思),那么您可以使用它进行算术运算。但是,“将 42 添加到 file not found”是什么意思呢?成功的对数是多少?失败的平方根是多少?

    在上面的字符串插值示例中,有趣的行为是“可以显示”。这基本上通过实现#to_s 来表明。连接两个字符串 OTOH 需要两个字符串。

    “基本上是一个字符串/整数”的论点对我来说非常模糊。例如,您会看到Float#to_int 被多次提及。故事是这样的,因为浮点数总是有一个整数部分,to_int 是一个有效的方法。但是,我认为这是虚假的:浮点数 not 是一个整数(因为它有一个非整数分量),因此试图将它们的“类型”等同起来没有多大意义。您可以合法地将浮点数转换为整数(通过截断),但我可以说我也可以将我的状态转换为整数(通过“截断”所有的非退出代码信息)。

    同样,这是一个相当弱的论点,因为我实际上同意你的观点:这是错误的。

    在德国法律中,我们有一个难以理解且不直观的原则,但我认为它在这里完美适用。它被称为“Keine Gleichheit im Unrecht”(错误中没有平等)。这意味着宪法赋予的平等基本权利,仅在法律范围内适用。换句话说:OJ 不会让谋杀合法化。

    所以,仅仅因为 Ruby 核心库中有垃圾代码(相信我,有很多 很多),并不意味着你也可以写垃圾代码 :-)

    在这种特殊情况下,Float#to_int 完全是错误的,不应该存在。 Float 不是 Integer 的子类型。乍一看,似乎相反,即Integer#to_float 是有效的,但实际上这也不正确:在Ruby 中,Integers 具有任意精度,但Floats 具有固定精度。 实现Fixnum#to_float 是有效的,但这是个坏主意,因为Integers 可以神奇地从Fixnum 转换为BigInteger 并返回,因此#to_float方法会“神奇地”出现和消失。

    最终帮助理解to_xto_xyz之间的区别是Array#join:它打印出数组的元素,由分隔符对象分隔。它通过在数组的每个元素上调用to_s 和在分隔符上调用to_str 来实现这一点。一旦您了解为什么它调用to_sto_str 后,您基本上就设置好了。

    (尽管您在Float#to_int 上的 cmets 已经表明您确实了解。)


    旁注:对于代数上下文中的双重调度,Ruby 实际上使用#coerce 协议。因此,如果您希望 1 + a_status 示例工作,则需要实现 Status#coerce。但是,请不要。

    【讨论】:

    • 同意字符串插值在实践中是一种更好的方法;我只是将字符串 concat 用作需要 to_str 的示例。 (事实上​​,在实际编码中,我避免串联只是因为它适用于任意对象)。
    • 那么为什么Array#join 一方面调用to_s 而另一方面调用to_str
    • 我的猜测是,连接的项目不应该是字符串——暗示我们想要连接的是它们的字符串表示。 OTOH 分隔符应该是一个字符串,而不仅仅是任何对象。写[1,2].join(3)是错误的,在分隔符上使用to_str是检测的方法。
    【解决方案2】:

    duck-typing 的精神当然不会让人去查找 Status 对象的源代码来找出返回的内容。

    个人认为应该通过两个实例方法暴露文本结果和退出状态:

    class Status
    
      ...
    
      def message
        @output
      end
    
      def exit_status
        @status.exitstatus
      end
    
    end
    

    然后按如下方式使用

    a_string = "Output was: " + results.message
    a_int    = 1 + results.exit_status
    

    这对于任何阅读代码的人来说都是有意义的,恕我直言。

    【讨论】:

    • 我在实际代码中这样做了(请参阅链接的类),但为了简洁起见,我在此处发布的示例代码中省略了这些方法。
    猜你喜欢
    • 2012-06-26
    • 2010-11-07
    • 1970-01-01
    • 2015-03-11
    • 1970-01-01
    • 2012-08-26
    • 2014-12-08
    相关资源
    最近更新 更多