假设我有一些文件frobozz.ml
,其第一行是:
open Foo
据我所知,如果我删除此行 1 ,frobozz.ml
可能不再编译,除非我还用其余内容替换某些标识符X
他们的完全合格"表格Foo.X
。
是否存在一种有效/安全的方式(与编译和查看相反,即试错),以确定X
中我需要执行此操作的frobozz.ml
标识符(如果我删除了open Foo
行?
(抱歉这个愚蠢的问题:我对OCaml很新。)
<子> 1
为什么我要摆脱open ...
行?有三个原因:1)目前,添加open Foo
形式的行导致编译器错误,该行在用于编译之前用于编译,然后进行添加;我希望通过完全避免open Foo
行,我将能够编译该文件; 2)在我学习OCaml的这个阶段,也就是说,直到我对各种软件包/模块的内容更加熟悉,我认为使用完整的合格名称会更有启发性,从而保持一切的出处完全清楚,即使这意味着键入不方便的长标识符; 3)原则上,我认为命名空间很好;每当存在无意遮蔽的风险时(我后面定义的先前定义的标识符),我都不喜欢使用不合格的标识符,因为&#34;阴影错误&#34;可能真的难以捕捉;如果我想减少输入(或者只是让我的代码看起来不那么杂乱),如果我的语言支持它,我更喜欢将命名空间别名为一些短字符串;例如在Python中,语言支持以下三种可能性,我更喜欢最后一种:
import foo.bar.baz
...
foo.bar.baz.initialize() # a lot of typing/clutter
from foo.bar.baz import initialize
...
initialize() # risk of insidious shadowing bugs
import foo.bar.baz as fbb
...
fbb.initialize() # safer, IMO
当然,最后一种形式并没有完全消除阴影错误的风险,至少在Python中,模块别名可以在文件中相互遮挡(以及非混淆模块名称);即以下内容不会产生警告
import foo.bar.baz as fbb
...
import fro.bnitz.bong as fbb
...
fbb.initialize()
但是这种风险可以通过将所有import语句放在文件顶部的Python惯例来缓解,这使得很容易知道已经使用了哪些别名。 子>
<子> 我不知道OCaml是否支持这样的别名(或其道德等同物#34;)。另一方面,OCaml与Python不同,我已经内置了防止标识符无意遮蔽的保护措施,这降低了使用非限定标识符的风险。
答案 0 :(得分:4)
您可以使用OCaml来保护您免受阴影错误的影响,而不是删除open语句。如果您打开警告44(在版本4.01中添加),则只要您使用被open语句遮蔽的标识符,OCaml就会发出警告。使用open!
语法可以使此警告静音。
所以在你的例子中你应该使用:
open! Core.Std
open Foo
并使用警告44编译它:
ocamlc -w +44 bar.ml
这会针对您使用Foo
中使用的Core.Std
隐藏某些内容的每个标识符发出警告。
答案 1 :(得分:3)
我不知道有一个现成的工具来进行这种转换,但这并不意味着没有一个。然而,编译器确实提供了一个关键工具。使用-annot
option编译您的程序。这会为每个源文件生成一个.annot
文件,其中包含有关编译器推断的各种内容的信息,包括类型和绑定。
此处它正在处理示例文件。
open Str
let foo = regexp "foo"
let regexp x = 42
let shadow = regexp "foo"
以下是.annot
文件中与表达式中regexp
标识符的两种用法相对应的部分,第一部分来自Str
模块,第二部分来自本地重新定义。
"a.ml" 2 9 19 "a.ml" 2 9 25
type(
string -> Str.regexp
)
ident(
ext_ref Str.regexp
)
"a.ml" 4 50 63 "a.ml" 4 50 69
type(
string -> int
)
ident(
int_ref regexp "a.ml" 3 32 36 "a.ml" 3 32 42
)
.annot
文件的格式没有记录,但您可以查看源代码以及解析它的各种程序,包括OCaml附带的Emacs模式。您可以重用OCamlSpotter中的代码。
关于定义模块的快捷方式,Python import foo.bar.baz as fbb
的等价物是module Fbb = Foo.Bar.Baz
。在OCaml到版本4.01中,别名与所有上下文中的原始别名相同,因为抽象类型上的相等性基于通向类型的模块路径,而OCaml仅跟踪类型等式,而不是模块等式。例如:
module S = String;;
fun (x : S.t) -> (x : String.t);; (*fine: S.t is the same type as String.t*)
module StringSet = Set.Make(String);;
module SSet = Set.Make(S);;
fun (x : StringSet.elt) -> (x : SSet.elt);; (*still fine, still the same type*)
fun (x : StringSet.t) -> (x : SSet.t);; (*incompatible types*)
抽象类型StringSet.t = Set.Make(String).t
和SSet.t = Set.Make(S).t
不兼容,因为OCaml没有看到S
和String
是相同的模块,它只是到目前为止因为他们碰巧定义了相同的类型。从OCaml 4.02开始,类型检查器和代码生成器会跟踪此类别名,因此我相信Fbb
和Foo.Bar.Baz
可以互换使用。
与Python的另一个区别是外部模块不需要显式声明。 OCaml的open
实际上是Python的import * from
;任何外部包都可以在没有事先声明的情况下用作模块。
答案 2 :(得分:2)
实际上,在OCaml中我们尝试根本不打开模块,并挤压已打开模块的范围。例如,在OCaml中,您可以使用两种不同的语法在本地打开模块:
let sum x y =
let open Int64 in
x + y
或者,有时甚至更方便:
let sum x y = Int64.(x + y)
关于你的问题,我认为反复试验是一个不错的选择。特别是如果您使用的是好的工具,例如emacs
和merlin
。否则,您可以尝试询问utop
特定模块的内容:
# #typeof "Int64";;
但要注意,它可能非常大。
P.S。我已更新this answer,其中包含有关如何使用utop
我忘了注意,有些情况下你应该打开一个模块。实际上,现代OCaml编程中存在一种趋势,即使用一个特定的伞形模块运送您的库,这将导出您认为应该导出的所有内容。它就像python的包。当你进行链接时,这也有助于解决命名空间冲突的问题。
因此,如果您要使用core
库,则应使用此open Core.Std
启动所有文件。
顺便说一句,如果您遇到此模块的问题,那么您最好尝试阅读在线文档,而不是#typeof
。太大了。官方的参考点是here,但是css
很糟糕,所以我建议您使用this引用,尽管它已经过时且非官方。