Reputation: 805
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
Upvotes: 1
Views: 139
Reputation: 80714
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.
Upvotes: 1