Ruby中类型的约定是什么?

时间:2017-01-12 16:36:41

标签: ruby typing design-documents

由于Ruby是一种纯粹动态类型的语言,我不能确定对于传递给我的方法的类型应该具有什么样的期望值。例如,如果我的方法仅在传递整数时起作用,我是否应该主动检查以确保是这种情况,还是应该在这种情况下允许类型异常?

此外,在围绕Ruby代码编写设计文档时,指定方法应该对哪些类型进行操作的正确方法是什么? Javadocs(虽然通常不用于设计文档)例如确切地指定了一个方法将运行的类型,因为语言本身是静态类型的,但似乎Ruby文档对于方法的前后条件一直非常不精确。在Ruby中指定这种格式是否有标准做法?

4 个答案:

答案 0 :(得分:3)

IMO这是非常基于意见的。并且高度依赖于背景和您的要求。问问你自己:我在乎吗?可以提出错误吗?谁是用户(我的代码与外部客户)?我可以处理修复输入吗?

我认为不关心一切都很好(可能引起奇怪的例外)

def add(a, b)
  a + b # raise NoMethodError if a does not respond_to +
end

over 使用鸭子类型检查

def add(a, b)
  if a.respond_to?(:+)
     a + b
  else
     "#{a} #{b}" # might makes sense?
  end
end 

只是将其翻译为例外类型

def add(a, b)
  a.to_i + b.to_i
end

检查预先输入的类型(并提出一个有用的异常):

def integers(a, b)
  raise ArgumentError, "args must be integers" unless a.is_a?(Integer) and b.is_a?(Integer)
  a + b
end

这实际上取决于您的需求以及您所需的安全性和安全性。

答案 1 :(得分:3)

您需要注意的第一件事是类型之间的区别。

非常不幸的是,Java通过让类始终是类型来混淆这种区别(尽管Java中还有其他类型的类,即接口,基元和泛型类型参数)。事实上,几乎所有关于Java风格的书都会告诉你将类用作类型。此外,在他的开创性论文 On Understanding Data Abstraction,Revisited 中,William R. Cook指出,在Java中,类描述的是抽象数据类型,而不是对象。接口描述了对象,所以如果你在Java中使用类作为类型,你就不会做OO;如果你想在Java中使用OO,那么你唯一能用作类型的就是接口,你可以使用的唯一东西就是工厂。

在Ruby中,类型更像是网络协议:类型描述对象理解的消息以及它对它们的反应。 (这种相似性并非偶然:Smalltalk,Ruby的远古祖先受到后来成为互联网的启发。在Smalltalk的说法中," protocol"是非正式用于描述类型的术语在Objective-C中,这种非正式的协议概念已成为语言的一部分,主要受Objective-C影响的Java直接复制了这个概念,但将其重命名为" interface"。)

所以,在Ruby中,我们有:

  • module(语言特征):用于代码共享和差异实现的工具; 类型
  • class(语言功能):对象的工厂,也是IS-A module不是类型
  • 协议非正式事物):对象的类型,其特征是消息响应以及它如何响应它们

另请注意,对象可以有多种类型。例如。字符串对象具有类型&#34; 可附加&#34; (它响应<<)和&#34; 可索引&#34; (它响应[])。

所以,回顾一下重点:

  • 类型在Ruby语言中不存在,只在程序员的头脑中存在
  • 课程和模块不属于
  • 类型是协议,其特征在于对象如何响应消息

显然,协议不能在语言中指定,因此通常在文档中指定。虽然通常没有指明它们。这实际上没有它听起来那么糟糕:例如,通常情况下,强加给消息参数的要求是明显的&#34;从该方法的名称或预期用途。此外,在某些项目中,预计面向用户的验收测试将发挥作用。 (例如,在不再存在的Merb Web框架中就是这种情况。在验收测试中完整描述了API。)传递错误类型时得到的错误消息和异常通常足以弄清楚方法是什么需要。最后但并非最不重要的是,始终存在源代码。

有几个众所周知的协议,例如混合each所需的Enumerable协议(对象必须通过产生其元素来响应each - by-one并且如果传递了块则返回self并且如果没有传递块则返回Enumerator,如果对象想要成为一个端点,则需要Range协议。 Range(它必须回复succ及其后继者,并且必须回复<=)或混合<=>所需的Comparable协议(对象)必须使用<=>-101回复nil。这些也不是在任何地方写下来的,或者只是在片段中写下来,它们只是被现有的Rubyists所熟知,并且很好地教给新的。

一个很好的例子是StringIO:它与IO具有相同的协议,但不会从中继承,也不会从共同的祖先继承(明显的{{1}除外) })。因此,当有人检查Object时,我无法传入IO(对测试非常有用),但如果他们只是使用对象AS-IF它是{ {1}},我可以传递StringIO,他们永远不会知道差异。

当然,这并不理想,但与Java相比:重要要求和保证的 lot 也在散文中指定!例如,在IO的类型签名中,它是否表示结果列表将被排序?无处!这只在JavaDoc中提到过。功能界面的类型是什么?再次,仅在英文散文中指定。 Stream API有一个完整的概念动物园,在类型系统中没有捕获,如非干扰和可变性。

