静态类型在更大的项目中真的有什么帮助?


9

在脚本编程语言网站的首页上进行好奇时,我遇到了这段话:

当系统太大而无法控制时,可以添加静态类型。

这让我记住,在许多静态的,已编译的语言(例如Java)和动态的,解释性语言(主要是Python,因为它使用较多,但它是大多数脚本语言之间共享的“问题”)之间的宗教战争中,静态地抱怨之一打字语言对动态打字语言的拥护者认为,它们不能很好地扩展到较大的项目,因为“有一天,您会忘记函数的返回类型,而必须查找它,而使用静态打字语言时,一切被明确声明”。

我从不理解这种说法。老实说,即使您声明了函数的返回类型,在编写了许多行代码之后,您也可能会忘记它,而您仍然必须返回到使用的搜索功能声明它的行。您的文本编辑器进行检查。

另外,当函数使用声明时type funcname()...type您将不得不在调用该函数的每一行中进行搜索,因为您只知道funcname,而在Python之类的代码中,您只会搜索def funcnamefunction funcname只发生一次,在声明。

而且,使用REPL可以很容易地测试一个函数是否具有不同输入的返回类型,而对于静态类型的语言,您需要添加一些代码行并重新编译所有内容才能知道所声明的类型。

因此,除了知道函数的返回类型显然不是静态类型语言的强项之外,静态类型在更大的项目中真的有什么帮助?



2
如果您阅读了另一个问题的答案,则可能会找到所需的答案,他们基本上是从不同的角度询问同一件事:)
sara 2016年

1
Swift和Playground是静态类型语言的REPL。
daven11 '16

2
语言不是编译的,实现是编译的。为“编译”语言编写REPL的方法是编写一些可以解释该语言的东西,或者至少逐行编译并执行它,同时保持必要的状态。另外,Java 9将附带REPL。
塞巴斯蒂安·雷德尔

2
@ user6245072:这是为解释器制作REPL的方法:读取代码,将其发送给解释器,打印出结果。这是为编译器制作REPL的方法:读取代码,将其发送给编译器,运行编译后的代码,打印出结果。非常简单。这就是FSi(F♯REPL),GHCi(GHC Haskell的REPL),Scala REPL和Cling所做的。
约尔格W¯¯米塔格

Answers:


21

而且,对于REPL,使用不同的输入来测试其返回类型的函数很简单

这不是小事。这不是小事可言。对于琐碎的功能,这样做只是微不足道的。

例如,您可以简单地定义一个函数,其中返回类型完全取决于输入类型。

getAnswer(v) {
 return v.answer
}

在这种情况下,getAnswer并没有真正一个单一的返回类型。您无法编写任何带有示例输入来调用此测试的测试,以了解返回类型是什么。它将始终取决于实际参数。在运行时。

而且这甚至不包括执行数据库查找的功能。或根据用户输入来执行操作。或查找全局变量,它们当然是动态类型。或在随机情况下更改其返回类型。更不用说需要每次手动测试每个单独的功能。

getAnswer(x, y) {
   if (x + y.answer == 13)
       return 1;
   return "1";
}

从根本上讲,在一般情况下证明函数的返回类型在数学上几乎是不可能的(停顿问题)。保证返回类型的唯一方法是限制输入,以便通过不允许无法证明的程序来回答该问题,而不属于暂停问题的范畴,这就是静态类型所做的。

另外,由于函数是用funcname()...类型声明的,因此要知道类型,您将不得不在调用该函数的每一行中进行搜索,因为您只知道funcname,而在Python等语言中,您只需在声明中搜索仅发生一次的def funcname或函数funcname。

静态类型的语言称为“工具”。它们是可以帮助您处理源代码的程序。在这种情况下,由于使用了Resharper,我只需右键单击并转到“定义”即可。或使用键盘快捷键。或者只需将鼠标悬停在上面,它将告诉我涉及的类型是什么。我根本不在乎grepping文件。文本编辑器本身是用于编辑程序源代码的可悲工具。

从内存来看,def funcname在Python中是不够的,因为可以任意重新分配该函数。或者可以在多个模块中重复声明。或在课堂上。等等。

并且您仍然必须返回到使用文本编辑器的搜索功能对其进行声明的行以进行检查。

在文件中搜索函数名称是一个可怕的原始操作,永远不需要。这代表您的环境和工具的根本故障。您甚至考虑在Python中进行文本搜索的事实对Python来说是一个重大问题。


