Jules May
Jules May

Reputation: 805

Compile errors caused by: Warning: This construct causes code to be less generic than indicated by the type annotations.

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

Answers (1)

Fyodor Soikin
Fyodor Soikin

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

Related Questions