OCaml 中没有 where 子句的长函数

Long functions in OCaml without where clauses

用 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

将内部函数提升到外部可能是不可取的,原因有二:

如果 OCaml 有第一个 class where 子句,这将不是问题。不过我确实找到了 PPX and another Github repo

请提供一个大型项目的参考文献book/style guide/official documentation/name,该项目遵循您回答中建议的方法。

编辑:我在这个代码示例中遇到的问题是可读性受到损害,因为 big_function 的实际定义与顶部的原始 let big_function ... 语句相隔很远。所以我正在寻找一种更具可读性的惯用替代方法。

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

我从来没有这样做过,但是您可以使用 let rec:

将 "main" 任务放在首位
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

我有时会在从父函数中提取带有许多参数的局部定义时这样做。

如果不知道辅助函数中发生了什么,很难说清楚,但一般来说,您可以将它们提取为顶级函数。这有几个优点:

  • 更容易阅读
  • 它减少了范围内每一点的变量数量,降低了引用错误变量的风险(可能发生在 folds/accumulators 中)
  • 它可以测试内部函数

也有一些缺点,就像你说的:

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