5

有一个公开字符串值和 int 值的类(分别是命令输出和退出代码)。除了通过to_sand暴露它们之外to_i,我还使用to_strand to_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是字符串/int 时使用/ ”。to_ito_strto_int

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

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

所以,我的问题是:实施and是否有任何真正的(即:实际的)危害to_strto_int


更新:Jörg W Mittag 举了一个让我想到一些事情的例子。换个说法:当你已经拥有/时,真的需要拥有to_str/吗?(除了特定方法已经期待结束的事实之外)to_intto_sto_ito_strto_s

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

4

2 回答 2

8

它使我的课程不那么灵活/可用。例如:String#+如果我没有Status#to_str.

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

a_string = "Output was: #{results}"

只是有效™,因为字符串插值实际上调用to_s了插值表达式的结果。

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

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

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

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

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

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

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

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

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

最终帮助to_x理解和之间的区别to_xyzArray#join:它打印出数组的元素,由分隔符对象分隔。它通过调用to_s数组的每个元素和to_str分隔符来做到这一点。一旦你理解了为什么它会调用to_s一个和to_str另一个,你基本上就设置好了。

(尽管您的评论Float#to_int已经表明您确实了解。)


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

于 2009-11-12T02:05:25.677 回答
4

鸭式打字的精神当然不会让人查找 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

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

于 2009-11-11T21:21:14.030 回答