Allow aliases in interface implementation
Consider this F# code:
type IFoo =
abstract Bar1: int -> int
abstract Bar2: int -> int -> int
type Foo() as this =
interface IFoo with
member x.Bar1 a = this.Bar2 a 0
member x.Bar2 a b = a + b
This doesn't compile because this doesn't implement IFoo implicitly, still, it is very common to have to reference this as a specific interface repeatedly.
The proposal is to add the as {alias} optional syntax to the interface implementation in a similar manner it can be added to the type defintion:
type IFoo =
abstract Bar1: int -> int
abstract Bar2: int -> int -> int
type Foo() as this =
interface IFoo as ifoo with
member x.Bar1 a = ifoo.Bar2 a 0
member x.Bar2 a b = a + b
Pros
Making it less cumbersome to deal with interface implementation while retaining the explicit type conversions semantics.
Cons
More OO
You do this:
type IFoo =
abstract Bar1: int -> int
abstract Bar2: int -> int -> int
type Foo() =
interface IFoo with
member x.Bar1 a = (x :> IFoo).Bar2 a 0
member x.Bar2 a b = a + b
which is statically typed and seems safe enough,
@dsyme I'm aware of the work around, what I'm seeing though is that you need to do this in many places (assume those interface members are called in many places in the class ) or to bind (x :> IFoo) in the class itself.
type IFoo =
abstract Bar1: int -> int
abstract Bar2: int -> int -> int
type Foo() as this =
let ifoo = this :> IFoo
interface IFoo with
member x.Bar1 a = ifoo.Bar2 a 0
member x.Bar2 a b = a + b
It seems that aesthetically it would be a nice and coherent fit with the type declaration so I thought of putting the suggestion out.
I agree it is a minor enhancement.
Yeah, the idea is sound - it's quite elegant really
@smoothdeveloper I've marked this approved-in-principle. It's a nice idea and fits well with the language design. Not high priority though...
Looking at this, and confirming that changes to pars.fsy are rather simple (since it's my first try):
https://github.com/smoothdeveloper/visualfsharp/commit/d13c79e5f58123b2ac68885b8f8a1536ac036069#diff-f684cd54cc6b2765be570b976a252d3c

I'll try to investigate how that thing is handled for class declarations, not sure the same can apply to interfaces as on class declaration, the binding is implicit, for interface, I need to introduce a private field in the class.
If I'm able to advance the implementation some more, I'll draft an RFC.
type Foo() as this =
let f = ifoo
interface IFoo as ifoo with
member x.Bar1 a = ifoo.Bar2 a 0
member x.Bar2 a b = a + b
Should using ifoo in a let binding be allowed? I assume it should as I consider the self identifier to be similar as the this in terms of scoping.
@smoothdeveloper If this is allowed then I assume it will be subject to the same caveats as as this regarding initialization soundness. For example this throws an exception:
type Foo() as this =
let y = this.mkY()
member _.mkY() = "test"
let foo = Foo() // System.InvalidOperationException: The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized.
so I would expect this to do the same:
type Foo() =
let y = ifoo.mkY()
interface IFoo as ifoo with
member _.mkY() = "test"
@Tarmil, thanks and great point, that is also relevant to add to the RFC, "the behaviour with the top level self identifier in context of initialization soundness is the same".
Should your first sample (calling an intrinsic member from uninitialized this) be forbidden? or at least raise a warning?
I'm not able to see one, although I've remember seeing such warnings in other contexts.
@Tarmil, I'm just wondering, but why is that illegal? Why is this not fully initialized? Or put in other words, from an OO perspective, where is this initialized such that it raises this error? And what is the use of as this if you cannot access it anyway?
I think an argument could be made that this ought to be fully initialized before any of the private let bindings is processed. The error suggests that's not the case.
Forgive me my ignorance here, but I rarely use as this, instead I use the 'normal' self identifier for member declarations,which doesn't exhibit this issue (nor would is be accessible in let bindings).
@abelbraaksma, could you check this in sharplab:
open System
type C() as this =
member this.M() = ()
edit: weird, this code gives the warning, but as soon as I use this in a let binding, it is gone.
and then compare without the as this, I'm surprised how this gets compiled and it seems there is potential for optimization to elide the FSharpRef, or to not add it in the first place if it can be determined it is not going to be used.
I think we are discussing about this code:
https://github.com/dotnet/fsharp/blob/f49548692d89d645115c4b05b6b8d8d7fbde5b0b/src/fsharp/TypeChecker.fs#L13259-L13265
In my mind, it would have all resolved to the this pointer, I don't understand why the FSharpRef is needed but it seems to be doing with base constructor call.
This documentation section explains how this is happening, in the last § https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/classes#self-identifiers
The self identifier that is declared with the
askeyword is not initialized until after theletbindings are executed. Therefore, it cannot be used in theletbindings. You can use the self identifier in thedobindings section.
The RFC can mention:
"""
Defining a self identifier on an interface implementation will result in forcing or reusing the class level self identifier, and insert the equivalent of (selfIdentifier.Value :> InterfaceType) in the typed AST.
"""
@cartermp, it seems the documentation is not accurate, it says the self identifier cannot be used in let bindings, while such code actually compiles fine:
type MyClass2(dataIn) as self =
let data = dataIn
let a = sprintf "%d %s" data (string self)
let b = sprintf "%d %s" dataIn (string self)
do
self.PrintMessage()
new (a: string) = MyClass2(0L)
member this.PrintMessage() =
printfn "Creating MyClass2 with Data %d" data
printfn "self: %s" a
printfn "self: %s" b
let c1 = MyClass2(1L)
let c2 = MyClass2("")
Should the compiler emit a warning about usage of self identifier outside of do block or has this been relaxed on purpose in a past release of the language?
quoting an answer from @dsyme in fsharp.org compiler channel
Using "self" on the whole class is expensive and should almost always be avoided.
The reason for the extra checks is that "self" can be accessed everywhere, including in the call to the base class constructor, and in strict "let" bindings. The ref cell is a bit of a hack to make this safer - it is what it is and we won't change it now, it is normal just to avoid this use of self
It seems using the reference in a strict let binding is fine, but passing it to base constructor is failing at runtime (adjusting code snippet @dsyme gave) with same error as @Tarmil sample:
type B(c: obj) =
member x.P = 2
and C() as this =
inherit B(this) // exception here
let x = this.P
member __.P = x + 1
let c = C()
printfn "%A" c.P
I'd like a compiler warning or an error for this if there is no counter exemple where passing this would not cause runtime error.
Right now, it discourages usage of let bound functions that would close over that self identifier, and kind of pushes one to write the whole implementation of a class with mutually callable members.