OCaml中没有where子句的长函数

问题描述:

在OCaml中编写以下代码的惯用方式具有更好的可读性?OCaml中没有where子句的长函数

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)示出)。如果有更多的参数,这会变得很麻烦,比如​​需要3个参数,big_helper_fn使用所有参数,并且累加器也是3元素的元组。
  • 辅助函数在比所需范围更大的范围内变得不必要地可见。当人们仅仅撇开模块时,他们可能会分心,因为与重要的​​相同的压痕深度。

如果OCaml有第一类where子句,这不会是一个问题。我确实找到了一个PPX和另一个Github repo

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

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

+0

什么的(%>)运算符吗? – user3240588

+1

@ user3240588,它构成功能从左至右(而不是通常的从右到左)。 – theindigamer

+0

我真的不明白这个问题。你的代码已经在OCaml中有效并且相当习惯。 “where”子句只会颠倒声明的顺序(这在OCaml中是非常不惯用的)。 – Drup

你的代码看起来已经很OCamlish了。许多大型项目都是以这种方式编写的:请参阅OCaml编译器实现本身。我喜欢在Haskell中使用where,但是我个人反对使用非纯语言的任意where,因为它可能会使副作用的排序非常混乱,这可能会导致很难修复的错误。将where的定义仅限制在非扩展表达式中是可以的,但我不确定您提到的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 

我这样做有时候在用父函数提取许多参数的本地定义时。

+0

我问这个的原因是因为我写的代码似乎有点难以阅读,因为'big_function'的实际定义与原来的'let in'语句分开了很多。你的第二个解决方案看起来不错,但它又不像“where”那样在语法上重量轻。 – theindigamer

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

  • 更容易阅读
  • 它减少的范围变量的数量在每一点上,减少指的是错误的变量的风险(可以折叠/累加器发生)
  • 这使得它可以测试内部函数

有一些缺点,如你所说:

  • 将有更多*的功能,所以不可能有命名冲突(你可以使用一个模块来帮助与)
  • 你需要传递更多变量明确,而不是通过关闭。我会说这是一个优势,因为这会使耦合更加明显。这是传递较少数据的机会(例如,一个字段而不是整个记录)。
+0

关于测试的观点很有用;我没有想到这一点。虽然我不确定第二个优点,如果你无论如何都通过父范围隐式地传递参数,它并不重要。 OTOH,如果外部函数的参数没有被直接使用,那么我同意最好将较小的函数移出。 – theindigamer