Hi James, thanks for a very interesting question! I always like answering questions like this :-). As I was writing this, I thought it would be an interesting blog post too. Would you mind if I turned this answer into an article for my blog?

Anyway, there is no way to implement the computation builder if you want get the syntax you used in your example. The reason is that the bind operator gets the parameter (random number) as a value, so it cannot re-run it (because it's not a function). I can think of two ways to write this. First of all, you can turn the value (random number) into a sequence of random numbers. Then, the computation builder can pick numbers multiple times. However, the syntax is a bit more complicated:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
type FirstSuccessBuilder() = 
  member x.Bind(vals, f) =
    let first = Seq.find (fun n -> n < 10) vals
    f(first)
  member x.Return(v) = v
  
let first = new FirstSuccessBuilder()

first {
  let rnd = new Random()  
  let! n = 
    seq { 
      while true do 
        let n = rnd.Next(20)
        printfn "yielding %d" n
        yield n }
  printfn "got it %d" n
  return n }
  

You can of course define a randomSequence value, which makes it quite nice:

1
2
3
4
5
6
7
8
 
let randomSequence = 
  seq { let rnd = new Random()  
         while true do yield rnd.Next(20) }
first {
  let! n = randomSequence
  printfn "got it %d" n
  return n }

This is probably quite close to what you were looking for. Anyway, here is another thought - you can re-run the rest of the computation (after let!), so you can write the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
type FirstSuccessBuilder() = 
  member x.Bind(vals, f) =
    Seq.tryPick f vals
  member x.Return(v) = v
  
let first = new FirstSuccessBuilder()

first {
  let! n = randomSequence
  if (n >= 10) then
    printfn "retrying %d" n
    return None 
  else
    printfn "got it %d" n
    return Some(n) }

This will take numbers from the sequence until it finds some for which the computation returns 'Some' and then the overall result will be the returned 'Some' value. The only limitation is that it can re-run only the code starting from the last "let!", but there is a way to solve this too. I think this may be quite close to what transactional memory monad does, but I'm not sure yet.

Hope this will give you some interesting ideas!
Tomas

By on 9/7/2009 3:46 PM ()

Hi James, thanks for a very interesting question! I always like answering questions like this :-). As I was writing this, I thought it would be an interesting blog post too. Would you mind if I turned this answer into an article for my blog?

Of course, feel free.

I got curious when I saw Chris Smith's post on F# for Architects, where he talked about doing retries of network requests. Unfortunately, he just put in a teaser, saying he was going to talk about how to do it in a future post.

I was just trying to figure out the more general case of trying to retry anything, not just net requests. (I don't actually care about random numbers, that was just the simplest example I could think of.) Maybe it would be interesting to do things like create computation expressions for a series of steps a user has to complete on a website. Create an object that tracks the user's position in the expression, retry if they haven't completed the right steps yet, save and restore their position, that sort of thing.

By on 9/8/2009 8:43 AM ()

Sequences can be used as backrtacking computations, so some form of retry is built in. But this case I wouldn't interpret as retrying, but rather as filtering an infinite sequence:

1
2
3
4
5
6
7
let random = System.Random()
let smallRandoms = 
    Seq.unfold( fun _ -> Some (random.Next(1, 100),())) ()
    |> Seq.filter (fun r -> r < 10)

> Seq.take 5 smallRandoms;;
val it : seq<int> = seq [2; 4; 6; 1; ...]
By on 9/6/2009 11:11 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper