Как узнать, является ли функция хвостовой рекурсивной в F #

Будьте осторожны в том, чтобы доверять консоли, часто возникает асинхронное поведение, которое может вас опрокинуть.

Вы ожидаете, что console.log(x) будет вести себя следующим образом:

  1. Вы вызываете console.log(x).
  2. x сбрасывается на консоль.
  3. Выполнение продолжается с оператором сразу после вашего вызова console.log(x).

Но это не так, реальность более похожа на это:

  1. Вы вызываете console.log(x).
  2. Браузер захватывает ссылку на x, и ожидает очередного вызова console.log.
  3. Различные другие биты запуска JavaScript (или нет).
  4. Позже console.log вызов из (2) (f14) не обязательно будет соответствовать x, как это было в (2).

В вашем случае , вы делаете это:

console.log(this.attributes);
console.log(this.attributes.widgets);

Итак, у вас есть что-то подобное в (2):

         attributes.widgets
             ^         ^
             |         |
console.log -+         |
console.log -----------+

, а затем что-то происходит в (3), что эффективно делает this.attributes.widgets = [...] (т. е. изменяет ссылку attributes.widget), и поэтому, когда (4) у вас есть это:

         attributes.widgets // the new one from (3)
             ^
             |
console.log -+
console.log -----------> widgets // the original from (1)

Это оставляет вам вид двух разных версий widgets: новый, который получил что-то в (3) и оригинал, который пуст.

Когда вы это сделаете:

console.log(_(this.attributes).clone());
console.log(_(this.attributes.widgets).clone());

вы захватываете копии this.attributes и this.attributes.widgets, которые прикреплены к вызовам console.log, поэтому (3) не будет мешать вашим ссылкам, и вы см. разумные результаты в консоли.

Это ответ на этот вопрос:

Показывает, что виджеты не являются пустыми при регистрации this.attributes, но он отображается как пустой при регистрации this.attributes.widgets. Кто-нибудь знает, что может вызвать это?

Что касается основной проблемы, у вас, вероятно, есть вызов fetch, и вы не учитываете его асинхронное поведение. Возможно, решение связано с событием "add" или "reset".

15
задан Ganesh Sittampalam 10 June 2009 в 12:28
поделиться

1 ответ

Unfortunately there is no trivial way.

It is not too hard to read the source code and use the types and determine whether something is a tail call by inspection (is it 'the last thing', and not in a 'try' block), but people second-guess themselves and make mistakes. There's no simple automated way (other than e.g. inspecting the generated code).

Of course, you can just try your function on a large piece of test data and see if it blows up or not.

The F# compiler will generate .tail IL instructions for all tail calls (unless the compiler flags to turn them off is used - used for when you want to keep stack frames for debugging), with the exception that directly tail-recursive functions will be optimized into loops. (EDIT: I think nowadays the F# compiler also fails to emit .tail in cases where it can prove there are no recursive loops through this call site; this is an optimization given that the .tail opcode is a little slower on many platforms.)

'tailcall' is a reserved keyword, with the idea that a future version of F# may allow you to write e.g.

tailcall func args

and then get a warning/error if it's not a tail call.

Only functions that are not naturally tail-recursive (and thus need an extra accumulator parameter) will 'force' you into the 'inner function' idiom.

Here's a code sample of what you asked:

let rec nTimes n f x =
    if n = 0 then
        x
    else
        nTimes (n-1) f (f x)

let r = nTimes 3 (fun s -> s ^ " is a rose") "A rose"
printfn "%s" r
22
ответ дан 1 December 2019 в 03:05
поделиться
Другие вопросы по тегам:

Похожие вопросы: