17

F# has feature called "Type extension" that gives a developer ability to extend existing types. There is two types of extensions: intrinsic extension and optional extension. First one is similar to partial types in C# and second one is something similar to method extension (but more powerful).

To use intrinsic extension we should put two declarations into the same file. In this case compiler will merge two definitions into one final type (i.e. this is two "parts" of one type).

The issue is that those two types has different access rules for different members and values:

// SampleType.fs
// "Main" declaration
type SampleType(a: int) =
    let f1 = 42
    let func() = 42

    [<DefaultValue>]
    val mutable f2: int
    member private x.f3 = 42
    static member private f4 = 42

    member private this.someMethod() =
        // "Main" declaration has access to all values (a, f1 and func())
        // as well as to all members (f2, f3, f4)
        printf "a: %d, f1: %d, f2: %d, f3: %d, f4: %d, func(): %d"
            a f1 this.f2 this.f3 SampleType.f4 (func())

// "Partial" declaration
type SampleType with

    member private this.anotherMethod() =
        // But "partial" declaration has no access to values (a, f1 and func())
        // and following two lines won't compile
        //printf "a: %d" a
        //printf "f1: %d" f1
        //printf "func(): %d" (func())

        // But has access to private members (f2, f3 and f4)
        printf "f2: %d, f3: %d, f4: %d"
            this.f2 this.f3 SampleType.f4

I read F# specification but didn't find any ideas why F# compiler differentiate between value and member declarations.

In 8.6.1.3 section of F# spec said that "The functions and values defined by instance definitions are lexically scoped (and thus implicitly private) to the object being defined.". Partial declaration has all access to all private members (static and instance). My guess is that by "lexical scope" specification authors specifically mean only "main" declaration but this behavior seems weird to me.

The question is: is this behavior intentional and what rationale behind it?

Guy Coder
  • 24,501
  • 8
  • 71
  • 136
Sergey Teplyakov
  • 11,477
  • 34
  • 49

2 Answers2

10

This is a great question! As you pointed out, the specification says that "local values are lexically scoped to the object being defined", but looking at the F# specification, it does not actually define what lexical scoping means in this case.

As your sample shows, the current behavior is that the lexical scope of object definition is just the primary type definition (excluding intrinsic extensions). I'm not too surprised by that, but I see that the other interpretation would make sense too...

I think a good reason for this is that the two kinds of extensions should behave the same (as much as possible) and you should be able to refactor your code from using one to using the other as you need. The two kinds only differ in how they are compiled under the cover. This property would be broken if one kind allowed access to lexical scope while the other did not (because, extension members technically cannot do that).

That said, I think this could be (at least) clarified in the specification. The best way to report this is to send email to fsbugs at microsoft dot com.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • 2
    I got your point about similarity between two types of type extensions. But behavior of *intrinsic* and *optional* extensions are different: optional extensions can access only to public surface of extending type but intrinsic extensions still have an access to private and protected members of extending type. So they had different semantic as well, not only how they compiled under the cover. – Sergey Teplyakov Apr 05 '13 at 13:24
  • @SergeyTeplyakov Hmm, that is a good point. You're right that they differ in this aspect. I suppose the key point then is the definition of "lexical scope" in this context (which is clearly missing in the F# specification). – Tomas Petricek Apr 05 '13 at 14:11
  • Thanks, Tomas. I think you're right about lexical scope. My point is, that partial types in C# is well known and widely used feature for designers support. But with such kind of restriction we can't treat intrinsic extension as full-featured partial type declaration. And this means that we can't get any designers support (for WinForms and WPF) till intrinsic extensions has such restrictions. – Sergey Teplyakov Apr 05 '13 at 14:35
3

I sent this question to fsbugs at microsoft dot com and got following answer from Don Syme:

Hi Sergey,

Yes, the behaviour is intentional. When you use “let” in the class scope the identifier has lexical scope over the type definition. The value may not even be placed in a field – for example if a value is not captured by any methods then it becomes local to the constructor. This analysis is done locally to the class.

I understand that you expect the feature to work like partial classes in C#. However it just doesn’t work that way.

I think term "lexical scope" should be define more clearly in the spec, because otherwise current behavior would be surprising for other developers as well.

Many thanks to Don for his response!

Sergey Teplyakov
  • 11,477
  • 34
  • 49