Wann wäre dynamisches Scoping sinnvoll?


9

Mit dynamischem Scoping kann ein Angerufener auf die Variablen seines Anrufers zugreifen. Pseudo-C-Code:

void foo()
{
    print(x);
}

void bar()
{
    int x = 42;
    foo();
}

Da ich noch nie in einer Sprache programmiert habe, die dynamisches Scoping unterstützt, frage ich mich, wie einige reale Anwendungsfälle für dynamisches Scoping aussehen würden.


Vielleicht ist es einfacher zu implementieren, wenn der Interpreter in einer bestimmten anderen Sprache implementiert ist?
Alf P. Steinbach

2
Es ist so wenig nützlich, dass die meisten Sprachen, die es einmal verwendet haben (z. B. Lisp), es nicht mehr tun. Nur für ein offensichtliches Beispiel verwendeten die meisten frühen Lisp-Implementierungen dynamisches Scoping, aber jetzt verwenden alle Hauptvarianten (z. B. CL, Schema) lexikalisches Scoping.
Jerry Coffin

1
@JerryCoffin: Bemerkenswerte Ausnahmen sind Perl und Emacs Lisp - beide verwendeten ursprünglich dynamisches Scoping und unterstützen jetzt (Perl 5, Emacs 24) sowohl dynamisches als auch lexikalisches Scoping. Es ist schön, wählen zu können.
Jon Purdy

@JerryCoffin Es stimmt nicht genau mit dem codierten Beispiel überein, aber Javascript nutzt das dynamische Scoping immer noch in großem Umfang, wenn ich die Frage richtig verstehe. Ich versuche immer noch, an einen allgemeinen Vorteil zu denken, der jedoch nicht nur ein kurzes Kommen der Sprache ausgleicht.
Adrian

@JerryCoffin In Common Lisp wird immer noch ein dynamisches Scoping verwendet, das sich hauptsächlich mit der dynamischen Steuerung von Lesen und Drucken befasst.
Vatine

Antworten:


15

Eine sehr nützliche Anwendung des dynamischen Scoping besteht darin , Kontextparameter zu übergeben, ohne dass jeder Funktion in einem Aufrufstapel explizit neue Parameter hinzugefügt werden müssen

Beispielsweise unterstützt Clojure das dynamische Scoping über das Binden , mit dem der Wert *out*für das Drucken vorübergehend neu zugewiesen werden kann . Wenn Sie erneut binden, *out*wird jeder Aufruf zum Drucken im dynamischen Bereich der Bindung in Ihrem neuen Ausgabestream gedruckt. Sehr nützlich, wenn Sie beispielsweise alle gedruckten Ausgaben in eine Art Debugging-Protokoll umleiten möchten.

Beispiel: Im folgenden Code wird die Do-Stuff-Funktion in der Debug-Ausgabe und nicht in der Standardausgabe gedruckt. Beachten Sie jedoch, dass ich keinen Ausgabeparameter zu Do-Stuff hinzufügen musste, um dies zu aktivieren.

(defn do-stuff [] 
  (do-other-stuff)
  (print "stuff done!"))

(binding [*out* my-debug-output-writer]
  (do-stuff))

Beachten Sie, dass die Bindungen von Clojure auch threadlokal sind, sodass Sie kein Problem mit der gleichzeitigen Verwendung dieser Funktion haben. Dies macht Bindungen erheblich sicherer als (ab) die Verwendung globaler Variablen für denselben Zweck.


2

(Haftungsausschluss: Ich habe noch nie in einer dynamischen Scoping-Sprache programmiert.)

Das Scoping ist viel einfacher zu implementieren und möglicherweise schneller. Beim dynamischen Scoping wird nur die eine Symboltabelle benötigt (die derzeit verfügbaren Variablen). Es liest nur aus dieser Symboltabelle für alles.

Stellen Sie sich in Python dieselbe Funktion vor.

def bar():
    x = 42;
    foo(42)

def foo(x):
    print x

Wenn ich bar aufrufe, füge ich x in die Symboltabelle ein. Wenn ich foo aufrufe, nehme ich die aktuell für bar verwendete Symboltabelle und schiebe sie auf den Stapel. Ich rufe dann foo auf, an das x übergeben wurde (wahrscheinlich wurde es beim Aufrufen der Funktion in die neue Symboltabelle eingefügt). Nach dem Beenden der Funktion muss ich den neuen Bereich zerstören und den alten wiederherstellen.

Beim dynamischen Scoping ist dies nicht erforderlich. Ich muss nur die Anweisung kennen, zu der ich zurückkehren muss, wenn die Funktion endet, da nichts an der Symboltabelle getan werden muss.


"Potenziell schneller" nur in einem (naiven) Dolmetscher; Compiler können mit lexikalischem Scoping viel besser umgehen als mit dynamischem Scoping. Außerdem ist "Beim dynamischen Scoping ist dies nicht erforderlich ..." falsch: Beim dynamischen Scoping müssen Sie die Symboltabelle aktualisieren, um den vorherigen Wert wiederherzustellen, wenn der Gültigkeitsbereich einer Variablen endet (z. B. wenn die Funktion zurückgegeben wird). Tatsächlich denke ich, dass der Code normalerweise direkt auf ein "Symbolobjekt" verweist, das ein veränderbares Feld für den aktuellen Wert der Variablen enthält, viel schneller als jedes Mal eine Tabellensuche durchzuführen. Trotzdem verschwindet die Update-Arbeit nicht einfach.
Ryan Culpepper

