【问题标题】:How to print lists with different types in Ocaml?如何在 Ocaml 中打印不同类型的列表?
【发布时间】:2020-02-11 00:23:41
【问题描述】:

我正在使用 Ocaml 中的列表,因此我编写了一个打印列表内容的函数。 这是我的代码:

let leastB = [false; true; true; true; true; false; false; false; true]
let leastI = [-16; 4; 7; 3444; -100]

let prListInt l =
    Printf.printf "[";
let rec prListIntrec l =    
    match l with
    [] -> Printf.printf "]\n"
    | g :: []-> Printf.printf "%d]\n" g
    | g :: t -> Printf.printf "%d; " g; prListIntrec t
in
    prListIntrec l


let prListB l =
    Printf.printf "[";  
let rec prListBrec l =  
    match l with
    [] -> Printf.printf "]\n"
    | g :: []-> Printf.printf "%B]\n" g
    | g :: t -> Printf.printf "%B; " g; prListBrec t
in
    prListBrec l


let () =
    prListB leastB;
    prListInt leastI

它工作正常,但我需要为每种类型的数据创建一个新函数还是有办法统一这些函数?

任何改进代码的提示(它是惯用的 Ocaml 吗?)?

【问题讨论】:

  • 这几乎是高阶函数用例的教科书示例。你了解过吗?这是作业还是练习?因为它确实看起来像一个
  • 查看Batteries.dump,这对于打印任意对象很有用
  • 这是一个练习,我正在从一本书中学习(没有解决方案),准备下学期的课程。高阶函数我还没学过,也不知道是什么意思。
  • 高阶函数是将另一个函数作为参数(或返回一个函数)的函数,通常是为了特化更通用算法的一小部分。 List.map 是高阶函数的一个例子。您当前所在的部分的标题是什么?
  • 制作清单 ;-)

标签: list printf ocaml


【解决方案1】:

在 OCaml 中打印事物列表的惯用方法是使用标准的 Format 模块,顺便提一下,该模块提供了 pp_print_list 函数,该函数接受项目打印机、分隔符打印机和列表并打印所述项目的列表,例如,

let pp_comma ppf () = Format.fprintf ppf ",@ "

let pp_int_list ppf ints = 
  Format.fprintf ppf "[%a]" 
    Format.(pp_print_list ~pp_sep:pp_comma pp_print_int) ints

# Format.printf "@[hello = [%a]@]@\n" pp_int_list [1;2;3];;
hello = [1, 2, 3]

pp_print_list 函数有类型

?pp_sep:(formatter -> unit -> unit) ->
(formatter -> 'a -> unit) -> 
(formatter -> 'a list -> unit)

即,它是一个接受两个函数(其中一个是可选的)并返回一个打印列表的函数的函数。如果为了简洁起见省略pp_sep 参数,那么我们可以说pp_print_list 接受'a 打印机并返回'a list 打印机,其中printer 是formatter -> 'a -> unit 类型的函数。 pp_print_list 函数是所谓的高阶函数,因为它需要另一个函数作为参数。在一般的函数式编程中,特别是在 OCaml 中,高阶函数非常普遍,所以实际上没有人重视它。

由于 OCaml 没有任何自省工具,并且在编译过程中会删除类型信息,因此除了 Format 模块之外,该语言中没有通用的漂亮打印工具。因此,对于每个新定义的类型,我们必须提供一个打印机,即formatter -> t -> unit 类型的函数,其中t 是我们的新类型。这是许多静态编译语言共有的习语,参见。使用 Haskell 的 Show 类或在 C++ 中实现 << 函数。 OCaml 中没有类型类或重载,因此打印机函数没有正式或规范的名称,但在 Janestreet 的核心中有一个约定将此类函数命名为 pp、cf Int.ppFloat.pp 等图书馆。

如果我们回到您的解决方案,那么很容易看出printListBrecprListIntrec 基本相同,它们只是项目的打印方式不同。因此,我们可以使用一个函数来参数化这样的函数,该函数接受列表项类型的值,例如,将其转换为字符串类型,例如,

let rec prGen prItem l =  
  match l with
  [] -> Printf.printf "]\n"
  | g :: []-> Printf.printf "%s]\n" (prItem g) g
  | g :: t -> Printf.printf "%s; " (prItem g); prGen prItem t

这个函数已经更通用了,虽然有点不习惯。由于翻译成字符串然后打印效率不是很高(如果我们可以直接打印,为什么要创建中间对象),我们使用打印机和%a说明符(打印机的描述见对应的Printf和Format模块) .最后,我们在 OCaml 中不使用 camelCase(尽管 OCaml 是 OCaml)。

【讨论】:

  • 谢谢你,但你仍然需要每个类型的函数?
  • 我已经扩展了答案,我希望现在会更清楚一点。虽然我提到的一些概念在第一次阅读时可能会令人困惑,但一旦你深入阅读你的教科书,它们应该会变得更加清晰。我或多或少直接回答了这个问题,但我有一种感觉,你对高阶函数更感兴趣,而不是对打印设施本身感兴趣。
【解决方案2】:

如果您比较这两个函数,您可能会发现它们几乎相同,除了 %d%B

现在统一它们的一种方法是将格式字符串作为参数传递。但是使用格式字符串背后的黑盒魔法并不是最简单的。这也不是通用解决方案,因为并非所有值都可以使用简单的格式字符串打印。

因此,为了更通用,您可以传递一个函数,该函数打印出列表的一个元素,并统一关于添加“[”、“;”和“]”以及迭代列表的所有剩余逻辑。

在您的情况下,构造函数来打印一个元素是微不足道的,因为 ocaml 使用柯里化。您只需使用(Printf.printf "%d")(Printf.printf "%B")。在其他情况下,可以传递更复杂的函数来打印更复杂的列表元素。

PS:不提供任何来源,因为这可能是家庭作业。此处的信息应该可以帮助您朝着正确的方向开始。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多