(懒惰)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
这样我就可以以编程方式获取所有测试的列表)。
更多详情:
我认识到解决这个问题的正确方法(可能)是编写一个可以为任何泛型类型生成底层类型的函数。对于内置基元(和 list
、option
等),这很简单(只是冗长)。这对于记录变得更加复杂(可能在 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.magic
。 Obj.magic
的测试代码听起来不正确...
为了避免 int
的值立即失败,我会将类型更改为 unit -> int
或 int option
以获得简单的默认值。
let a = fun () -> failwith "undefined"
let a = None
或者,也许,一般使用 [@@deriving xxx]
生成给定类型的默认值可能很适合您的情况,因为您有 mli。但我担心它需要一个新的 ppx_deriving
的派生器或用 typerep
和 ppx_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
,它甚至不会编译。
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
这样我就可以以编程方式获取所有测试的列表)。
更多详情:
我认识到解决这个问题的正确方法(可能)是编写一个可以为任何泛型类型生成底层类型的函数。对于内置基元(和 list
、option
等),这很简单(只是冗长)。这对于记录变得更加复杂(可能在 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.magic
。 Obj.magic
的测试代码听起来不正确...
为了避免 int
的值立即失败,我会将类型更改为 unit -> int
或 int option
以获得简单的默认值。
let a = fun () -> failwith "undefined"
let a = None
或者,也许,一般使用 [@@deriving xxx]
生成给定类型的默认值可能很适合您的情况,因为您有 mli。但我担心它需要一个新的 ppx_deriving
的派生器或用 typerep
和 ppx_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
,它甚至不会编译。