如何省略“开放......”?

时间:2014-11-08 15:00:26

标签: ocaml

假设我有一些文件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不同,我已经内置了防止标识符无意遮蔽的保护措施,这降低了使用非限定标识符的风险。

3 个答案:

答案 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).tSSet.t = Set.Make(S).t不兼容,因为OCaml没有看到SString是相同的模块,它只是到目前为止因为他们碰巧定义了相同的类型。从OCaml 4.02开始,类型检查器和代码生成器会跟踪此类别名,因此我相信FbbFoo.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)

关于你的问题,我认为反复试验是一个不错的选择。特别是如果您使用的是好的工具,例如emacsmerlin。否则,您可以尝试询问utop特定模块的内容:

 # #typeof "Int64";;

但要注意,它可能非常大。

P.S。我已更新this answer,其中包含有关如何使用utop

的信息

更新

我忘了注意,有些情况下你应该打开一个模块。实际上,现代OCaml编程中存在一种趋势,即使用一个特定的伞形模块运送您的库,这将导出您认为应该导出的所有内容。它就像python的包。当你进行链接时,这也有助于解决命名空间冲突的问题。

因此,如果您要使用core库,则应使用此open Core.Std启动所有文件。

顺便说一句,如果您遇到此模块的问题,那么您最好尝试阅读在线文档,而不是#typeof。太大了。官方的参考点是here,但是css很糟糕,所以我建议您使用this引用,尽管它已经过时且非官方。