How to call the base F# method from a computation expression in an overridden method?
Here is some contrived example of the surprising problem. Do not ask why I need
this particular scenario, the actual use case makes more sense but it is
difficult to represent. In particular, async
is irrelevant, in the actual
case I use a different computation expression and do not even need the bang
(!
) for calling the base method. But the symptoms are the same.
// Base class with a virtual method
type T1 () =
abstract Test : unit -> Async<unit>
default _.Test () =
async {
printfn "In test"
}
// Derived class with an overridden method calling the base in a computation expression
type T2 () =
inherit T1 ()
override _.Test () =
async {
do! base.Test ()
}
let t2 = T2 ()
t2.Test () |> Async.RunSynchronously
The above code cannot compile:
error FS0405: A protected member is called or 'base' is being used. This is
only allowed in the direct implementation of members since they could
escape their object scope.
What does this cryptic message mean? I am calling a public member from the direct implementation of it, like the compiler "wants".
The main question then, how do I call the base method in such cases? What is the idiomatic F# way?
The workarounds are possible, for example:
type T1 () =
abstract Test : unit -> Async<unit>
default m.Test () =
m.DoTest ()
member _.DoTest () =
async {
printfn "In test"
}
type T2 () =
inherit T1 ()
override m.Test () =
async {
do! m.DoTest ()
}
let t2 = T2 ()
t2.Test () |> Async.RunSynchronously
I am not really looking for workarounds but it would be interesting to see others.
The problem here is that code inside async
is compiled into a separate class, which then lives outside of the current class and so it cannot call the base methods in a typical way.
In this case, you can call the method before the async
block. This works, because this is just a part of the normal method body. The method returns async
which is not actually executed until you call it with do!
type T2 () =
inherit T1 ()
override __.Test () =
let baseTest = base.Test ()
async {
do! baseTest
}
This is a nice but limited trick. It will not work when you need to first compute some parameters and then pass them to Test
(because you cannot make baseTest
a function - it has to be a value). The only option in that case is to define a helper method that calls the base method:
type T2 () =
inherit T1 ()
member private x.BaseTest() =
base.Test ()
override x.Test () =
async {
do! x.BaseTest()
}
I think this is a bit nicer than your workaround, because it does not require any virtual methods in the base class. base.Test
call will work from a private method of the derived class.
The workaround is to use a self-identifier:
type T2 () as self =
inherit T1 ()
override _.Test () =
async {
do! self.Test ()
}
I think the reason that base
is not allowed here is that in creating the T1
, it would need to know about the T1
. So the T1
becomes recursively defined, and F# doesn't favour recursive definitions by default. as self
makes T2
recursively defined and allows this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With