F# break from while loop

后端 未结 7 2098
甜味超标
甜味超标 2021-02-18 14:30

There is any way to do it like C/C#?

For example (C# style)

for (int i = 0; i < 100; i++)
{
   if (I == 66)
       break;
} 
相关标签:
7条回答
  • 2021-02-18 15:12

    This is really ugly, but in my case it worked.

    let mutable Break = false
    while not Break do
        //doStuff
    
        if breakCondition then
            Break <- true
    done
    

    This is useful for do-while loops, because it guarantees that the loop is executed at least once.

    I hope there's a more elegant solution. I don't like the recursive one, because I'm afraid of stack overflows. :-(

    0 讨论(0)
  • 2021-02-18 15:15

    Try this:

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

    I know that some folks don't like to use exceptions. But it has merits.

    • You don't have to think about complicated recursive function. Of cause you can do that, but sometimes it is unnecessarily bothersome and using exception is simpler.

    • This method allows you to break at halfway of the loop body. (Break "flag" method is simple too but it only allows to break at the end of the loop body.)

    • You can easily escape from nested loop.

    0 讨论(0)
  • 2021-02-18 15:15

    Recently I tried to solve a similar situation:

    A list of, say, 10 pieces of data. Each of them must be queried against a Restful server, then get a result for each.

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

    The problem:

    • If there is a failed API call (e.g. network issue), there is no point making further calls as we need all the 10 results available. The entire process should stop ASAP when an API call fails.

    The naive approach: use 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
    )
    

    But as said, it's not optimal. When a failed API occurs, the remaining items keep being processed. They do something that is ignored at the end anyway.

    The hacky approach: use List.tryFindIndex()

    Unlike map(), we must store the results somewhere in the lamda function. A reasonable choice is to use mutable list. So when tryFindIndex() returns None, we know that everything was ok and can start making use of the mutable list.

    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..."
    

    The idiomatic approach: use recursion

    Not very idiomatic as it uses Exception to stop the operation.

    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"
    

    The old-fashion imperative approach: while... do

    This still involves mutable list.

    0 讨论(0)
  • 2021-02-18 15:18

    The short answer is no. You would generally use some higher-order function to express the same functionality. There is a number of functions that let you do this, corresponding to different patterns (so if you describe what exactly you need, someone might give you a better answer).

    For example, tryFind function returns the first value from a sequence for which a given predicate returns true, which lets you write something like this:

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

    In practice, this is the best way to go if you are expressing some high-level logic and there is a corresponding function. If you really need to express something like break, you can use a recursive function:

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

    A more exotic option (that is not as efficient, but may be nice for DSLs) is that you can define a computation expression that lets you write break and continue. Here is an example, but as I said, this is not as efficient.

    0 讨论(0)
  • 2021-02-18 15:18
    seq { 
        for i = 0 to 99 do
            if i = 66 then yield ()
    }
    |> Seq.tryItem 0
    |> ignore
    
    0 讨论(0)
  • 2021-02-18 15:28

    You have to change it to a while loop.

    let (i, ans) = (ref 0, ref -1)
    while(!i < 100 and !ans < 0) do
     if !i = 66 then
       ans := !i
    ans
    

    (This breaks when i gets to 66--but yes the syntax is quite different, another variable is introduced, etc.)

    0 讨论(0)
提交回复
热议问题