在Ruby中实现to_int和to_str的后果

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

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

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

所以,我的问题是:在实现to_strto_int 是否有任何真实的(即:实际的)伤害


更新:JörgWMittag举了一个例子让我想到了什么。 重新解释这个问题:当你已经拥有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有没有受益于这种分离?

它使我的课程不那么灵活/可用。 例如:如果我没有Status#to_str我就无法使用String#+将Status输出与另一个字符串连接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 转换为整数(通过截断),但我可以说我也可以我的状态转换为整数(通过“截断”所有非退出代码信息)。

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

在德国法律中,我们有一个难以掌握和非本能的原则,但我认为这完全适用于此。 它被称为“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之间to_xArray#join :它打印出数组的元素,由分隔符对象分隔。 它通过在数组的每个元素上调用to_s并在分隔符上调用to_str来完成此操作。 一旦你理解为什么它在一个上调用to_s在另一个上调用to_str ,你基本上就已经设置了。

(虽然你对Float#to_int评论已经表明你确实理解了。)


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

鸭子打字的精神肯定不会让人查找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 

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