ruby - 在 Ruby 中实现 to_int 和 to_str 的后果

标签 ruby duck-typing

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

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

a_string = "Output was: " + results

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

an_int = 1 + results

到目前为止,我读到的所有内容都表明这可能是一件“糟糕”的事情。共同的主题是这样的:“当您的对象可以表示为字符串/整数时使用to_s/to_i,但是to_str/to_int 仅当您的对象基本上是一个字符串/int 时。

毫无疑问,我的类“从根本上”不是字符串或整数。然而 我对这条规则有一些疑问:

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

所以,我的问题是:在实现 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,那么您可以成功地将更多对象传递给它(例如:整数、符号等)并获得更大的灵 active 。 Ruby 是否从这种分离中受益?

最佳答案

It makes my class less flexible/usable. For example: I couldn't use String#+ to concatenate the Status output with the other string if I didn't have Status#to_str.

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

a_string = "Output was: #{results}"

就可以工作™,因为字符串插值实际上会在插值表达式的结果上调用to_s

It seems to violate the spirit of duck-typing. The user of an object (ie: a method that gets it as a parameter) shouldn't care what that object is, it should only care what it can do. (In this case, "do" means "can be represented as a string/int".)

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

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

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

The arguments for "is fundamentally a string/int" are pretty fuzzy to me. For example, you'll see that Float#to_int is mentioned a lot. The story goes that since a floating-point number always has an integer component, to_int is a valid method. However, I think this is spurious: a Float is not an integer (as it has a non-integer component) and so trying to equate their "typeness" doesn't make much sense. You can legitimately convert a Float to an integer (through truncation), but then I can say that I can convert my Status to an integer as well (by "truncating" all of the non-exit-code information).

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

在德国法律中,我们有一个原则很难理解且不直观,但我认为它完美地适用于此。它被称为“Keine Gleichheit im Unrecht”(错误中没有平等)。这意味着宪法赋予的平等基本权利仅在法律范围内适用。换句话说: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:它打印出数组的元素,由分隔符对象分隔。它通过在数组的每个元素上调用 to_s 并在分隔符上调用 to_str 来实现这一点。一旦您理解了为什么它在一个上调用to_s 并在另一个上调用to_str,您就基本上设置好了。

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


旁注:对于代数上下文中的双重分派(dispatch),Ruby 实际上使用 #coerce 协议(protocol)。因此,如果您想让 1 + a_status 示例正常工作,您需要实现 Status#coerce。但是,请不要。

关于ruby - 在 Ruby 中实现 to_int 和 to_str 的后果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1718043/

相关文章:

ruby - Ruby block 的最佳解释?

python - 动态语言的用途

ruby - 如何从文本中删除 URL?

ruby - 在 Ruby 脚本中接受命令行参数

ruby-on-rails - Ruby 中参数前的 & 有什么作用?

arrays - Ruby String Split on "\t"丢失 "\n"

c# - 如何在运行时在 IEnumerable<T> 上创建动态 Select?

python - python如何将任意对象转换为列表?

戈朗 : Can you wrap a Package in an Interface?

dart - dart 中的可选类型有什么特别之处