Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call the base F# method from a computation expression in an overridden method?

Tags:

f#

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.

like image 973
Roman Kuzmin Avatar asked Jan 01 '23 08:01

Roman Kuzmin


2 Answers

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.

like image 119
Tomas Petricek Avatar answered May 10 '23 06:05

Tomas Petricek


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.

like image 35
Charles Roddie Avatar answered May 10 '23 05:05

Charles Roddie