2
公平地说,这些“工具”是用动态语言发明的,而动态语言早于静态语言就拥有它们。在静态语言甚至具有图形或IDE之前,图形Lisp和Smalltalk IDE中就已经存在“转到定义,代码完成,自动重构等”功能,更不用说图形IDE了。
约尔格W¯¯米塔格

知道函数的返回类型并不总是告诉你什么功能DO。您可以编写带有示例值的文档测试来代替编写类型。例如,将(单词'some words oue')=> ['some','words,'oeu']与(words字符串)-> [string],(zip {abc} [1..3])比较=> [[(a,1),(b,2),(c,3)]及其类型。
aoeu256

18

想想一个拥有许多程序员的项目,这些项目多年来已经发生了变化。您必须保持这一点。有一个功能

getAnswer(v) {
 return v.answer
}

它到底在做什么?什么v啊 元素answer来自哪里?

getAnswer(v : AnswerBot) {
  return v.answer
}

现在我们有了更多信息-; 它需要一种类型AnswerBot

如果我们使用基于班级的语言,我们可以说

class AnswerBot {
  var answer : String
  func getAnswer() -> String {
    return answer
  }
}

现在我们可以拥有一个类型变量AnswerBot并调用该方法getAnswer,每个人都知道它的作用。在完成任何运行时测试之前,编译器将捕获所有更改。还有许多其他示例,但是也许这可以使您想到?


1
看起来已经更加清晰了-除非您指出没有类似的功能存在,否则当然只是一个例子。
user6245072 '16

麻烦的是,当您在一个大型项目中有多个程序员时,确实存在(甚至更糟)这样的功能,这就是噩梦。还要考虑动态语言的函数在全局名称空间中,因此随着时间的推移,您可能会拥有几个getAnswer函数-它们都可以工作,并且它们彼此不同,因为它们在不同的时间加载。
daven11 '16

1
我猜这是对函数编程的误解。但是,说它们在全局名称空间中是什么意思?
user6245072 '16

3
“默认情况下,全局名称空间中的动态语言功能是默认的”,这是特定于语言的详细信息,而不是由于具有动态类型而引起的约束。
萨拉2016年

2
@ daven11“确实,我在这里使用javascript”,但是其他动态语言具有实际的名称空间/模块/包,并且可以在重新定义时向您发出警告。您可能过于概括了。
coredump

10

您似乎对使用大型静态项目可能会误以为是的判断有误解。这里有一些指针:

即使您声明了函数的返回类型,在编写了许多行代码之后,您也可能会忘记它,而您仍然必须返回至使用文本编辑器的搜索功能来声明该行的代码,核实。

大多数使用静态类型语言的人都使用该语言的IDE或与特定于语言的工具集成的智能编辑器(例如vim或emacs)。在这些工具中,通常有一种快速找到功能类型的方法。例如,对于Java项目上的Eclipse,通常有两种方法可以找到方法的类型:

  • 如果我要在'this'之外的其他对象上使用方法,则键入一个引用和一个点(例如someVariable.),然后Eclipse查找该类型,someVariable并提供该类型中定义的所有方法的下拉列表。当我向下滚动列表时,每个列表的类型和文档都会被选中。请注意,使用动态语言很难做到这一点,因为编辑器很难(在某些情况下是不可能)确定类型someVariable,因此无法轻松生成正确的列表。如果我想在上面使用方法,this只需按ctrl + space即可获得相同的列表(尽管在这种情况下,对于动态语言而言,实现起来并不难)。
  • 如果已经有针对特定方法的引用,则可以将鼠标光标移到该方法上,并且该方法的类型和文档显示在工具提示中。

如您所见,这比动态语言可用的典型工具要好一些(不是在动态语言中这是不可能的,因为有些语言具有相当不错的IDE功能-smalltalk是一个让人想起的东西-但很难做到)动态语言,因此不太可能使用)。

另外,由于函数是用funcname()...类型声明的,因此要知道类型,您将不得不在调用该函数的每一行中进行搜索,因为您只知道funcname,而在Python等语言中,您只需在声明中搜索仅发生一次的def funcname或函数funcname。

静态语言工具通常提供语义搜索功能,即,它们可以精确地找到特定符号的定义和参考,而无需执行文本搜索。例如,使用Eclipse用于Java项目,我可以在文本编辑器中突出显示一个符号,然后右键单击它,然后选择“转到定义”或“查找引用”以执行这些操作之一。您不需要搜索函数定义的文本,因为您的编辑器已经知道它的确切位置。

但是,相反的是,按文本搜索方法定义在大型动态项目中确实不如您建议的那样有效,因为在这样的项目中很容易存在多个同名方法,并且您可能没有易于使用的工具来消除您正在调用的工具中的一个的歧义(因为此类工具充其量很难编写,或者在一般情况下是不可能的),因此您必须手动执行。

而且,对于REPL,使用不同的输入来测试其返回类型的函数很简单

为静态类型的语言准备一个REPL并非不可能。Haskell是一个让人想起的例子,但是也有其他静态类型语言的REPL。但要点是,您无需执行代码即可以静态语言查找函数的返回类型 -可以通过检查确定它,而无需运行任何东西。

而对于静态类型的语言,您需要添加一些代码行并重新编译所有内容,以了解声明的类型。

即使您确实需要这样做,您也不必重新编译所有内容。大多数现代静态语言都有增量编译器,这些编译器只会编译已更改代码的一小部分,因此,如果您犯了类型错误,则几乎可以获得瞬时反馈。例如,Eclipse / Java将在您仍在键入错误突出显示类型错误。


4
You seem to have a few misconceptions about working with large static projects that may be clouding your judgement.好吧,我只有14岁,而且我在Android上的编程时间还不到一年,所以我猜很可能。
user6245072 '16

1
即使没有IDE,如果您从Java的类中删除一个方法并且某些东西依赖于该方法,则任何Java编译器都会为您提供使用该方法的每一行的列表。在Python中,当执行的代码调用缺少的方法时,它将失败。我经常使用Java和Python,并且我喜欢Python,因为它能以多快的速度运行事物,并且可以完成Java不支持的出色工作,但现实是我在Python程序中遇到了一些问题,而这些问题是不会发生的(直接)Java。在Python中,重构尤其困难。
JimmyJames

6
  1. 因为对于静态类型的语言,静态检查器更容易。
    • 如果没有编译,则至少没有动态语言功能,那么在运行时就不会有未解析的函数。这在ADA项目和微控制器C中很常见。(微控制器程序有时会变大……就像数百个kloc大。)
  2. 静态编译引用检查是函数不变式的子集,它可以用静态语言在编译时进行检查。
  3. 静态语言通常具有更多的引用透明性。结果是,新开发人员可以深入研究单个文件并了解正在发生的事情,并且可以修复错误或添加小的功能,而无需了解代码库中的所有奇特事物。

相比之下,例如javascript,Ruby或Smalltalk,开发人员会在运行时重新定义核心语言功能。这使得理解大项目变得更加困难。

更大的项目不仅有更多的人,而且还有更多的时间。每个人都有足够的时间忘记或继续前进。

有趣的是,我的一个熟人在Lisp中有一个安全的“ Job for Life”编程。除团队外,其他任何人都无法理解代码库。


Anecdotally, an acquaintance of mine has a secure "Job For Life" programming in Lisp. Nobody except the team can understand the code-base.真的那么糟糕吗?他们添加的个性化设置是否有助于他们提高生产力?
user6245072 '16

@ user6245072对于目前在这里工作的人来说,这可能是一个优势,但是这使招募新人变得更加困难。找到已经知道一种非主流语言的人或教给他们一种他们还不认识的语言,需要花费更多的时间。这可能会使项目在成功时难以扩大规模,也很难从波动中恢复过来-人们确实会搬走,升任其他职位...一段时间后,对于专家本身来说也可能是不利的-一旦您只写了10种左右的语言,就很难继续尝试新的东西。
绿巨人

您不能仅使用跟踪程序从正在运行的Lisp程序中创建单元测试吗?像在Python中一样,您可以创建一个称为print_args的装饰器(副词),该装饰器接受一个函数并返回修改后的函数,以打印出其参数。然后,您可以将其应用于sys.modules中的整个程序,尽管更简单的方法是使用sys.set_trace。
aoeu256

@ aoeu256我对Lisp运行时环境功能不熟悉。但是他们确实大量使用了宏,因此没有普通的Lisp程序员可以读取代码。由于宏更改了有关Lisp的所有内容,因此尝试对运行时执行“简单”的操作很可能无法正常工作。
Tim Williscroft

@TimWilliscroft您可以等到所有宏都展开后再进行此类操作。Emacs具有许多快捷键,可让您内联扩展宏(也许还有内联函数)。
aoeu256

4

我从不理解这种说法。老实说,即使您声明了函数的返回类型,在编写了许多行代码之后,您也可能会忘记它,而您仍然必须返回到使用的搜索功能声明它的行。您的文本编辑器进行检查。