我为这篇长篇文章道歉,但理解类型之间的区别非常重要,并了解类型< / em>使用像Ruby这样的OO语言。

处理类型的最佳方法是简单地使用对象和文档协议。如果你想打电话,只需致电StringIO;不要要求它是List.sort。 (对于一个,这意味着我无法传递call,这将是一个恼人的限制。)如果你想添加一些东西,只需拨打Proc,如果你想追加某些东西,只需打电话Method,如果您想要打印某些内容,只需拨打+<<(后者有用,例如,在测试中,当我可以传入{{1}时而不是print)。不要尝试以编程方式确定某个对象是否满足某个协议,这是徒劳的:它等同于解决暂停问题。 YARD文档系统有一个用于描述类型的标记。它是完全自由格式的文本。但是,有一种建议的类型语言(我不是特别喜欢,因为我认为它过分关注类而不是协议)。

如果确实绝对必须拥有特定类的实例(而不是满足某个协议的对象),则有许多类型转换方法由你处置。但请注意,只要您需要某个类而不是依赖协议,就会离开面向对象编程领域。

您应该知道的最重要的类型转换方法是单字母和多字母puts方法。这是两者之间的重要区别:

  • 如果一个物体可以“合理地”#34;将表示作为数组,字符串,整数,浮点数等,它将响应StringIOFileto_X,{{1}等等。
  • 如果某个对象与<{1}},to_ato_sto_i等实例属于相同类型,则会回复to_fArrayStringInteger等。

对于这两种方法,保证它们永远不会引发异常。 (当然,如果它们存在,否则将引发Float。)对于这两种方法,保证返回值将是相应核心类的实例。对于多字母方法,转换应该在语义上无损。 (注意,当我说&#34;它有保证&#34;,我说的是已经存在的方法。如果你自己编写,这不是保证,而是你必须履行的要求,以便它成为一个使用您的方法保证其他人。)

多字母方法通常要严格得多,而且这些方法要少得多。例如,完全可以合理地说to_ary&#34;可以表示为&#34;空字符串,但说to_str IS-AN为空字符串是荒谬的,因此to_int会响应to_float,但不会NoMethodError。同样,float通过返回它的截断来响应nil,但它不响应nil,因为你不能无损地将float转换为整数。

以下是Ruby API中的一个示例:nil实际上并未使用OO原则实现。出于性能原因,Ruby作弊。因此,您实际上只能使用to_s类的实际实例索引到to_str,而不只是任何任意&# 34;整数状&#34;宾语。 但是,而不是要求你传入to_i,Ruby会首先调用to_int,让你有机会继续使用你自己的整数类对象。然而,它调用Array,因为使用不是整数的东西索引到数组是没有意义的;只能在某种程度上合理地代表&#34;作为一个。 OTOH,ArrayIntegerIntegerto_int和朋友在他们的论点上调用to_i,以允许您合理地打印任何对象。 Kernel#print在其参数上调用Kernel#puts,但在数组元素上调用IO#print;一旦你明白为什么这是有意义的,你就更接近于理解Ruby中的类型。

以下是一些经验法则:

  • 不要测试类型,只需使用它们并记录它们
  • 如果您绝对肯定 必须拥有特定类的实例,您应该使用多字母类型转换;做只测试类,给对象一个转换自己的机会
  • 单字母类型转换几乎总是错误的,除了用于打印的IO#puts;在没有意识到 to_s或字符串的情况下,您可以想象有多少情况可以无声地将Array#jointo_str转换为to_s正确的事情吗?

答案 2 :(得分:0)

有趣的问题!

类型安全

Java和Ruby几乎截然相反。 在Ruby中,你可以这样做:

String = Array
# warning: already initialized constant String
p String.new
# []

因此,您几乎可以忘记从Java中了解的任何类型安全性。

对于您的第一个问题,您可以:

  • 确保不使用Integer(例如my_method(array.size)
  • 以外的任何其他方法调用该方法
  • 接受可以使用Float,Integer或Rational调用该方法,并可能在输入上调用to_i
  • 使用与Floats一起使用的方法:例如: (1..3.5).to_a #=> [1, 2, 3]'a'*2.5 #=> 'aa'
  • 如果使用其他内容调用它,您可能会获得NoMethodError: undefined method 'to_i' for object ...,并且可以尝试处理它(例如使用rescue

文档

记录方法的预期输入和输出的第一步是在正确的位置(类或模块)定义方法并使用适当的方法名称:

  • is_prime?应返回布尔值
  • is_prime?应在Integer
  • 中定义

否则,YARD支持文档中的类型:

# @param [Array<String, Symbol>] arg takes an Array of Strings or Symbols
def foo(arg)
end

答案 3 :(得分:0)

我不确定为什么你只需要将整数传递给你的方法,但我不会在我的代码中主动检查这个值是一个整数。例如,如果您正在执行需要整数的算术,我会在需要的时候对该值进行类型转换或转换为整数,并通过注释或在方法头中解释这样做的目的。