在Ruby中实现to_int和to_str的后果

时间:2009-11-11 21:02:21

标签: ruby duck-typing

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,但只有当您的对象to_str / to_int >基本上是一个字符串/ int“。

毫无疑问,我的课程并非“从根本上”是一个字符串或一个整数。然而 我对这个规则有一些问题:

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


    更新:JörgWMittag举了一个让我想到的例子。要重新提出问题:当您已经to_str / to_int时,是否真的需要to_s / to_i? (除了特定方法已经期望to_str超过to_s

    之外

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

2 个答案:

答案 0 :(得分:8)

  

这使我的课程变得不那么灵活/可用。例如:如果我没有String#+,我无法使用Status#to_str将状态输出与其他字符串连接。

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

a_string = "Output was: #{results}"

这个正常工作™,因为字符串插值实际上会对插值表达式的结果调用to_s

  

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

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

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

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

  

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

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

在德国法律中,我们有一个难以掌握和非本能的原则,但我认为这完全适用于此。它被称为“Keine Gleichheit im Unrecht”(在错误中不平等)。这意味着宪法赋予的Equaliy的基本权利仅适用于法律。换句话说:OJ并没有使谋杀合法化。

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

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

最终帮助理解to_xto_xyz之间差异的事情是Array#join:它打印出数组的元素,由a分隔分隔符对象。它通过在数组的每个元素上调用to_s并在分隔符上调用to_str来实现此目的。一旦你理解为什么它会在另一个上调用to_s而在另一个上调用to_str,你基本上就会设置。

(虽然您对Float#to_int的评论已经表明您了解。)


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

答案 1 :(得分: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

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