Hi Sebastian,

Does this code have a context I'm not aware of? I'd like to help, but the snippet seems incomplete and doesn't type check, so I'm having difficulties following your explanation. Could you maybe try to post a minimal complete example demonstrating the issue?

Best regards,
Stephan

By on 4/4/2008 2:28 PM ()

In reducing my problem to something small, I believe I now understand the problem. Functions appear to be generalized differently when let bound as opposed to when they are members. This all type checks:

1
2
3
4
module foo =
    let f x = x "foo" (* remains as expected, (string -> 'a) -> 'a *)
    let g x = f (fun _ -> "hi")
    let h x = f (fun _ -> 5)

Where as these functions, which I would expect to be equivalent, don't.

1
2
3
4
type foo =
    member this.f x = x "foo" (* deduced as (string -> string) -> string *)
    member this.g x = this.f (fun a -> "hi")
    member this.h x = this.f (fun a -> 5) (* type check fails: int should be string *)

Of course the functions aren't equivalent, they're carrying around extra "this" bindings, but the type of "this" shouldn't come into play in proving the type of this.f, right?

Eek!

By on 4/4/2008 3:03 PM ()

That's indeed odd. You can't even explicitly generalize a member, as the following example shows:

type foo() =
member t.f<'a>(x: 'a) : 'a = x
member t.g = t.f(1) // this restricts 'a to int
member t.h = t.f("a") // error: string used with type int

If you comment out g and h, you'll get a perfectly general identity function f.

By on 4/5/2008 12:33 AM ()

Hi Sebastian,

Yes, "let" bindings are generalized independently, while members are generalzied collectively.

We are making a design change where explicit type annotations can be used to generalize member bindings (and ordinary let-rec bindings) independently, precisely as you have shown (i.e. "generic recursion when calling targets with full syntactic type information prior to type inference"). This has been implemented and we're currently in the process of testing that implementation. If you'd like to have an early private drop of the implementation to play around with please let me know and I'll see what I can do.

Thanks

Don

By on 4/5/2008 4:05 AM ()

I confess I continue to wonder "why"? This will be (has been) very confusing; it feels wrong when a functional language breaks compositionality (in this case, type generalization and class declarations) :-) In the case quoted here, leaving f as a generic function is clearly easy; I'm guessing it's some sort of harier case where it gets bizarre?

Cheers,

- Sebastian

By on 4/5/2008 3:29 PM ()

Members are typechecked using essentially the same rules as "let rec" recursive functions.

I agree that in many cases there is a simple and obvious generalization available. However allowing arbitrary generic recursion is known to make Hindley-Milner type inference undecideable, and quite likely in ways that would be hit in practice. However, theree have been some recent advances in this area, e.g. ML-F with contributions by Remy and Leijen.

The solution you followed (fully annotate the target functions) is a reasonably simple one to adopt in practice. Languages such as Haskell also do a topological sort of the dependencies prior to type inference, however this requires no type-directed syntax resolution in the language, and F# has quite a lot of that (to otherwise good effect).

Kind regards

don

By on 4/5/2008 3:44 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