Ah, ich war mir nicht bewusst, dass dynamisches Scoping den Wert noch vor dem Aufruf der Funktion wiederherstellen würde. Ich dachte, sie beziehen sich alle auf den gleichen Wert.
Sternberg

2
Ja, es nistet immer noch. Andernfalls wäre es gleichbedeutend damit, jede Variable global zu machen und ihr nur eine einfache Zuweisung vorzunehmen.
Ryan Culpepper

2

Die Ausnahmebehandlung in den meisten Sprachen verwendet dynamisches Scoping. Wenn eine Ausnahme auftritt, wird die Steuerung an den nächstgelegenen Handler im (dynamischen) Aktivierungsstapel zurücküberwiesen.


Bitte kommentieren Sie den Grund für die Ablehnung. Vielen Dank!
Eyvind

Dies ist eine interessante Einstellung. Würden Sie auch sagen, dass returnAnweisungen in den meisten Sprachen dynamisches Scoping verwenden, weil sie die Kontrolle an den Aufrufer auf dem Stapel zurückgeben?
Ruakh

1

Ich bin mir nicht 100% sicher, ob dies eine exakte Übereinstimmung ist, aber ich denke, dass es im Allgemeinen zumindest nahe genug kommt, um zu zeigen, wo es nützlich sein kann, um Regeln für den Geltungsbereich zu brechen oder zu ändern.

Die Ruby-Sprache ist die Vorlagenklasse ERB, die beispielsweise in Rails zum Generieren von HTML-Dateien verwendet wird. Wenn Sie es verwenden, sieht es so aus:

require 'erb'

x = 42
template = ERB.new <<-EOF
  The value of x is: <%= x %>
EOF
puts template.result(binding)

Die bindingHände greifen auf lokale Variablen des ERB-Methodenaufrufs zu, damit sie darauf zugreifen und sie zum Füllen der Vorlage verwenden können. (Der Code zwischen den EOFs ist eine Zeichenfolge, der Teil zwischen <% =%> wird von ERB als Ruby-Code ausgewertet und deklariert seinen eigenen Bereich wie eine Funktion.)

Ein Rails-Beispiel zeigt dies noch besser. In einem Artikel-Controller finden Sie ungefähr Folgendes:

def index
  @articles = Article.all

  respond_to do |format|
    format.html
    format.xml  { render :xml => @posts }
  end
end

Die Datei index.html.erb könnte dann die lokale Variable @articleswie folgt verwenden (in diesem Fall werden die Erstellung eines ERB-Objekts und die Bindung vom Rails-Framework verwaltet, sodass Sie sie hier nicht sehen):

<ul>
<% @articles.each do |article| %>
  <li><%= article.name</li>
<% end %>
</ul>

Durch die Verwendung einer Bindungsvariablen ermöglicht Ruby die Ausführung ein und desselben Vorlagencodes in verschiedenen Kontexten.

Die ERB-Klasse ist nur ein Anwendungsbeispiel. Ruby ermöglicht es im Allgemeinen, den tatsächlichen Ausführungsstatus mit Variablen- und Methodenbindungen mithilfe der Kernel # -Bindung abzurufen. Dies ist sehr nützlich in jedem Kontext, in dem Sie eine Methode in einem anderen Kontext auswerten oder einen Kontext für die spätere Verwendung beibehalten möchten.


1

Die Anwendungsfälle für das dynamische Scoping sind IHMO-dieselben wie für globale Variablen. Dynamisches Scoping vermeidet einige Probleme mit globalen Variablen und ermöglicht eine kontrollierte Aktualisierung der Variablen.

Einige Anwendungsfälle, an die ich denken kann:

  • Protokollierung : Es ist sinnvoller, Protokolle nach Laufzeitkontext zu gruppieren, als nach einem lexikalischen Kontext. Sie können den Logger dynamisch an einen Anforderungshandler binden, sodass alle von dort aufgerufenen Funktionen dieselbe "requestID" in den Protokolleinträgen verwenden.
  • Ausgangsumleitung
  • Ressourcenzuweiser : Um die für eine bestimmte Aktion / Anforderung zugewiesenen Ressourcen zu verfolgen.
  • Ausnahmebehandlung .
  • Kontextverhalten im Allgemeinen, z. B. Emacs, die die Schlüsselzuordnung in bestimmten Kontexten ändern .

Natürlich ist dynamisches Scoping nicht "unbedingt erforderlich", sondern entlastet Sie von der Notwendigkeit, Tramp-Daten entlang der Anrufkette weiterzuleiten oder intelligente globale Proxys und Kontextmanager zu implementieren.

Aber auch hier ist dynamisches Scoping praktisch, wenn es um Elemente geht, die häufig als globale Elemente behandelt werden (Protokollierung, Ausgabe, Ressourcenzuweisung / -verwaltung usw.).


-2

Es gibt so ziemlich keine. Ich hoffe sehr, dass Sie zum Beispiel nie zweimal dieselbe Variable verwenden.

void foo() {
    print_int(x);
}
void bar() {
    print_string(x);
}

Wie ruft man nun foo und bar von derselben Funktion aus auf?

Dies ähnelt praktisch der einfachen Verwendung einer globalen Variablen und ist aus den gleichen Gründen schlecht.


2
x = 42; foo(); x = '42'; bar();?
Luc Danton
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.