Как вызвать базовый метод F # из выражения вычисления в переопределенном методе? - PullRequest
1 голос
/ 25 января 2020

Как вызвать базовый метод F # из выражения вычисления в переопределенном методе?

Вот несколько надуманных примеров удивительной проблемы. Не спрашивайте, зачем мне нужен именно этот сценарий, фактический вариант использования имеет больше смысла, но его трудно представить. В частности, async не имеет значения, в реальном случае я использую другое выражение для вычисления и даже не требует взрыва (!) для вызова базового метода. Но симптомы те же.

// 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

Приведенный выше код не может скомпилироваться:

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.

Что означает это крипти c сообщение? Я вызываю publi c член из его прямой реализации, как того «хочет» компилятор.

Главный вопрос тогда, как мне вызвать базовый метод в таких случаях? Что такое идиоматический c F # способ?

Возможны обходные пути, например:

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

На самом деле я не ищу обходных путей, но было бы интересно увидеть других.

Ответы [ 2 ]

3 голосов
/ 27 января 2020

Проблема в том, что код внутри async скомпилирован в отдельный класс, который затем живет за пределами текущего класса и поэтому не может вызывать базовые методы обычным способом.

В этом случае вы можете вызвать метод перед блоком async. Это работает, потому что это только часть тела обычного метода. Метод возвращает async, который фактически не выполняется, пока вы не вызовете его с помощью do!

type T2 () =
    inherit T1 ()
    override __.Test () =
        let baseTest = base.Test ()
        async {
            do! baseTest
        }

Это хороший, но ограниченный прием. Он не будет работать, когда вам нужно сначала вычислить некоторые параметры, а затем передать их в Test (потому что вы не можете сделать baseTest функцией - это должно быть значение). Единственный вариант в этом случае - определить вспомогательный метод, который вызывает базовый метод:

type T2 () =
    inherit T1 ()
    member private x.BaseTest() = 
        base.Test ()
    override x.Test () =
        async {
            do! x.BaseTest()
        }

Я думаю, что это немного лучше, чем ваш обходной путь, потому что он не требует никаких виртуальных методов в базовом классе. , base.Test вызов будет работать из закрытого метода производного класса.

1 голос
/ 26 января 2020

Обходной путь должен использовать самоидентификатор:

type T2 () as self =
    inherit T1 ()
    override _.Test () =
        async {
            do! self.Test ()
        }

Я думаю, что причина, по которой base здесь недопустима, заключается в том, что при создании T1 необходимо знать о T1. Таким образом, T1 становится рекурсивно определенным, и F # по умолчанию не поддерживает рекурсивные определения. as self делает T2 рекурсивно определенным и позволяет это.

...