当我在另一个不存在或具有错误数量的模块中调用函数时,为什么没有编译时错误或警告?
编译器在一个模块中包含所有导出信息,以使这成为可能。它只是尚未实施,还是有技术原因导致我看不到?
当我在另一个不存在或具有错误数量的模块中调用函数时,为什么没有编译时错误或警告?
编译器在一个模块中包含所有导出信息,以使这成为可能。它只是尚未实施,还是有技术原因导致我看不到?
我不知道它为什么会丢失(可能是因为模块是完全独立的,并且一个模块的编译实际上并不依赖于另一个模块——但这只是猜测)。但我相信你可以通过透析器静态分析发现这样的问题。看看http://www.erlang.org/doc/man/dialyzer.html
它是系统本身的一部分,因此请尝试将其包含在您的工作流程中。
正如其他人所说。模块是单独编译的,绝对不能保证编译时存在的环境与运行时退出的环境相同。这意味着在编译时检查模块或其中的函数是否存在基本上是没有意义的。在运行时,该模块可能会或可能不会被加载,您调用的函数可能会或可能不会在模块中定义,或者它可能会执行与您预期完全不同的事情。
这一切都归功于 Erlang 系统的动态特性。没有真正的方法来定义运行时系统中的内容。热代码加载是其中的一部分,并且由于系统的动态特性而可以正常工作。这意味着您可以在运行时重新定义系统,您可以加载具有不同接口的现有模块的新版本,您可以加载全新的模块并删除现有模块。
为此,必须在运行时完成所有关于模块或函数是否存在的检查。
像透析器这样的工具可以帮助解决这个问题,但它们确实假设您在运行时没有做任何“有趣”的事情,并且您检查的系统与您运行的系统相同。这当然很好,但非常静态。并且与 Erlang 的天性相反,即在所有方面都是动态的。
不幸的是,在这种情况下,你不能既吃蛋糕又吃蛋糕。
您可以使用该xref
应用程序检查已弃用、未定义和未使用的函数(以及更多!)的使用情况。
编译模块debug_info
:
Eshell V6.2 (abort with ^G)
1> c(test, debug_info).
{ok,test}
检查模块xref:m/1
:
2> xref:m(test).
[{deprecated,[]},
{undefined,[{{test,start,0},{erlang,foo,0}}]},
{unused,[]}]
您可能想在xref
这里查看更多信息:
这是由于热代码加载。每个模块都可以在任何特定时间加载。因此,当您的模块A
代码中有调用函数时,当您的模块源代码没有函数B:F
时,您无法在编译时判断它是错误的。想象一下:您通过调用来编译模块。您将模块加载到没有功能的内存中。然后你加载包含调用但不调用它的模块。然后用.编译新版本的模块。然后加载这个新模块,然后你就可以调用了,一切都很好。想象一下您的模块使模块运行并加载它。您无法在任何特定时间判断该模块是错误的B
B:F
A
B:F
B
B:F
A
B:F
B
B:F
B:F
A
B
A
包含对不存在函数的调用B:F
。
当您编译例如alpha
调用 的模块时beta:some_function(...)
,编译器不能假定某些特定版本的beta
将在运行时使用。beta
也许你会在编译后编译一个更新的版本,alpha
这将有正确的some_function
导出。也许您将上传alpha
以在具有所有其他模块的不同主机上使用。
因此,编译器只编译远程调用,任何错误(不存在的模块或函数)都会在运行时解决,此时beta
将加载某个版本。
在我看来,大多数(如果不是全部的话)编译器不会在编译时验证函数是否存在。通常需要的是函数的原型声明:返回值的类型、所有参数的列表和类型。这是在 C/C++ 中通过在每个模块定义(不是 .c 或 .cpp)中包含 some_file.h 来完成的。
在 Erlang 中,这种类型验证是在程序运行时动态完成的,因此不需要包含这些定义。甚至完全没用,因为 Erlang 允许在运行时升级应用程序,因此在应用程序生命周期内,函数类型可能会发生变化,或者函数可能会有意或无意地消失;这就是为什么 Erlang 设计者选择在运行时而不是在构建时进行验证的原因。
您所说的错误通常发生在代码生成的链接阶段,当“编译器”试图将一些单独的目标代码收集在一起以构建可执行文件或库时,在此阶段链接器解决了所有外部地址(对于共享变量,静态调用...)。这个阶段在 Erlang 中不存在,一个模块是完全自包含的;它不与应用程序的其余部分共享任何内容,没有变量或函数地址。
当然,在更新正在运行的生产程序之前,必须使用一些工具并进行一些测试,但我认为这些验证与算法本身的正确性具有完全相同的重要性。