Daniel
Daniel

Reputation: 47904

Abstracting a loop

I find myself writing loops (recursive functions) occasionally within assignments. It makes for awkward code like this:

let value =
  let rec loop a =
    if ... then a
    else loop a.B
  loop a

I know I could move the loop outside the let binding, but it's only purpose is to compute the bound value.

So I thought I might abstract the loop into a separate function:

let loop f a =
  let rec aux a =
    match f a with
    | Some b -> aux b
    | None -> a
  aux a

then I could do:

let value = a |> loop (fun a -> if ... then None else Some a.B)

Maybe that's better--at least it looks like more like assignment than a function definition. Here are my questions:

  1. Is a recursive function in a let binding something of a code smell?
  2. Is there a better way to refactor this?
  3. If not, could my loop function be generalized further, or somehow improved?

Upvotes: 2

Views: 209

Answers (2)

Tomas Petricek
Tomas Petricek

Reputation: 243061

I think that your or kvb's solutions are perfectly fine.

I don't know what is the data structure that you're iterating over in the loop. It looks like a data structure though, so it may make sense to implement the IEnuemerable<'T> interface or write a function that turns it into IEnumerable<'T>:

let rec asSeq a = seq {
  yield a 
  yield! asSeq a.B }

Then you could just use Seq.find and give it the condition you need:

// Using explicit conversion function
let value = a |> asSeq |> Seq.find (fun a -> ...)

// If 'a' actually implements 'seq<'T>', it is even nicer:
let value = a |> Seq.find (fun a -> ...)

Upvotes: 0

kvb
kvb

Reputation: 55184

These questions are a bit subjective, but here are my answers:

  1. No
  2. I think what you've got is okay.
  3. Here's how I'd do it:

    let rec loop guard step init =
        if guard init then init
        else loop guard step (step init)
    
    let value = a |> loop (fun a -> ...) (fun a -> a.B)
    

Upvotes: 4

Related Questions