Reputation: 11647
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?
Upvotes: 17
Views: 460
Reputation: 11647
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!
Upvotes: 3
Reputation: 243051
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
.
Upvotes: 10