Question

This code comes from a paper called "Lazy v. Yield". Its about a way to decouple producers and consumer of streams of data. I understand the Haskell portion of the code but the O'Caml/F# eludes me. I don't understand this code for the following reasons:

What kind of behavior can I expect from a function that takes as argument an exception and returns unit?

How does the consumer project into a specific exception? (what does that mean?)

What would be an example of a consumer?

module SimpleGenerators

type 'a gen        = unit -> 'a
type producer      = unit gen
type consumer      = exn -> unit  (* consumer will project into specific exception *)
type 'a transducer = 'a gen -> 'a gen 

let yield_handler : (exn -> unit) ref = 
    ref (fun _ -> failwith "yield handler is not set")

let iterate (gen : producer) (consumer : consumer) : unit = 
    let oldh = !yield_handler in
    let rec newh x =
        try 
            yield_handler := oldh
            consumer x
            yield_handler := newh
        with e -> yield_handler := newh; raise e
    in
    try
        yield_handler := newh
        let r = gen () in
        yield_handler := oldh
        r
    with e -> yield_handler := oldh; raise e
Was it helpful?

Solution

I'm not familiar with the paper, so others will probably be more enlightening. Here are some quick answers/guesses in the meantime.

A function of type exn -> unit is basically an exception handler.

Exceptions can contain data. They're quite similar to polymorphic variants that way--i.e., you can add a new exception whenever you want, and it can act as a data constructor.

It looks like the consumer is going to look for a particular exception(s) that give it the data it wants. Others it will just re-raise. So, it's only looking at a projection of the space of possible exceptions (I guess).

OTHER TIPS

I think the OCaml sample is using a few constructs and design patterns that you would not typically use in F#, so it is quite OCaml-specific. As Jeffrey says, OCaml programs often use exceptions for control flow (while in F# they are only used for exceptional situations).

Also, F# has really powerful sequence expressions mechanism that can be used quite nicely to separate producers of data from the consumers of data. I did not read the paper in detail, so maybe they have something more complicated, but a simple example in F# could look like this:

// Generator: Produces infinite sequence of numbers from 'start'
// and prints the numbers as they are being generated (to show I/O behaviour)
let rec numbers start = seq { 
  printfn "generating: %d" start
  yield start
  yield! numbers (start + 1) }

A simple consumer can be implemented using for loop, but if we want to consume the stream, we need to say how many elements to consume using Seq.take:

// Consumer: takes a sequence of numbers generated by the 
// producer and consumes first 100 elements 
let consumer nums = 
  for n in nums |> Seq.take 100 do
    printfn "consuming: %d" n

When you run consumer (numbers 0) the code starts printing:

generating: 0
consuming: 0
generating: 1
consuming: 1
generating: 2
consuming: 2

So you can see that the effects of producers and consumers are interleaved. I think this is quite simple & powerful mechanism, but maybe I'm missing the point of the paper and they have something even more interesting. If so, please let me know! Although I think the idiomatic F# solution will probably look quite similar to the above.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top