Ein in Arbeit befindliches Konzeptdokument
Inspiriert durch die Skriptsprache Wren ist die Idee entstanden, Fibers in Consize einzubauen. Fibers (siehe Wikipedia) kann man als eine Implementierung von Coroutinen ansehen, eine Form leichtgewichtiger Threads mit deteministischem Verhalten.
Eigentlich sind Fibers ein naheliegendes Konzept für Consize, denn eine Continuation, also ein Rechenzustand aus Data- und Callstack, ist fundamental für Consize. Ich habe dafür in der Vergangenheit schon oft diese Notation dafür gewählt: Ein Stapel [ @S ]
und eine Quotierung [ @Q ]
im Verbund stellen eine Fiber dar, [| @S | @Q |]
.
Die verwendete Notation wird ausführlich in der Consize-Documentation (Anhang B, Version 2017) erläutert.
[ @S ] [ @Q ] fiber -> [| @S | @Q |]
[| @S | @Q |] unfiber -> [ @S ] [ @Q ]
Der erste Designvorschlag zum Aufruf (call/f
) einer Fiber legt die aktuelle Continuation als Fiber auf einem neu und eigens dafür eingeführten Fiberstack ab, der sich "unter" dem Datastack befindet. Auf dem Datastack muss sich genau ein Datenelement #D
befinden, das als Argument auf dem Stapel der Fiber abgelegt wird.
@F | @DS #D [| @S | @Q |] | call/f @CS
=> @F [| @DS | @CS |] | @S #D | @Q
Dieser Entwurf geht davon aus, dass eine Fiber immer einen Datenwert
#D
beim Aufruf erwartet und ebenso einen Datenwert bei ihrer Unterbrechung zurückgibt. Wenn kein Datenwerte übergeben oder zurückgegeben wird, so ist mit Dummywerten zu arbeiten, die durch eindrop
entfernt werden.
Die Selbstunterbrechung einer Fieber durch yield
und die damit einhergehende Suspendierung (Stilllegung) aktiviert wieder die oberste Fiber auf dem Fiberstack. Der Datenwert #D
wird an die reaktivierte Fiber übergeben.
@F [| @S | @Q |] | @DS #D | yield @CS
=> @F [| @DS | @CS |] | @S #D | @Q
@F [| @S | @Q |] | @DS #D | yield
=> @F | @S #D | @Q
@F [| @S | @Q |] | @DS #D |
=> @F | @S #D | @Q
Frage: Wie wird eine Fiber beendet, ohne dass sich an einer Reaktivierung versucht wird? Schon oben konnte ein Fiber mit einer leeren Quotierung aufgerufen werden. Das ist nicht brauchbar. Würde ein
fiber-done?
etwas nützen?[| @S | |] | fiber-done? -> t [| @S | #C @Q |] | fiber-done? -> f : fiber-done? unfiber swap drop isEmpty?
Um eine Fiber aufzurufen, muss sie zunächst mit resume
vom Fiberstapel geholt werden.
@F [| @D | @Q |] | @DS | resume @CS
=> @F | @DS [| @S | @Q |] | @CS
Es heißt, dass man mit call/cc
Fiber bzw. Coroutinen implementieren können soll. Das scheint mir ein ziemlicher Aufwand zu sein.
@DS [ @Q ] | call/cc @CS =>
[ @DS ] [ @CS ] | @Q
[ @DS ] [ @CS ] | continue =>
@DS | @CS
Kann eine Fiber innerhalb ihrer selbst ein call/cc
umsetzen? Und kann das mit Hilfe einer zweiten Fiber realisiert werden ohne ein primitives Wort einzuführen?
[| @D | @C |] [ @Q ] call/cf
: call/cf [ unfiber ] dip call
: continuef [ fiber ] dip
Das ist so noch nicht lauffähig.
Wren kennt die folgenden Primitive, siehe wren_core.c
: new
, abort
, current
, suspend
, yield
, call
, error
, isDone
, transfer
, transferError
, try
.
PRIMITIVE(vm->fiberClass->obj.classObj, "new(_)", fiber_new);
PRIMITIVE(vm->fiberClass->obj.classObj, "abort(_)", fiber_abort);
PRIMITIVE(vm->fiberClass->obj.classObj, "current", fiber_current);
PRIMITIVE(vm->fiberClass->obj.classObj, "suspend()", fiber_suspend);
PRIMITIVE(vm->fiberClass->obj.classObj, "yield()", fiber_yield);
PRIMITIVE(vm->fiberClass->obj.classObj, "yield(_)", fiber_yield1);
PRIMITIVE(vm->fiberClass, "call()", fiber_call);
PRIMITIVE(vm->fiberClass, "call(_)", fiber_call1);
PRIMITIVE(vm->fiberClass, "error", fiber_error);
PRIMITIVE(vm->fiberClass, "isDone", fiber_isDone);
PRIMITIVE(vm->fiberClass, "transfer()", fiber_transfer);
PRIMITIVE(vm->fiberClass, "transfer(_)", fiber_transfer1);
PRIMITIVE(vm->fiberClass, "transferError(_)", fiber_transferError);
PRIMITIVE(vm->fiberClass, "try()", fiber_try);
PRIMITIVE(vm->fiberClass, "try(_)", fiber_try1);