web-dev-qa-db-ja.com

F#はwhileループから抜け出します

C/C#のようにそれを行う方法はありますか?

例(C#スタイル)

for( int i=0; i<100; i++)
{
   if(i==66)
    break;
} 
20
cheziHoyzer

簡単な答えはノーです。通常、同じ機能を表現するには、高階関数を使用します。さまざまなパターンに対応して、これを実行できる関数がいくつかあります(したがって、必要なものを正確に説明すると、誰かがより良い答えを与える可能性があります)。

たとえば、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には適している可能性があります)は、breakおよびcontinueを記述できる計算式を定義できることです。 ここに例があります 、しかし私が言ったように、これはそれほど効率的ではありません。

29
Tomas Petricek

これは本当に醜いですが、私の場合はうまくいきました。

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

    if breakCondition then
        Break <- true
done

これは、ループが少なくとも1回実行されることを保証するため、do-whileループに役立ちます。

もっとエレガントな解決策があるといいのですが。スタックオーバーフローが怖いので、再帰的なものは好きではありません。 :-(

6
itmuckel

Whileループに変更する必要があります。

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

(これは、66に達すると壊れますが、構文がまったく異なり、別の変数が導入されるなどです。)

4
user2647060

これを試して:

exception BreakException

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

一部の人々は例外を使用することを好まないことを私は知っています。しかし、それにはメリットがあります。

  • 複雑な再帰関数について考える必要はありません。当然のことながらそれは可能ですが、場合によっては不必要に面倒であり、例外を使用する方が簡単です。

  • この方法では、ループ本体の途中でブレークできます。 (ブレーク「フラグ」メソッドも単純ですが、ループ本体の最後でのみブレークできます。)

  • ネストされたループから簡単に脱出できます。

1
user3737161

最近、私は同様の状況を解決しようとしました:

たとえば、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が発生すると、残りのアイテムは処理され続けます。彼らはとにかく最後に無視される何かをします。

ハッキーなアプローチ:List.tryFindIndex()を使用する

map()とは異なり、結果をlamda関数のどこかに格納する必要があります。合理的な選択は、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_

これにはまだmutableリストが含まれます。

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

この種の問題には、再帰関数を使用できます。

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
user2074102