OCaml中的长函数没有where子句

时间:2017-07-18 21:23:46

标签: ocaml idioms idiomatic

在OCaml中编写以下代码的惯用方法是什么,具有更好的可读性?

let big_function arg =
    let big_helper_fn acc = function
      | p -> ...
      ...
      ...
      ...     foo(arg)
      ...
      ...
      | _ -> ...
    in
    let small_helper_1 a b = 
    ...
    ...
    in
    let small_helper_2 a b =
    ...
    ...
    in

    fold big_function default_acc
    %> small_helper_1 aa1
    %> small_helper_2 aa2

由于两个原因,将内部功能提升到外面可能是不受欢迎的:

  • 可能需要显式传递多个参数而不是直接访问(上面显示为foo(arg))。如果有更多参数,这将变得很麻烦,例如big_function接受3个参数,big_helper_fn使用所有参数,累加器也是3个元素的元组。
  • 辅助功能在比所需范围更大的范围内变得不必要。当一个人只是简单地浏览模块时,他们可能会分心,因为与重要的big_function具有相同的缩进深度。

如果OCaml有第一类where条款,这不会有问题。我确实为此找到了PPX和另一个Github repo

请按照答案中建议的方法提供大型项目的书籍/风格指南/官方文档/名称的参考。

编辑:我在这个代码示例中遇到的麻烦是可读性受损,因为big_function的实际定义与顶部的原始let big_function ...语句相隔很多。因此,我正在寻找一种更具可读性的惯用语。

2 个答案:

答案 0 :(得分:1)

您的代码看起来已经非常OCamlish。许多大型项目都是以这样的方式编写的:请参阅OCaml编译器实现本身。我喜欢Haskell中的where,但我个人反对非纯语言中的任意where因为它可能使副作用的排序非常混乱,这可能导致很难修复的错误。将where的定义仅限于非扩展表达式是可以的,但我不确定您提到的PPX是否执行此类检查。

我从来没有这样做过,但是你可以把'#34; main"首先使用let rec

执行任务
let big_function arg =
  let rec go () =
    fold big_helper_fn default_acc
    %> small_helper_1 aa1
    %> small_helper_2 aa2
  and big_helper_fn acc = function
    ..
  and small_helper_1 a b = 
    ..
  and small_helper_2 a b =
    ..
  in
  go ()

或者,您可以使用本地模块:

module BigFunctionHelpers(A : sig val arg : t end) = struct
  open A

  let big_helper_fn acc = function ... foo(arg) ...

  let small_helper_1 a b = ...

  let small_helper_2 a b = ...
end

let big_function arg = 
  let module H = BigFunctionHelpers(struct let arg = arg end) in
  let open H in
  fold big_helper_fn default_acc
  %> small_helper_1 aa1
  %> small_helper_2 aa2

有时我从父函数中提取包含许多参数的本地定义时会这样做。

答案 1 :(得分:1)

在不知道辅助函数中发生了什么的情况下很难说,但一般来说,您可以将它们作为顶级函数提取出来。这有几个好处:

  • 更容易阅读
  • 它减少了每个点范围内变量的数量,降低了引用错误变量的风险(可能会出现折叠/累加器)。
  • 可以测试内部功能

还有一些缺点,如你所说:

  • 会有更多顶级函数,因此可能存在命名冲突(您可以使用模块来帮助它)
  • 您需要显式传递更多变量而不是闭包。我认为这是一个优势,因为这使得耦合更加明显。这是一个传递较少数据的机会(比如,一个字段而不是整个记录)。