【问题标题】:Is there a function that can make a string representation of any type?有没有可以制作任何类型的字符串表示的函数?
【发布时间】:2022-01-17 22:49:46
【问题描述】:

最后一小时我一直在拼命寻找 OCaml 库中将 'a 转换为字符串的方法:

'a -> string 图书馆里有什么我没找到的吗?还是我必须做不同的事情(我自己写所有东西)?

【问题讨论】:

  • 你到底想做什么,如果你解释你想要什么,也许我们可以给你一些解决方法。但正如下面的答案中所说,这个问题没有通用的解决方案。
  • 对不起!我现在才看到你的评论。我的问题是我正在寻找一种打印不同类型的通用方法。以树为例:我有不同的类型作为值,比如 int、string、float 或 created 类型。现在我想打印它们。

标签: ocaml


【解决方案1】:

不可能在 OCaml 中编写 'a -> string 类型的打印函数 show

确实,在 OCaml 中编译后类型会被擦除。 (它们实际上在类型检查之后被擦除,这是编译管道的早期阶段之一)。

因此,'a -> _ 类型的函数可以:

  • 忽略它的论点:
let f _ = "<something>"
  • 查看值的内存表示
let f x = if Obj.is_block x then "<block>" else "<immediate>"

即使窥视一个值的内存表示形式的用处也有限,因为许多不同的类型将共享相同的内存表示形式。

如果你想打印一个类型,你需要为这个类型创建一个打印机。您可以使用Fmt 库(或标准库中的Format 模块)手动完成此操作

type tree = Leaf of int | Node of { left:tree; right: tree } 
let pp ppf tree = match tree with 
| Leaf d -> Fmt.fp ppf "Leaf %d" d
| Node n -> Fmt.fp ppf "Node { left:%a; right:%a}" pp n.left pp n.right 

或使用 ppx(OCaml 的小型预处理扩展),例如 https://github.com/ocaml-ppx/ppx_deriving

type tree = Leaf of int | Node of { left:tree; right: tree } [@@deriving show]

【讨论】:

    【解决方案2】:

    您正在寻找的是'a. 'a -&gt; string 类型的有意义 函数,具有参数多态性(即单个函数可以对所有可能的类型@ 987654322@,即使是那些在创建函数时不存在的)。这在 OCaml 中是不可能的。以下是取决于您的编程背景的解释。

    来自 Haskell

    如果您因为熟悉 Haskell 函数 show 而期待这样的函数,那么请注意它的类型实际上是 show :: Show a =&gt; a -&gt; String。它使用类型类Show a 的实例,编译器在调用点隐式插入该实例。这不是参数多态,这是临时多态(show 被重载,如果你愿意的话)。 OCaml 中没有这样的功能(但是?有针对该语言未来的项目,寻找“模块化隐式”或“模块化显式”)。

    来自 OOP

    如果您因为熟悉 OO 语言而期待这样的函数,其中每个值都是具有方法 toString 的对象,那么 OCaml 不是这种情况。 OCaml 没有普遍使用对象模型,并且 OCaml 值的运行时表示不保留(或很少)类型概念。我建议您参考@octachron 的答案。

    同样,OOP 中的toString 不是参数多态性,而是重载:没有为所有 可能类型定义的单一方法toString。取而代之的是同名方法的多个(可能非常不同)实现。在某些 OO 语言中,程序员尝试遵循为他们定义的每个类都使用该名称实现方法的原则,但这只是一种编码实践。可以很好地创建没有这种方法的对象。

    [ 实际上,这两个世界所涉及的概念非常相似:Haskell 需要一个类型类的实例 Show a 提供一个函数 show; OOP 需要一个类Stringifiable 的对象(例如)提供一个方法toString。或者,当然,后代类型类/类的实例/对象。 ]

    【讨论】:

      【解决方案3】:

      如果您只想快速解决问题,可以使用Batteries 库中的dump。它不适用于所有情况,但它确实适用于原语、列表等。它访问底层的原始内存表示,因此能够(在某种程度上)克服其他答案中提到的困难。

      你可以这样使用它(通过opam install batteries安装后):

      # #require "batteries";;
      # Batteries.dump 1;;
      - : string = "1"
      # Batteries.dump 1.2;;
      - : string = "1.2"
      # Batteries.dump [1;2;3];;
      - : string = "[1; 2; 3]"
      

      如果您想要更“合适”的解决方案,请按照@octachron 的建议使用ppx_deriving。它更可靠/可维护/可定制。

      【讨论】:

      • 声称Batteries.dump适用于记录和代数类型取决于您所说的working。在那个级别,它不知道构造函数的名称(None 转储为0Some 42 转储为(42))或记录字段的名称,甚至无法区分两者。所有记录和代数类型看起来都一样:带有数字标签的元组。您唯一拥有的是一个基本启发式,当形状恰好与列表的形状兼容时切换到列表语法(这可能是偶然发生的:(42, 0) 被转储为[42])。
      • @Maëlan 是的,那部分是从Batteries.dump 文档中复制和粘贴的,但现在看它有点误导。更新了答案并删除了“使用记录和代数类型”部分。
      【解决方案4】:

      另一种可能性是使用https://github.com/ocaml-ppx/ppx_deriving with 将创建 Path.To.My.Super.Type.t 的函数 -> 然后您可以将其与您的值一起使用。但是你仍然需要手动跟踪类型的路径,但总比没有好。

      另一个项目提供类似于 Batterie https://github.com/reasonml/reason-native/blob/master/src/console/README.md 的功能(我没有测试过 Batterie,所以不能给出意见)它们有相同的限制:它们内省运行时编码,因此无法获得真正可用的东西。我认为它是在考虑 Windows/浏览器的情况下完成的,所以如果需要跨平台,我会在之前测试这个(除非电池已经被拉出)。即使代码源合理,您也可以在 OCaml 中使用相同的 API。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-03-17
        • 1970-01-01
        • 2023-03-28
        • 1970-01-01
        • 2011-03-30
        • 1970-01-01
        • 1970-01-01
        • 2014-01-09
        相关资源
        最近更新 更多