Ruby中类型的约定是什么?

问题描述 投票:4回答:4

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

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

ruby typing design-documents
4个回答
3
投票

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

我觉得一切都很好,不在乎(可能引起奇怪的例外)

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

过度使用鸭子类型检查

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

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


3
投票

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

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

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

所以,在Ruby中,我们有:

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

另请注意,对象可以有多种类型。例如。字符串对象的类型为“Appendable”(它响应<<)和“Indexable”(它响应[])。

那么,回顾一下重点:

  • Ruby语言中不存在类型,只有程序员才能使用
  • 类和模块不是类型
  • 类型是协议,其特征在于对象如何响应消息

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

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

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

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

我为这篇长篇文章道歉,但理解类和类型之间的区别,并理解像Ruby这样的OO语言中的类型是非常重要的。

处理类型的最佳方法是简单地使用对象并记录协议。如果你想打电话,请拨打call;不要求它是Proc。 (对于一个,这意味着我不能通过一个Method,这将是一个烦人的限制。)如果你想添加一些东西,只需调用+,如果你想附加一些东西,只需调用<<,如果你想打印一些东西,只需要调用printputs(例如,在测试时,后者可以使用StringIO而不是File)。不要试图以编程方式确定某个对象是否满足某个协议,这是徒劳的:它等同于解决暂停问题。 YARD文档系统有一个用于描述类型的标记。它是完全自由格式的文本。但是,有一种建议的类型语言(我并不特别喜欢,因为我认为它过分关注类而不是协议)。

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

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

  • 如果一个对象可以“有些合理地”表示为数组,字符串,整数,浮点数等,它将响应to_ato_sto_ito_f等。
  • 如果一个对象与ArrayStringIntegerFloat等实例的类型相同,它将响应to_aryto_strto_intto_float等。

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

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

以下是Ruby API中的一个示例:Arrays实际上并未使用OO原则实现。出于性能原因,Ruby作弊。因此,您实际上只能使用Array类的实际实例索引到Integer,而不是只使用任意“类似整数”对象。但是,Ruby不会要求你传入Integer,而是首先调用to_int,让你有机会继续使用你自己的整数类对象。但是,它不会调用to_i,因为使用不是整数的东西索引到数组是没有意义的;只能“合理地表现”为一个。 OTOH,Kernel#printKernel#putsIO#printIO#puts和朋友们在他们的论点上打电话给to_s,让你可以合理地打印任何物体。并且Array#jointo_str的论证中称呼to_s,但to_s在阵列元素上;一旦你明白为什么这是有道理的,你就更接近理解Ruby中的类型。

以下是一些经验法则:

  • 不要测试类型,只需使用它们并记录它们
  • 如果你绝对肯定必须有一个特定类的实例,你应该使用多字母类型的转换;不只是测试类,给对象一个转换自己的机会
  • 单字母类型的转换几乎总是错误的,除了用于打印的nil;有多少情况你能想象在哪里静静地将"one hundred"0转换为nil而你甚至没有意识到有一个String = Array # warning: already initialized constant String p String.new # [] 或一个字符串是正确的做法?

0
投票

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


0
投票

有趣的问题!

Type-safety

Java和Ruby几乎截然相反。在Ruby中,您可以:

my_method(array.size)

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

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

  • 确保除了Integer之外没有调用该方法(例如to_i
  • 接受可以使用Float,Integer或Rational调用该方法,并可能在输入上调用(1..3.5).to_a #=> [1, 2, 3]
  • 使用与Floats一起使用的方法:例如'a'*2.5 #=> 'aa'NoMethodError: undefined method 'to_i' for object ...
  • 如果用其他东西调用它,你可能得到一个rescue,你可以试着处理它(例如用is_prime?

Documentation

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

  • is_prime?应该返回一个布尔值
  • Integer应该在YARD中定义

否则,# @param [Array<String, Symbol>] arg takes an Array of Strings or Symbols def foo(arg) end 支持文档中的类型:

qazxswpoi
© www.soinside.com 2019 - 2024. All rights reserved.