Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Though maybe I stand corrected. I revisited that. Here's what I had initially:

(include the following stubs to compile):

  let upload _ = async.Return()
  let takePhoto i = async.Return [|0uy|]
  // i stands for "photo i of N", a required parameter for our workflow
---

  let runSequence n =
    async {
      let rec runSequenceRec (framesSoFar: byte[] list) =
        async {
          if framesSoFar.Length = n then
            return List.rev framesSoFar
          else
            let! bytes = takePhoto framesSoFar.Length
            do! upload bytes
            return! runSequenceRec (bytes::framesSoFar)
        }
      return! runSequenceRec []
    }
Nasty dual-level async calls, list reversal, recursion, if/else, an initializing []. A lot of visual junk that takes away from what the code is actually doing. I rewrote it imperatively to this:

  let runSequence' n =
    async {
      let frames = System.Collections.Generic.List<byte[]>()
      for i = 0 to n do
        let! bytes = takePhoto i
        do! upload bytes
        frames.Add bytes
      return frames
    }
Much cleaner, you can see exactly what's going on. I was fine with that, and that's where I left it until now.

However reading some of the Haskell comments, I thought there might be something better. So I tried replicating Haskell's forM:

  let forNAsync f n =
    let rec runOnce (soFar: _ list) =
      async {
        if soFar.Length = n then
          return List.rev soFar
        else
          let! result = f soFar.Length
          return! runOnce (result::soFar)
      }
    async.ReturnFrom(runOnce [])
Okay, not pretty but it's a library function so we should never have to look at it again. With that in hand though, we can define:

  let runOneShot i =   
    async {
      let! bytes = takePhoto i
      do! upload bytes
      return bytes
    }

  let runSequence'' = forNAsync runOneShot
This is terrific. Shorter and cleaner than the imperative method, preserves immutability throughout, and composes easily.

If you were to try to compose `runOneShot` from the imperative method, you'd end up with this, because of your frames variable

  let runSequence''' n =
    async {
      let frames = System.Collections.Generic.List<byte[]>()
      for i = 0 to n do
        let! bytes = runOneShot i
        frames.Add bytes
      return frames
    }
Or if you wanted to try to rework the `runOneShot` to include the frames variable, it wouldn't be much easier:

  let runOneShot'''' i (frames: System.Collections.Generic.List<byte[]>) = 
    async {
      let! bytes = takePhoto i
      do! upload bytes
      frames.Add bytes
    }

  let runSequence'''' n =
    async {
      let frames = System.Collections.Generic.List<byte[]>()
      for i = 0 to n do
        do! runOneShot'''' i frames
      return frames
    }
So, maybe things that seem imperative do work better functionally. You've just got to create some combinators that let you hook them up.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: