(懒惰)OCaml 中的 Haskell undefined/bottom

(Lazy) Haskell undefined/bottom in OCaml

Haskell 有一个非常大的 undefined 值,它懒惰地引发异常(在评估时)。 Ocaml当然是严格的,所以as far as I can tell there is no equivalent of Haskell's undefined。但这很不幸,因为这意味着值没有底部类型。假设我想要一个

val a : int

我当然可以

let a = failwith "undefined"
let () =
  print_string "something unrelated\n"

这很愉快地编译了。不幸的是,在 运行 处理它时,我们得到了未定义的异常(这是预期的)。

我想要的是让 a 成为 bottom/undefined 值而不改变它的类型(所以像 Lazy 这样的东西不会起作用)。这可能吗?

额外的细节: 所以我要求的可能听起来很愚蠢。为了减少对我为什么不应该这样做的任何评论,请允许我简要描述一下我的用例。我正在编写一个脚本来修改 mli 文件的 AST 以生成与其签名匹配的 "empty" ml 文件。对于一般情况,可能 val a : int 在你的 mli 中,所以我需要一种方法来一般地合成一个底层类型。如果我只需要编译成功,failwith "undefined" 就可以工作。但不幸的是,我还需要 link 这个 ml 文件针对 OUnit 测试套件和 运行 它(显然该套件会失败,但目的是能够 运行 它与 -list-test 这样我就可以以编程方式获取所有测试的列表)。

更多详情: 我认识到解决这个问题的正确方法(可能)是编写一个可以为任何泛型类型生成底层类型的函数。对于内置基元(和 listoption 等),这很简单(只是冗长)。这对于记录变得更加复杂(可能在 stdlib 中定义,但也可能在同一文件或不同的包中定义)。为了处理这个问题,我的 AST 转换器需要对 OCaml 类型系统和包的文件导入策略有一个完整的理解,这比我想要 to/should 包含的逻辑要多得多。

重要说明: 正如@PatJ 和@camlspotter 所指出的,Obj.magic 会导致一些可怕的行为。事实上,我会冒险使用它 returns 的东西,而无需某种证明(例如 Coq)证明它的使用是有保证的,这类似于 C 中的未定义行为。你什么也得不到,你可能会遇到段错误,或者你可以放火烧你的房子。一切皆有可能。如果您 从不使用或调用任何未定义的值,则应 使用下面的解决方案。 所以,这对我来说很好,因为我需要仅 link 针对此包的 OUnit 测试套件具有未定义的值(但 OUint 从不调用包 中的任何内容)。这允许您使用 -list-test 运行 OUint 测试套件(使用 failwith 运行 在 let a = failwith "undefined" 的情况下测试二进制文件立即失败)。但是由于 -list-test 从不 运行 其 link 反对的任何代码,它从不尝试使用未定义的值,所以这个特定的用例是安全的。谨慎行事。

我设法找到了一个相当棘手的解决方案。 Obj 模块启用了一些花哨的 运行time 东西,这些东西需要一些松散的类型(并且似乎不做 运行time 检查)。它提供了一些具有一些有前途的签名的功能:

type t 
val repr : 'a -> t
val obj : t -> 'a
val magic : 'a -> 'b

经过一些实验,很明显这些是不安全的操作(它们不进行任何 运行时间检查),这非常适合我们的用例(因为我们不太关心会发生什么如果您尝试使用这些存根值)。这些中的任何一个似乎都足以等同于 Haskell 的 undefined:

Obj.obj (Obj.repr 0) (* 0 could be unit, another primitive, etc. *)
Obj.magic 0

当然,当您尝试在任何地方使用这些值时,等效性就结束了。您可能会遇到一些段错误。对于我前面提到的用例,这很好,因为 OUnit -list-test 实际上从未 运行 测试(如果有,它们在单独的进程中 运行,因此段错误不会影响测试马具)。

我很想知道是否有任何对 OCaml 内部有更多经验的人可以对这种方法发表评论。如果使用这些值导致异常而不是潜在的段错误(或其他一些未定义的行为——对于 0 有时似乎什么都没发生),那将是非常酷的(尽管在这种情况下并不是真正需要的)。

OCaml 没有 undefined。我认为这是幸运的,因为我在 Haskell 唯一的麻烦就是懒惰。 :-P

我不惜一切代价避免Obj.magicObj.magic 的测试代码听起来不正确...

为了避免 int 的值立即失败,我会将类型更改为 unit -> intint option 以获得简单的默认值。

let a = fun () -> failwith "undefined"
let a = None

或者,也许,一般使用 [@@deriving xxx] 生成给定类型的默认值可能很适合您的情况,因为您有 mli。但我担心它需要一个新的 ppx_deriving 的派生器或用 typerepppx_typerep_conv 编程。

没看到有人提到assert false,可能不是bottom(),因为它有值,但是一般type a. a.

我在“运行时不应到达此处,但类型检查器会”匹配表达式中看到它。
它允许 Haskell 的 undefined 用法的一个子集,因为 OCaml 不是普遍懒惰的,如果你说这样的话,它会 blow-up:

let undefined = assert false
let _ = List.length [undefined; undefined; undefined]

另外,根据 OCaml 手册,assert false 是断言的特例,-noassert 对其没有影响,因为它直接被简化为断言异常。给它多态 属性.

另外,它在断言所在的地方失败了,所以当你做类似

的事情时
(*1*) let undefined = assert false
(*n*) let... = undefined

断言,如果由于某种原因发生,将指向错误的代码行,即第 27 行。 1 与第 n 行相对。

此外,还有一个 (??),我认为它在手册中是为 camlp4 保留的,但 merlin 可以识别它。它在用法上可能更接近 Haskell 的 undefined;没有值,用作 type-checked 占位符。不像 assert false,它甚至不会编译。