gpt4 book ai didi

ocaml - 什么时候应该在 OCaml 中使用可扩展的变体类型?

转载 作者:行者123 更新时间:2023-12-04 12:44:46 24 4
gpt4 key购买 nike

我在 extensible variant types 之前上过 OCaml 类(class)被介绍了,我对他们了解不多。我有几个问题:

  • (此问题已被删除,因为它吸引了“客观上无法回答”的近距离投票。)
  • 使用 EVT 的低级后果是什么,例如性能、内存表示和(取消)编码?

  • 请注意,我的问题是关于可扩展变体类型的,与建议的与此相同的问题不同(该问题是在引入 EVT 之前提出的!)。

    最佳答案

    可扩展变体与标准变体在以下方面有很大不同
    运行时行为。

    特别是,扩展构造函数是存在于内部的运行时值
    定义它们的模块。例如,在

     type t = ..
    module M = struct
    type t +=A
    end
    open M

    第二行定义一个新的扩展构造函数值 A并将其添加到 M 的现有扩展构造函数在运行时。
    相反,经典变体在运行时并不真正存在。

    通过注意到我可以使用来观察这种差异是可能的
    经典变体的仅 mli 编译单元:
     (* classical.mli *)
    type t = A

    (* main.ml *)
    let x = Classical.A

    然后编译 main.ml

    ocamlopt classical.mli main.ml



    没有麻烦,因为 Classical 中没有任何值(value)模块。

    与可扩展变体相反,这是不可能的。如果我有
     (* ext.mli *)
    type t = ..
    type t+=A

    (* main.ml *)
    let x = Ext.A

    然后命令

    ocamlopt ext.mli main.ml



    失败了

    Error: Required module `Ext' is unavailable



    因为扩展构造函数的运行时值 Ext.A不见了。

    您还可以查看扩展构造函数的名称和 id
    使用 Obj模块来查看这些值
     let a = [%extension_constructor A]
    Obj.extension_name a;;

    • : string = "M.A"

     Obj.extension_id a;;

    • : int = 144


    (这个 id 非常脆弱,它的值(value)没有特别意义。)
    重要的一点是扩展构造函数使用它们的
    内存位置。因此,具有 n 的构造函数参数被实现
    作为 n+1 的 block 第一个隐藏参数是扩展名的参数
    构造函数:
    type t += B of int
    let x = B 0;;

    在这里, x包含两个字段,而不是一个:
     Obj.size (Obj.repr x);;

    • : int = 2


    第一个字段是扩展构造函数 B :
     Obj.field (Obj.repr x) 0 == Obj.repr [%extension_constructor B];;

    • : bool = true


    前面的语句也适用于 n=0 : 可扩展的变体永远不会
    表示为标记整数,与经典变体相反。

    由于编码不保持物理平等,这意味着可扩展
    sum 类型不能在不丢失其身份的情况下编码。例如,做
    往返
     let round_trip (x:'a):'a = Marshall.from_string (Marshall.to_string x []) 0

    然后用
      type t += C
    let is_c = function
    | C -> true
    | _ -> false

    导致失败:
       is_c (round_trip C)

    • : bool = false


    因为在读取编码值时往返分配了一个新 block
    这与异常已经存在的问题相同,因为异常
    是可扩展的变体。

    这也意味着可扩展类型上的模式匹配是完全不同的
    在运行时。例如,如果我定义一个简单的变体
     type s = A of int | B of int

    并定义一个函数 f作为
    let f = function
    | A n | B n -> n

    编译器足够聪明,可以优化这个函数来简单地访问
    参数的第一个字段。

    您可以咨询 ocamlc -dlambda上面的函数表示为
    Lambda 中介表示为:
    (function param/1008 (field 0 param/1008)))

    然而,对于可扩展的变体,我们不仅需要一个默认模式
       type e = ..
    type e += A of n | B of n
    let g = function
    | A n | B n -> n
    | _ -> 0

    但是我们还需要将参数与每个扩展构造函数进行比较
    匹配导致匹配的更复杂的 lambda IR
     (function param/1009
    (catch
    (if (== (field 0 param/1009) A/1003) (exit 1 (field 1 param/1009))
    (if (== (field 0 param/1009) B/1004) (exit 1 (field 1 param/1009))
    0))
    with (1 n/1007) n/1007)))

    最后,以可扩展变体的实际示例结束,
    在 OCaml 4.08 中,Format 模块替换了其基于字符串的用户定义标签
    具有可扩展的变体。

    这意味着定义新标签如下所示:

    首先,我们从新标签的实际定义开始
     type t =  Format.stag = ..
    type Format.stag += Warning | Error

    然后这些新标签的翻译功能是
    let mark_open_stag tag =
    match tag with
    | Error -> "\x1b[31m" (* aka print the content of the tag in red *)
    | Warning -> "\x1b[35m" (* ... in purple *)
    | _ -> ""

    let mark_close_stag _tag =
    "\x1b[0m" (*reset *)

    然后安装新标签
     let enable ppf =
    Format.pp_set_tags ppf true;
    Format.pp_set_mark_tags ppf true;
    Format.pp_set_formatter_stag_functions ppf
    { (Format.pp_get_formatter_stag_functions ppf ()) with
    mark_open_stag; mark_close_stag }

    借助一些辅助功能,可以使用这些新标签进行打印
     Format.printf "This message is %a.@." error "important"
    Format.printf "This one %a.@." warning "not so much"

    与字符串标签相比,有几个优点:
  • 拼写错误的空间更小
  • 无需序列化/反序列化潜在的复杂数据
  • 同名的不同扩展构造函数之间没有混淆。
  • 链接多个用户定义的mark_open_stag因此函数是安全的:
    每个函数只能识别自己的扩展构造函数。
  • 关于ocaml - 什么时候应该在 OCaml 中使用可扩展的变体类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54730373/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com