1

A little explanation before I ask the question. I have a sequence on which I want to perform a number of fold operations. But evaluating the sequence is slow and expensive (it's iterating through a database) and (though it's not explicit here) I want to provide progressive output to the user. So I'd really like to do all the folds in one go. Something like:

theSeq |> Seq.CoFold [ line (folder1, initAcc1, action1) line (folder2, initAcc2, action2) ] 

the action being something that's done with the accumulator once the fold is complete. I'd use it something like this:

theSeq |> Seq.CoFold [ line ((fun acc r -> acc+1), 0, (fun d -> printfn "%A" d)) // counter line ((fun acc r -> acc.Add(r), Set.empty, whatever) // uniques ] 

I've worked out that whatever line is, it should be genericked based on the type of theSeq, but it shouldn't depend on the type of initAcc, or the type of the folder function itself. So, I've come up with the following (which doesn't work):

module Seq = type 'T line (f:'State -> 'T -> 'State, a:'State, cb:'State->unit) = let s = ref a member x.incr (j:'T) = s := f !s j member x.cb = cb !s let CoFold (folders: 'T line list) (jj: 'T seq) = for j in jj do folders |> List.iter (fun o -> o.incr j) folders |> List.iter (fun o -> o.cb) 

The problem with this is that it wants to generic line based on both 'T and 'State, and that means that the two lines I've shown above are incompatible with each other, even though neither ever exposes the type of acc.

I've tried several other approaches (e.g. making line into a discriminated union, making line into an abstract base class, and more), and in every case I run into some other manifestation of the original problem. I'm really at a loss to know where to look next.

This doesn't feel like it should be a difficult problem, but I guess I've got a blind spot somewhere. Any hints gratefully received.

Thanks

1 Answer 1

1

Notice that CoFold doesn't really care for line itself, it only cares for incr and cb, and those are only generic in 'T, not 'State. So don't give it line itself, give it only incr and cb.

type 'T line = ('T -> unit) * (unit -> unit) let makeLine incr a cb: line<_> = let s = ref a let incr' t = s := incr !s t let cb' () = cb !s incr', cb' let CoFold (folders: 'T line list) (jj: 'T seq) = for j in jj do folders |> List.iter (fun (incr,_) -> incr j) folders |> List.iter (fun (_,cb) -> cb() ) aSeq |> Seq.CoFold [ makeLine f1 s1 a1 makeLine f2 s2 a2 ] 

If you want to get philosophical, the root of the problem is in complecting incr and cb, bundling them together, when they clearly don't have to be. This is a good rule of thumb in general: keep things small and separate as much as you can.
And if you look closely, you'll see that "classes" (or "objects") are exactly this: a means of complecting multiple data with multiple functions. Try to avoid using classes as much as possible.

Sign up to request clarification or add additional context in comments.

1 Comment

Awesome reply! Thank you very much. You've totally solved my problem. But - more than that - although I already knew everything I needed to solve this, I'd never have got there on my own. Yes, I did have a blind spot. And I can see how this is going to simplify a bunch of other problems I've got. Thanks again.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.