在 OCaml 中编写漂亮的多级嵌套 if-then-else 代码?
Write pretty multilevel nested if-then-else code in OCaml?
在 OCaml 中,如果我必须使用许多 if-then-else 编写一个函数,下面是我愚蠢而丑陋的解决方案。
let foo () =
let a1 = ... in
if (a1) then
result1
else
let a2 = ... in
if (a2) then
result2
else
let a3 = ... in
if (a3) then
result3
else
let a4 = ... in
if (a4) then
result4
else
result5.
如何美化上面的代码?我喜欢 C/C++ & Java 风格,它使用 "return" 来保存下一个 if 语句的缩进。
我可以用 OCaml 做同样的事情吗?
int foo () = {
bool a1 = ...;
if (a1)
return result1;
bool a2 = ...;
if (a2)
return result2;
bool a3 = ...;
if (a3)
return result3;
bool a4 = ...;
if (a4)
return result4;
return result5;
}
OCaml 中没有 return
语句,但您可以借助异常来模拟一个语句:
exception Ret of t
let my_ret x = raise (Ret x)
let foo () =
try
let a1 = ... in
if a1 then my_ret result1;
let a2 = ... in
if a2 then my_ret result2;
...
with Ret x -> x
另一个有用的解决方案是使用惰性求值:
let foo () =
let a1 = lazy ...
and a2 = lazy ...
and a3 = lazy ...
in
match a1, a2, a3 with
| lazy true, _, _ -> result1
| _, lazy true, _ -> result2
| _, _, lazy true -> result3
| _, _, _ -> result4
这是使用 lazy 的示例之一,可能还有更简洁的方式来表达您的计算。
核心库提供了一个 with_return
函数,它允许您从函数中执行非局部存在:
open Core_kernel.Std
let foo () = with_return (fun goto ->
if a1 then goto.return 1;
if a2 then goto.return 2;
if a3 then goto.return 3;
if a4 then goto.return 4;
if a5 then goto.return 5;
return 6)
但通常最好使用模式匹配或重新考虑您的代码。例如,如果您有一个谓词列表,并且根据哪个谓词为真您想要 return 一个值,这意味着您可以将其编码为某个映射结构中的搜索:
let foo () = [
clause1, expr1;
clause2, expr2;
clause3, expr3;
] |> List.Assoc.find true
|> Option.value ~default:expr4
当然在这种情况下你没有短路评估。您可以通过惰性求值或 thunk 来解决这个问题。但是除非你的计算真的很繁重或者产生副作用,否则不值得。
if
句法结构在 C 和 OCaml 中的工作方式确实不同。在C中,if
语法形式是语句,在OCaml中它们是表达式。在 C 中最接近 OCaml if
的是 ?:
三元运算符。如果您尝试使用此运算符而不是 if
重写您的 C 代码,您将面临同样的挑战。但是,这并不意味着这是不可能的,因为其他答案会为您提供解决方案。
最简单的方法,同时适用于两种语言,是将您的函数体分割成几个子函数 (*),并使用延续:
let rec foo () =
let a1 = … (* computation *) in
if a1
then result1
else foo2 ()
and foo2 () =
let a2 = … in
if a2
then result1
else foo3 ()
and foo3 () = … (* etc *)
写对象方法的时候可能还是有点麻烦,但是在方法作用域内总是可以使用内部函数重新获得"indentation balance"
另请注意,rec
关键字的唯一目的是允许每个延续在源代码布局中跟随其调用者,此处没有真正的递归。
(*): @gsg 在评论中也提到了
与 if
表达式不同,match
子句扩展到函数末尾,即使它们包含多个语句,也不需要括号。所以你可以这样做:
let foo () =
match ... with
| true -> result1
| false ->
match ... with
| true -> result2
| false ->
match ... with
| true -> result3
| false ->
match ... with
| true -> result4
| false -> result5
你没有在你的例子中说明 result1
来自哪里,所以我不能确定,但你可能会发现最好让 ...
return带有结果而不是布尔值的选项,例如
let foo () =
match ... with
| Some result1 -> result1
| None ->
...
在 OCaml 中,如果我必须使用许多 if-then-else 编写一个函数,下面是我愚蠢而丑陋的解决方案。
let foo () =
let a1 = ... in
if (a1) then
result1
else
let a2 = ... in
if (a2) then
result2
else
let a3 = ... in
if (a3) then
result3
else
let a4 = ... in
if (a4) then
result4
else
result5.
如何美化上面的代码?我喜欢 C/C++ & Java 风格,它使用 "return" 来保存下一个 if 语句的缩进。 我可以用 OCaml 做同样的事情吗?
int foo () = {
bool a1 = ...;
if (a1)
return result1;
bool a2 = ...;
if (a2)
return result2;
bool a3 = ...;
if (a3)
return result3;
bool a4 = ...;
if (a4)
return result4;
return result5;
}
OCaml 中没有 return
语句,但您可以借助异常来模拟一个语句:
exception Ret of t
let my_ret x = raise (Ret x)
let foo () =
try
let a1 = ... in
if a1 then my_ret result1;
let a2 = ... in
if a2 then my_ret result2;
...
with Ret x -> x
另一个有用的解决方案是使用惰性求值:
let foo () =
let a1 = lazy ...
and a2 = lazy ...
and a3 = lazy ...
in
match a1, a2, a3 with
| lazy true, _, _ -> result1
| _, lazy true, _ -> result2
| _, _, lazy true -> result3
| _, _, _ -> result4
这是使用 lazy 的示例之一,可能还有更简洁的方式来表达您的计算。
核心库提供了一个 with_return
函数,它允许您从函数中执行非局部存在:
open Core_kernel.Std
let foo () = with_return (fun goto ->
if a1 then goto.return 1;
if a2 then goto.return 2;
if a3 then goto.return 3;
if a4 then goto.return 4;
if a5 then goto.return 5;
return 6)
但通常最好使用模式匹配或重新考虑您的代码。例如,如果您有一个谓词列表,并且根据哪个谓词为真您想要 return 一个值,这意味着您可以将其编码为某个映射结构中的搜索:
let foo () = [
clause1, expr1;
clause2, expr2;
clause3, expr3;
] |> List.Assoc.find true
|> Option.value ~default:expr4
当然在这种情况下你没有短路评估。您可以通过惰性求值或 thunk 来解决这个问题。但是除非你的计算真的很繁重或者产生副作用,否则不值得。
if
句法结构在 C 和 OCaml 中的工作方式确实不同。在C中,if
语法形式是语句,在OCaml中它们是表达式。在 C 中最接近 OCaml if
的是 ?:
三元运算符。如果您尝试使用此运算符而不是 if
重写您的 C 代码,您将面临同样的挑战。但是,这并不意味着这是不可能的,因为其他答案会为您提供解决方案。
最简单的方法,同时适用于两种语言,是将您的函数体分割成几个子函数 (*),并使用延续:
let rec foo () =
let a1 = … (* computation *) in
if a1
then result1
else foo2 ()
and foo2 () =
let a2 = … in
if a2
then result1
else foo3 ()
and foo3 () = … (* etc *)
写对象方法的时候可能还是有点麻烦,但是在方法作用域内总是可以使用内部函数重新获得"indentation balance"
另请注意,rec
关键字的唯一目的是允许每个延续在源代码布局中跟随其调用者,此处没有真正的递归。
(*): @gsg 在评论中也提到了
与 if
表达式不同,match
子句扩展到函数末尾,即使它们包含多个语句,也不需要括号。所以你可以这样做:
let foo () =
match ... with
| true -> result1
| false ->
match ... with
| true -> result2
| false ->
match ... with
| true -> result3
| false ->
match ... with
| true -> result4
| false -> result5
你没有在你的例子中说明 result1
来自哪里,所以我不能确定,但你可能会发现最好让 ...
return带有结果而不是布尔值的选项,例如
let foo () =
match ... with
| Some result1 -> result1
| None ->
...