Метод Type, по-видимому, вызывает частные функции несинхронизированным образом

Я делаю свои первые шаги с F#, портируя интерпретатор, который я написал на другом языке. Я сделал следующий код действительно общим, чтобы получить минимальный воспроизводимый пример (поэтому он может выглядеть бессмысленным). У меня есть метод типа this.Process, который вызывает приватную scan функцию перед вызовом приватной cleanupProcess1 функции (которая запускает различные задачи «очистки»). По какой-то причине функции очистки вызываются, пока scan все еще выполняется.

type SomeClass() =
    let mutable charSeq = Seq.empty
    let mutable chars = Array.empty
    let mutable currentIndex = 0

    let cleanupProcess2 () = printfn "called `cleanupProcess2`"

    let cleanupProcess1 () =
        printfn "called `cleanupProcess1`"
        cleanupProcess2 ()

    let next () =
        currentIndex <- currentIndex + 1

        try
            Some(chars[currentIndex - 1])
        with :? System.IndexOutOfRangeException ->
            None

    let scan () =
        printfn "called `scan`"

        let rec advance c =
            seq {
                printf "called `advance`... "

                match c with
                | Some(c) ->
                    printfn $"{c}"
                    yield c
                    yield! advance (next ())
                | None ->
                    printfn "(`advance` complete)"
                    ()
            }

        printfn "(before calling `advance`)"
        charSeq <- advance (next ())

    member this.Process(s: string) =
        printfn "called `this.Process`"
        chars <- s.ToCharArray()
        scan ()            // this contains `advance`...
        cleanupProcess1 () // ...and this executes before the above `scan`/`advance` completes?

        printfn $"{charSeq |> Array.ofSeq |> System.String}"


let someClass = SomeClass()

someClass.Process("foobarbaz")

Выполнение этого кода должно привести к следующему выводу:

called `this.Process`
called `scan`
(before calling `advance`)
called `cleanupProcess1`  # <--- why are these two being called early here?
called `cleanupProcess2`  # <---
called `advance`... f
called `advance`... o
called `advance`... o
called `advance`... b
called `advance`... a
called `advance`... r
called `advance`... b
called `advance`... a
called `advance`... z
called `advance`... (`advance` complete)

                          # <--- instead of being called after `advance` completes here?
                          # <---

foobarbaz

Все компилируется без всяких предупреждений. Последовательность внутри scan/advance в конце концов строится, но не раньше, чем будут вызваны функции очистки. Я ожидал, что они будут вызваны после, так как вызов cleanupProcess1 появляется после вызова scan/advance внутри this.Process. Это похоже на какую-то асинхронную проблему, но я нигде не использую async {}, поэтому я не знаю, нужно ли что-то где-то «ждать»?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
65
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Последовательности F# (seq) являются ленивыми, что означает, что advance возвращается немедленно, а содержимое последовательности вычисляется позже, по запросу. В вашем случае это вызов Array.ofSeq, который вызывает оценку, но это происходит только после запуска вашего кода очистки. Если вы знакомы с C#, это то же самое поведение, что и IEnumerable<T>, и, по сути, seq реализовано через IEnumerable<T> под одеялом. Обратите внимание, что ленивая оценка — это не то же самое, что асинхронная оценка. В вашем коде нет ничего асинхронного.

Чтобы быстро вычислить последовательность, вы можете вместо этого использовать массив:

        let rec advance c =
            [|
                printf "called `advance`... "

                match c with
                | Some(c) ->
                    printfn $"{c}"
                    yield c
                    yield! advance (next ())
                | None ->
                    printfn "(`advance` complete)"
                    ()
            |]

Результирующий вывод:

calling `this.Process`
calling `scan`
(before calling `advance`)
called `advance`... f
called `advance`... o
called `advance`... o
called `advance`... b
called `advance`... a
called `advance`... r
called `advance`... b
called `advance`... a
called `advance`... z
called `advance`... (`advance` complete)
calling `cleanupProcess1`
calling `cleanupProcess2`
foobarbaz

Не знал, что вы уже опубликовали ответ, я удалю свой.

Jim Foye 04.04.2023 20:40

На самом деле seq<T> реализуется не только через IEnumerable<T>; это просто псевдоним для IEnumerable<T>.

Tarmil 05.04.2023 10:33

Другие вопросы по теме