在Ruby中实现to_int和to_str的后果

Cra*_*ker 5 ruby duck-typing

有一个类,它暴露了一个字符串值和一个int值(分别是一个命令输出和退出代码).除了通过to_s和暴露它们之外to_i,我也在使用,to_str并且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
Run Code Online (Sandbox Code Playgroud)

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

a_string = "Output was: " + results
Run Code Online (Sandbox Code Playgroud)

(我想用这个作为int强制的例子,但Fixnum.+不喜欢它,所以它实际上不起作用:)

an_int = 1 + results
Run Code Online (Sandbox Code Playgroud)

到目前为止,我所读过的所有内容都表示,这可能是一件"糟糕"的事情.常见的主题是这样的:" 当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_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有没有受益于这种分离?

Jör*_*tag 8

它使我的课程不那么灵活/可用.例如:String#+如果我没有,我无法使用其他字符串连接Status输出Status#to_str.

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

a_string = "Output was: #{results}"
Run Code Online (Sandbox Code Playgroud)

只是工作 ™,因为字符串插值实际上调用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中,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.但是,请不要.


小智 4

鸭子类型的精神肯定不会让人查找 Status 对象的源代码来找出返回的内容。

我个人认为你应该通过两个实例方法公开文本结果和退出状态:

class Status

  ...

  def message
    @output
  end

  def exit_status
    @status.exitstatus
  end

end
Run Code Online (Sandbox Code Playgroud)

然后使用如下

a_string = "Output was: " + results.message
a_int    = 1 + results.exit_status
Run Code Online (Sandbox Code Playgroud)

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