OCaml中不带where子句的长函数

OCaml中不带where子句的长函数,ocaml,idioms,Ocaml,Idioms,用OCaml编写以下代码的惯用方法是什么,可读性更好 let big_function arg = let big_helper_fn acc = function | p -> ... ... ... ... foo(arg) ... ... | _ -> ... in let small_helper_1 a b = ... ... in

用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_函数
    接受3个参数,
    big_helper_fn
    使用所有参数,累加器也是由3个元素组成的元组,那么这将变得很麻烦
  • 助手函数在比需要更大的范围内变得不必要的可见。当你简单地浏览模块时,它们可能会分散你的注意力,因为重要的
    big_函数
    具有相同的缩进深度
如果OCaml有一个第一类
where
子句,这就不会是问题。不过,我确实为此找到了一个和另一个

请提供一本书/风格指南/官方文件/大型项目名称的参考资料,该项目遵循您在回答中建议的方法


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

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

我从未这样做过,但您可以使用
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

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

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

  • 它更容易阅读
  • 它减少了范围内每个点的变量数量,降低了引用错误变量的风险(可能发生在折叠/累加器上)
  • 它使测试内部功能成为可能
正如你所说,也有一些缺点:

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

我问这个问题的原因是因为我写的代码看起来有点难读,因为
big\u函数的实际定义与原始
let-in
语句有很大的区别。您的第二个解决方案看起来不错,但它在语法上不如
where
那么轻量级。(%%>)操作符做什么?@user3240588,它从左到右(而不是通常的从右到左)组合函数。我不太理解这个问题。您的代码在OCaml中已经有效,并且相当地道。“where”子句只会颠倒声明的顺序(这在OCaml中是高度非惯用的)。@Drup我不知道这在OCaml中被认为是惯用的(当我问这个问题时),因为我觉得它不太可读。我将在问题中增加可读性部分。恐怕你将学会接受这一点。你要么把声明拿出来,要么把它放在里面,有各种各样的缺点。请注意,
where
子句也不是完美的:定义是在使用之后,这对于许多人来说是非常烦人的;我没想到。我不确定第二个优势是什么;如果您认为通过父作用域隐式传递参数是正确的,那么这并不重要。OTOH,如果没有直接使用外部函数参数,那么我同意最好将较小的参数移出。