Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type extensions and members visiblity in F#

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?

like image 889
Sergey Teplyakov Avatar asked Apr 05 '13 08:04

Sergey Teplyakov


1 Answers

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.

like image 80
Tomas Petricek Avatar answered Dec 31 '22 23:12

Tomas Petricek