F#如何跳出while循环?

29

有没有类似于 C/C# 的方法呢?

比如(C# 风格)

for (int i = 0; i < 100; i++)
{
   if (i == 66)
       break;
} 
7个回答

42

简短回答是不需要。你通常可以使用一些高阶函数来表达相同的功能。有许多函数可供使用,对应着不同的模式(因此如果您描述清楚您需要什么,有人可能会给您更好的答案)。

例如,tryFind函数从序列中返回第一个使给定谓词返回true的值,这样您就可以编写类似以下的代码:

seq { 0 .. 100 } |> Seq.tryFind (fun i ->
  printfn "%d" i
  i=66)

实际上,如果您正在表达一些高级逻辑并且有相应的函数,则这是最好的方法。如果确实需要表达类似于break的内容,则可以使用递归函数:

let rec loop n = 
  if n < 66 then 
    printfn "%d" n
    loop (n + 1)

loop 0      

更加神秘的选项(效率不如其他方法,但适用于DSL)是您可以定义一个计算表达式,让您编写 breakcontinue。这里是一个例子,但正如我所说,这并不高效。


13

这真的很丑,但在我的情况下它起作用了。

let mutable Break = false
while not Break do
    //doStuff

    if breakCondition then
        Break <- true
done

这对于 do-while 循环很有用,因为它保证了循环至少执行一次。

我希望有一个更优雅的解决方案。我不喜欢递归的方法,因为我担心堆栈溢出。 :-(


1
我的理解是,通过使用“rec”将F#函数声明为递归函数,它将有效地使用尾递归来避免递归调用的堆栈分配,从而避免堆栈溢出错误。 - jpierson

4
你需要将它改为while循环。
let (i, ans) = (ref 0, ref -1)
while(!i < 100 and !ans < 0) do
 if !i = 66 then
   ans := !i
ans

(当i到达66时,代码出现错误——但是语法确实有很大不同,引入了另一个变量等等。)


6
既然它不增加 i,所以这需要很长的时间。 :-) - Edward Brey
这个 := 运算符是什么? - knocte

3
seq { 
    for i = 0 to 99 do
        if i = 66 then yield ()
}
|> Seq.tryItem 0
|> ignore

2

试试这个:

exception BreakException

try
    for i = 0 to 99 do
      if i = 66 then
        raise BreakException
with BreakException -> ()

我知道有些人不喜欢使用异常。但它确实有其优点。

  • 您无需考虑复杂的递归函数。当然,您可以这样做,但有时会让事情变得更加麻烦,而使用异常则更简单。

  • 此方法允许您在循环体的一半中断执行。 (使用“break”标志方法也很简单,但它只允许在循环体的末尾中断执行。)

  • 您可以轻松地从嵌套循环中退出。


1
你不应该使用异常来控制应用程序的流程。 - Assassin

0
针对这类问题,您可以使用递归函数。
let rec IfEqualsNumber start finish num =
    if start = finish then false
    elif 
      start = num then true
    else
      let start2 = start + 1
      IfEqualsNumber start2 finish num

0

最近我尝试解决了一个类似的情况:

有一个包含10个数据的列表。每个数据都必须通过Restful服务器进行查询,然后获取结果。

let lst = [4;6;1;8]

问题:
  • 如果API调用失败(例如网络问题),则继续进行其他调用是没有意义的,因为我们需要所有10个结果可用。当API调用失败时,整个过程应尽快停止。

天真的方法:使用List.map()

lst |> List.map (fun x -> 
    try
        use sqlComd = ...
        sqlComd.Parameters.Add("@Id", SqlDbType.BigInt).Value <- x
        sqlComd.ExecuteScala() |> Some
    with
        | :? System.Data.SqlClient.SqlException as ex -> None
)

但是,正如所说的那样,这并不是最优解。当发生失败的API时,剩余的项目仍然会被处理。它们做了一些在最后被忽略的事情。

hacky方法:使用List.tryFindIndex()

map()不同,我们必须在lambda函数中将结果存储在某个地方。一个合理的选择是使用mutable列表。因此,当tryFindIndex()返回None时,我们知道一切都很好,并且可以开始利用mutable列表。

val myList: List<string>
let res = lst |> List.tryFindIndex (fun x ->
    try
        use sqlComd = ...
        sqlComd.Parameters.Add("@Id", SqlDbType.BigInt).Value <- x
        myList.Add(sqlComd.ExecuteScala())
        false
    with
        |:? System.Data.SqlClient.SqlException as ex -> true
)

match res with
| Some _ -> printfn "Something went wrong"
| None -> printfn "Here is the 10 results..."

惯用的方法:使用递归

这种方法并不是很惯用,因为它使用异常来停止操作。

exception MyException of string
let makeCall lstLocal =
    match lstLocal with
    | [] -> []
    | head::tail ->
        try
            use sqlComd = ...
            sqlComd.Parameters.Add("@Id", SqlDbType.BigInt).Value <- x
            let temp = sqlComd.ExecuteScala()
            temp :: makeCall (tail)
        with
            |:? System.Data.SqlClient.SqlException as ex -> raise MyException ex.Message

try
    let res = makeCall lst
    printfn "Here is the 10 results..."
with
    | :? MyException -> printfn "Something went wrong"

旧式的命令式方法:while... do

这仍然涉及到可变列表。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接