I have the following situation:

I have the following situation:

I have enumerator classes that have a MoveNext and some internal state. The first time MoveNext is called additionally to the usual logic of MoveNext it needs to do some initialization. Currently the code is basically like this:

function TEnumerator.MoveNext: Boolean;
begin
Result := False
if state = init then
begin
DoInit;
state := running;
end;
if state = running then
begin
// usual movenext stuff...
end;
end;

In languages with case/switch fallthrough I could simply write it like this:

case state of
init:
begin
DoInit;
state := running;
end;
running:
begin
// usual movenext stuff
end;
end;

I am seriously tempted to use a label and a goto here but I was wondering if anyone has another idea to avoid checking state twice.

Comments

  1. Do you have more states than init/running? Your code could be simply written as

    function TEnumerator.MoveNext: Boolean;
    begin
    Result := False
    if state = init then
    begin
    DoInit;
    state := running;
    end;
    // usual movenext stuff...
    end;

    but maybe you skipped some detail.

    ReplyDelete
  2. Primož Gabrijelčič I do - there are two (before init - when the enumerator is not valid yet, and one for finished, state running moves to finished when it returns false)

    ReplyDelete
  3. What about an additional Exit(MoveNext) at the end of the case-init label? Honestly, it is hard to suggest something sound when only part of the problem is presented. It depends heavily on the things you have to do in the different states. Even a plain state machine may be suitable.

    ReplyDelete
  4. Uwe Raabe I was thinking about the same, doing a recursive call because that can only happen on the first call.

    Everything I wanted is basically simulate a fall through on state init to state running because after init it will always be in state running. I also wanted to avoid writing another method that does this:

    case state of
    init:
    begin
    DoInit;
    state := running;
    Result := MoveNext;
    if not Result then
    state := finished;
    end;
    running:
    begin
    Result := MoveNext;
    if not Result then
    state := finished;
    end;
    else
    Result := False;
    end;

    ReplyDelete
  5. Either you have simplified the problem you are facing a bit too much, or you are solving problem that does not really exist.

    I don't see what is actually wrong with checking the state twice? Code looks fairly obvious.

    I actually dislike fall through switches, I always assume there is error in code when I see such case ;-)

    ReplyDelete
  6. Dalija Prasnikar It is part of optimizing some code in Spring4D which I found out suffers from some compiler generated implicit variables (which was the reason I actually looked into it) I saw that checking state only once after the first improvement to eliminate the implicit interface variable had some potential to improve things further.

    What I did now is this:

    case state of
    init,
    running:
    begin
    if state = init then
    DoInit;
    // movenextstuff
    end;

    Which just results in a test eax,eax for the if and causes almost zero overhead in a tight loop compared to a goto solution.

    ReplyDelete
  7. How about

    case state of
    finished: exit(False);
    init: begin
    DoInit();
    state := running;
    end;
    end;

    // usual MoveNext stuff


    Maybe with an empty else in there to silence the compiler.

    ReplyDelete
  8. Why do the init in the first call to MoveNext? Why not do it when the enumerator is initialized?

    ReplyDelete
  9. David Heffernan Because streamed execution is deferred until the first call to MoveNext by design.

    ReplyDelete
  10. OK. I guess that is so that the operation can be cancelled without incurring all the initialisation cost. I'm not familiar with streamed execution.

    ReplyDelete
  11. What's wrong with using goto when this is really the most obvious solution ;-) State machines (and this snippet is obviously a state machine) often need complex branching which is not easily forced into the rigid structured programming paradigm.

    ReplyDelete
  12. As I understand it, the speed of code execution.
    What about multithreading for the iterator?
    (TPalall, TTask, IFuture)

    ReplyDelete
  13. As I understand it the problem is that you're using an interface as your iterator. Is there perhaps an option to use a (intermediary?) record instead?

    The fall-though you can easily emulate by duplicating code (ugly, I know).

    ReplyDelete

Post a Comment