Forcing F# type inference on generics and interfaces to stay loose?

I have not analyzed the code enough to figure out why, but adding member internal tree.SyncStep() : unit seems to fix it EDIT See also stackoverflow.com/questions/1134647/why-... stackoverflow.com/questions/1131456/unde... stackoverflow.com/questions/2255602/unkn... It takes experience to get a very deep understanding of the F# type inference algorithm's capabilities and limitations. But this example seems to be in a class of issues people run into when they do very advanced things. For members of a class, the F# inference algorithm does something like Look at all the member explicit signatures to set up an initial type environment for all the members For any members that have fully explicit signatures, fix their types to the explicit signature Start reading the method bodies top to bottom, left to right (you'll encounter some 'forward references' that may involved unsolved type variables when doing this, and that can cause trouble, because...) Solve all the member bodies concurrently (... but we have not done any 'generalization' yet, the part that would 'infer type parameters' rather than 'fix' what in theory could be a function of 'a to be whatever concrete type its first call site used) Generalize (any remaining unsolved type variables become actual inferred type variables of generic methods) That may not be exactly right; I don't know it well enough to describe the algorithm, I just have a sense of it.

You can always go read the language spec What often happens is you get as far as bullet 3 and forcing the inferencer to start trying to concurrently solve/constrain all the method bodies when in fact it's unnecessary because, e.g. Maybe some function has an easy concrete fixed type. Like SyncStep is unit->unit, but F# doesn't know it yet in step 3, since the signature was not explicit, it just says ok SyncStep has type "unit -> 'a" for some yet-unsolved type 'a and then now SyncStep is now unnecessarily complicating all the solving by introducing an unnecessary variable The way I found this was, the first warning (This construct causes code to be less generic than indicated by the type annotations. The type variable 'a has been constrained to be type 'V') was on the last line of the body of UpdateRenditions at the call to docTree.Compare().

Now I know that Compare() should be unit -> unit. So how could I possibly be getting a warning about generic-ness there? Ah, ok, the compiler doesn't know the return type is unit at that point, so it must thing that something is generic that's not.

In fact, I could have added the return type annotation to Compare instead of SyncStep - either one works Anyway, I'm being very long-winded. To sum up if you have a well-type program, it should 'work sometimes the details of the inference algorithm will require some 'extra' annotations... in the worst case you can 'add them all' and then 'subtract away the unnecessary ones by using the compiler warnings and some mental model of the inference algorithm, you can quickly steer towards the missing annotation with experience very often the 'fix' is just to add one full type signature (including return type) to some key method that is 'declared late' but 'called early' (introducing a forward reference among the set of members) Hope that helps!

I have not analyzed the code enough to figure out why, but adding member internal tree.SyncStep() : unit = // ^^^^^^ seems to fix it. EDIT See also stackoverflow.com/questions/1134647/why-... stackoverflow.com/questions/1131456/unde... stackoverflow.com/questions/2255602/unkn... It takes experience to get a very deep understanding of the F# type inference algorithm's capabilities and limitations. But this example seems to be in a class of issues people run into when they do very advanced things.

For members of a class, the F# inference algorithm does something like Look at all the member explicit signatures to set up an initial type environment for all the members For any members that have fully explicit signatures, fix their types to the explicit signature Start reading the method bodies top to bottom, left to right (you'll encounter some 'forward references' that may involved unsolved type variables when doing this, and that can cause trouble, because...) Solve all the member bodies concurrently (... but we have not done any 'generalization' yet, the part that would 'infer type parameters' rather than 'fix' what in theory could be a function of 'a to be whatever concrete type its first call site used) Generalize (any remaining unsolved type variables become actual inferred type variables of generic methods) That may not be exactly right; I don't know it well enough to describe the algorithm, I just have a sense of it. You can always go read the language spec. What often happens is you get as far as bullet 3 and forcing the inferencer to start trying to concurrently solve/constrain all the method bodies when in fact it's unnecessary because, e.g. Maybe some function has an easy concrete fixed type.

Like SyncStep is unit->unit, but F# doesn't know it yet in step 3, since the signature was not explicit, it just says ok SyncStep has type "unit -> 'a" for some yet-unsolved type 'a and then now SyncStep is now unnecessarily complicating all the solving by introducing an unnecessary variable. The way I found this was, the first warning (This construct causes code to be less generic than indicated by the type annotations. The type variable 'a has been constrained to be type 'V') was on the last line of the body of UpdateRenditions at the call to docTree.Compare().

Now I know that Compare() should be unit -> unit. So how could I possibly be getting a warning about generic-ness there? Ah, ok, the compiler doesn't know the return type is unit at that point, so it must thing that something is generic that's not.

In fact, I could have added the return type annotation to Compare instead of SyncStep - either one works. Anyway, I'm being very long-winded.To sum up if you have a well-type program, it should 'work' sometimes the details of the inference algorithm will require some 'extra' annotations... in the worst case you can 'add them all' and then 'subtract away the unnecessary ones' by using the compiler warnings and some mental model of the inference algorithm, you can quickly steer towards the missing annotation with experience very often the 'fix' is just to add one full type signature (including return type) to some key method that is 'declared late' but 'called early' (introducing a forward reference among the set of members) Hope that helps!

OK. I'm hyperventilating here. You are seriously AWESOME, Brian.

I owe you a beer. Actually by this point I probably owe you dinner and a movie. Sheeeeesh.

But I have a question: HOW THE HECK DID YOU FIGURE THAT OUT? Haha. I think I tried forcibly annotating EVERY OTHER FUNCTION in many different ways, but I never thought that a unit -> unit function would benefit from an annotation.

– Dan Fitch Nov 27 '09 at 16:45 1 Yup, that does it, tests pass on the mock providers and everything. Now I just need to steal your brain. – Dan Fitch Nov 27 '09 at 16:47 Ok, I added a lot to the answer to suggest strategies you can use to help yourself out next time :) – Brian Nov 27 '09 at 17:12 Mega major thumbs up.

Thank you so much. Thinking about the type resolution from the compiler's perspective is still fairly new to me. (I come from the scary scripty world...) Now the problem and the solution actually make sense.

THANK YOU! – Dan Fitch Nov 27 '09 at 17:26 This answer just helped me /even more/ with a generic-typing issue. And thanks to your heuristic there, I was able to figure out what was going on all by my lil' self!

I definitely think you should expand or repost this on your blog. It's kind of an addendum to lorgonblog.spaces.live. Com/blog/cns!701679AD17B6D310!1526.

Entry anyway, for how inference is resolved in classes... – Dan Fitch Nov 27 '097 at 1:15.

Comparison varies on two types and takes a provider for the first and a writer provider for the second. /// Then it synchronizes them. The sync code is added later because some of it is dependent on the concrete types.

State = TreeBoth (atree. StateForPath apath, btree. Create(atree, apath, tree.

Let rand = (new Random()).

I cant really gove you an answer,but what I can give you is a way to a solution, that is you have to find the anglde that you relate to or peaks your interest. A good paper is one that people get drawn into because it reaches them ln some way.As for me WW11 to me, I think of the holocaust and the effect it had on the survivors, their families and those who stood by and did nothing until it was too late.

Related Questions