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
将内部函数提升到外部可能是不可取的,原因有二:
- 可能需要显式传递多个参数而不是直接访问(如上图
foo(arg)
所示)。如果有更多参数,这将变得很麻烦,比如 big_function
接受 3 个参数,big_helper_fn
使用所有参数并且累加器也是 3 个元素的元组。
- 辅助函数在比所需范围更大的范围内不必要地可见。由于与重要
big_function
. 相同的缩进深度,当人们只是浏览模块时,它们可能会分散注意力
如果 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 中)
- 它可以测试内部函数
也有一些缺点,就像你说的:
- 会有更多的顶层函数,因此可能会出现命名冲突(您可以使用模块来帮助解决)
- 您需要显式传递更多变量,而不是通过闭包。我会说这是一个优势,因为这使得耦合更加明显。这是传递较少数据的机会(例如,一个字段而不是整个记录)。
用 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 有第一个 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
:
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 中)
- 它可以测试内部函数
也有一些缺点,就像你说的:
- 会有更多的顶层函数,因此可能会出现命名冲突(您可以使用模块来帮助解决)
- 您需要显式传递更多变量,而不是通过闭包。我会说这是一个优势,因为这使得耦合更加明显。这是传递较少数据的机会(例如,一个字段而不是整个记录)。