这与您忘记返回类型无关,这总是会发生。关于该工具能够让您知道您忘记了返回类型。

另外,由于函数是用type声明的funcname()...,因此要知道类型,您将不得不在调用该函数的每一行中进行搜索,因为您只知道funcname,而在Python之类的语言中,您只能搜索def funcnamefunction funcname仅发生一次,在声明中。

这是语法问题,与静态类型完全无关。

当您想要查找声明而无需使用专用工具时,C系列语法确实是不友好的。其他语言没有这个问题。请参阅Rust的声明语法:

fn funcname(a: i32) -> i32

此外,使用REPL可以很容易地测试一个函数是否具有不同输入的返回类型,而对于静态类型的语言,您需要添加一些代码行并重新编译所有内容才能知道所声明的类型。

可以解释任何语言,并且任何语言都可以具有REPL。


因此,除了知道函数的返回类型显然不是静态类型语言的要点之外,静态类型在大型项目中真的有什么帮助?

我将以抽象的方式回答。

程序由各种操作组成,由于开发人员的某些假设,这些操作按其方式进行了布局。

有些假设是隐式的,有些则是显式的。一些假设涉及靠近它们的操作,一些假设涉及远离它们的操作。假设在明确表达时应易于识别,并尽可能接近其真值所在的地方。

错误是程序中存在的假设的表示,但在某些情况下不成立。要查找错误,我们需要确定错误的假设。要删除该错误,我们需要从程序中删除该假设或进行一些更改以使该假设真正成立。

我想将假设分为两种。

第一种是可能成立的假设,也可能不会成立,这取决于程序的输入。为了确定这种错误假设,我们需要在程序所有可能输入的空间中进行搜索。使用有根据的猜测和理性思考,我们可以缩小问题的范围,并在更小的空间内进行搜索。但是,尽管程序甚至增长了一点,但它的初始输入空间却以极大的速度增长-到了可以将其视为所有实际目的无限的程度。

第二类是对所有输入绝对成立的假设,或者对所有输入绝对错误的假设。当我们认为这种假设是错误的时,我们甚至不需要运行程序或测试任何输入。当我们确定这种假设是正确的时,当我们追踪一个错误(任何错误)时,我们就不用担心什么了。因此,有尽可能多的假设属于这种价值。

要将假设置于第二类中(与输入无关,始终为true或始终为false),我们需要在进行假设的地方提供最少的信息。在整个程序的源代码中,信息很快就会过时(例如,许多编译器不进行过程间分析,这使得大多数信息的调用都难以进行)。我们需要一种方法来使所需的信息保持最新(有效且邻近)。

一种方法是使此信息的源尽可能靠近要使用的地方,但是对于大多数用例而言,这是不切实际的。另一种方法是经常重复信息,从而在源代码中更新其相关性。

您已经猜到了,静态类型就是这样-散布在源代码中的类型信息信标。该信息可用于将关于类型正确性的大多数假设放在第二类中,这意味着就类型兼容性而言,几乎所有操作都可以归类为始终正确或始终不正确。

当我们的类型不正确时,该分析通过尽早而不是迟早地引起我们的注意来节省我们的时间。当我们的类型正确时,通过确保在发生错误时可以立即排除类型错误,该分析为我们节省了时间。


3

您还记得一句古老的格言:“垃圾进,垃圾出”,这就是静态类型有助于防止的事情。它不是通用的灵丹妙药,而是对例程接受和返回什么样的数据的严格要求,这意味着您可以肯定自己可以正确使用它。

因此,当您尝试在基于字符串的调用中使用它时,返回整数的getAnswer例程将无用。静态类型s已经告诉您要小心,您可能正在犯错。(当然,您可以覆盖它,但是您必须确切地知道您正在执行的操作,并使用强制转换在代码中指定它。通常,您不想这样做-入侵最终将圆钉钉入方孔永远无法正常工作)

现在,您可以通过使用复杂的类型,创建具有矿石功能的类来进一步进行开发,然后开始传递这些功能,然后突然在程序中获得更多的结构。结构化程序是使正确运行和维护容易得多的程序。


您不必进行静态类型推断(pylint),可以进行动态类型推断chrislaffra.blogspot.com/2016/12/…这也由PyPy的JIT编译器完成。动态类型推断还有另一种版本,其中计算机将模拟对象随机放置在参数中,并查看导致错误的原因。在99%的情况下,暂停问题并不重要,如果花费太多时间,只需停止算法即可(这是Python处理无限递归的方式,它具有可以设置的递归限制)。
aoeu256
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.