This is indeed subtle. The key points:

- type inference works top to bottom, left to right, and within a recursive group (e.g. class members or let-recs) it first scans all the declarations (and annotations) followed by the definitions

- Intellisense can "see forward", e.g. is the result of processing the whole file, whereas when the compiler is actually compiling code, it can only know what it's seen at this program point based on the rules above. As a result, Intellisense sometimes knows a type even when the compiler gives an error squiggle, as some 'later' code constrained a hitherto unconstrained type and Intellisense saw that

So 1) is addressed by my second bullet, and 2) is addressed by the first.

See also

[link:lorgonblog.wordpress.com]

for more overview. Here's your code again:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Ding = 

    val mutable plek : System.Drawing.Point 

    val mutable ga: Ding -> System.Drawing.Point 

    member i.pos with get() = i.plek and set p = i.plek <- p

    new(p,f) = {plek = p; ga = f} 

    new(x,y) = Ding(System.Drawing.Point(x,y)) 

    new(p) as i = Ding(p, fun d-> i.plek) 

    //new(p) as i = Ding(p, fun d-> d.pos)  // does not typecheck

    new() = Ding(0,0) 
By on 3/16/2011 5:10 PM ()

Thanks! I like subtleties, but not this kind - if code doesn't do what I intend, it ought to be because I didn't say what I intended to say, not because it didn't figure out something it could have figured out..

(Another case where that hit me was when I started a timer in an otherwise unreferenced file, and the compiler decided to skip the file!)

If I understand you correctly, if I move the new(p) line immediately before the new(x,y) line, it still comes after the new(p,f) line it delegates to, BUT it already knows there is another two-argument new(x,y) coming up, whereas it does NOT yet know what those arguments will be.

I still don't understand the d.pos problem, which occurs in the current ordering as well. The constructor new() doesn't come into play, and new(x,y) clearly doesn't match the function argument, so the compiler ought to be able to infer that the function is of the type of 'ga', i.e. that d is a Ding, oughtn't it?

Biep.

P.S. I get complaints from Ajax when trying to preview this text.

By on 3/18/2011 8:06 AM ()

I still don't understand the d.pos problem, which occurs in the current ordering as well. The constructor new() doesn't come into play, and new(x,y) clearly doesn't match the function argument, so the compiler ought to be able to infer that the function is of the type of 'ga', i.e. that d is a Ding, oughtn't it?

It could work this way, but it does not. Overloading is the bugaboo of type inference, and F# (smartly, I think) is rather conservative. F# does not say "oh, you have a lambda for the second argument here, so I only need to consider overloads with a function type for the second argument, ok there is only one such overload, so let's unify, know I know the function type and can keep inferring". Instead, F# does something more like "oh, there are a number of overloads I need to consider, do I have all the type arguments solved already to ensure I select the right overload? No, ok, so let's leave these type arguments unconstrained for the moment" and thus 'd' doesn't have the right type yet when you try to dot into it.

Name resolution is a very messy problem in every language, and even though F# has very few implicit conversions, they interact very badly with type inference and overloading, and so it is not unreasonable to be conservative here. The price of doing more comprehensive unification/inference during overload resolution is more confusing diagnostics when the code is wrong or when the inference system does the moral equivalent of 'guessing wrong'.

At the end of the day, it is very pragmatic for the human to not try to understand the full intracacies of the inference mechanism in the language, and instead just accept that occasionally (and those rare occasions almost always involve overloading) you will get an unexpected type inference error that can be fixed by adding a small type annotation somewhere.

By on 3/18/2011 1:06 PM ()

Thanks again. I am still wondering about the following:

Overloading is the bugaboo of type inference, and F# (smartly, I think) is rather conservative.

What would be a case in which this conservativeness would be a good thing?

And on another track: it seems F# tries hard to be a one-pass compilable language, which of course has its advantages. But a flag to allow two-pass compilation, with much stronger type checking, mutually recursive types in different files, one names it, would be very welcome, wouldn't it?

By on 4/5/2011 7:28 AM ()

At the end of the day, it is very pragmatic for the human to not try to understand the full intracacies of the inference mechanism in the language, and instead just accept that occasionally (and those rare occasions almost always involve overloading) you will get an unexpected type inference error that can be fixed by adding a small type annotation somewhere.

The rest is a pretty good explanation, but I strongly disagree with this statement. I think it is very important for every case to know exactly why F# can't infer types.

- there would be less such questions
- well-described inference algorithm would allow community to make suggestions on its improvement

So I'd like to see it described somewhere.

By on 3/19/2011 2:51 AM ()

I think it is very important for every case to know exactly why F# can't infer types.

- there would be less such questions
- well-described inference algorithm would allow community to make suggestions on its improvement

So I'd like to see it described somewhere.

It is described somewhere, Section 14 of the spec:

[link:research.microsoft.com]

By on 3/19/2011 11:37 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper