Experiment mit Blöcken möglich?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Experiment mit Blöcken möglich?

Beitragvon sma » Samstag 24. November 2007, 13:06

Ich glaube, ich würde gerne einmal eine Python-Variante ausprobieren wollen, in der ich wie folgt Codeblöcke definieren und sofort an eine Funktion übergeben kann:

Code: Alles auswählen

foo() do:
  bar()

Dies wäre äquivalent zu folgendem:

Code: Alles auswählen

def anon():
  bar()
foo(__do__=anon)

def foo(__do__=None):
  if __do__: __do__()

Das neue Schlüsselwort "do" müsste sich problemlos in die Syntax einfügen und bis auf den (zugegeben häßlichen) Schlüsselwortparameter ändert sich auch an Funktionen, denen Blöcke übergeben werden können, nichts Grundlegendes. Der Name "anon" steht für eine anonyme lokale Funktion.

Ungeklärt wäre, wie so ein Block etwas zurückgeben kann. Oder anders gefragt: Was passiert bei einem "return" innerhalb eines Blocks? Sollte er nach Smalltalk-Tradition (und wie es Neal Gafter daher auch für Java vorschlägt) die umgebende Funktion beenden? Und was ist mit "break" oder "continue"?

Ich bin bestimmt nicht der erste mit dieser Idee.

Hat das schon mal jemand ausprobiert? Der offensichtliche Ansatz wäre IMHO, einen Präprozessor für den normalen Python-Interpreter zu bauen, da man das ganze über eine AST-Transformation auf normalen Python-Code abbilden kann.

Ich hatte mir neulich mal so angeschaut, was für Parser-Generatoren es für Python gibt und mich gewundert, dass offenbar keiner einen Python-Parser als Beispiel mitliefert. Dabei wäre das IMHO doch der beste Testfall. Gibt es einen in Python geschriebenen Parser, den man als Grundlage für so einen Präprozessor nutzen könnte? Müsste ja eigentlich Teil von PyPy sein, doch das Ding wirkt ehrfurchteinflößend groß ;)

Ich fand eigentlich pyparsing als Combinator-Parser ganz interessant. Gibt es dafür vielleicht bereits eine Python-Grammatik?

Gibt es andere Projekte, die mit Supersets (oder Subsets) von Python experimentieren?

Stefan
Benutzeravatar
Leonidas
Administrator
Beiträge: 16023
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Re: Experiment mit Blöcken möglich?

Beitragvon Leonidas » Samstag 24. November 2007, 13:47

sma hat geschrieben:Ich fand eigentlich pyparsing als Combinator-Parser ganz interessant. Gibt es dafür vielleicht bereits eine Python-Grammatik?

Eine Grammatik gibt es schon, aber diese zu implementieren wäre wohl mit PLY wesentlich einfacher als mit pyparsing, denke ich.

sma hat geschrieben:Gibt es andere Projekte, die mit Supersets (oder Subsets) von Python experimentieren?

Jython und IronPython implementieren ihre eigenen Python-Parser, zudem hast du natürlich neben PyPys RPython noch Pyrex und Cython, welche ein Python-Subset in C-Code transferieren.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
BlackJack

Beitragvon BlackJack » Samstag 24. November 2007, 14:00

Iiiih wie hässlich. :-)

Das mit dem `__do__` ist unschön und es skaliert auch nicht so gut. Was ist wenn man mehrere Blöcke übergeben möchte?

Da es sich wie eine Funktion verhalten soll, müssten ``return``, ``continue``, und ``break`` die gleiche Semantik wie in normalen ``def``\inierten Funktionen haben. Wenn man eine einfache Transformation nach Deinem Muster durchführt, bekommt man das ja automatisch.

Du bist in der Tat nicht der erste mit einer hässlichen Idee für "Blöcke". ;-)

Zum Experimentieren eignet sich wahrscheinlich EasyExtend und da ist eventuell das "Makro-Fiber" einen Blick wert.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Beitragvon sma » Sonntag 2. Dezember 2007, 15:39

Leonidas,

mit Grammatik meinte ich eine Implementierung, nicht eine Beschreibung. Das es diese im Python-Standard gibt, war mir bekannt. Ich hätte halt vermutet, dass jedes Parser-Rahmenwerk in Python, das etwas auf sich gibt, mal mit einem Python-Parser als Beispiel kommt. Ist aber nicht so.

BlackJack,

Schönheit liegt im Auge des Betrachters. Du glaubst gar nicht, wie häßlich ich immer noch diese Unterstrichanhäufungen bei allem, was ansatzweise mit Meta-Programmierung zu tun hat, finde. Das `__do__` mit seinen Unterstrichen fand ich dann aber analog zu `__name__` oder `__doc__` und daher angemessen. Mehrere Blöcke, du fragtest danach, gehen halt nicht. Braucht man so gut wie nie zeigt z.B. die Praxis bei Ruby.

Das EasyExtend war übrigens ein guter Tipp. Hat Spass gemacht, damit zu experimentieren und es war leicht, die Wunschsyntax damit zu realisieren. Dennoch, für mein PySpec habe ich mich jetzt für eine externe DSL entschieden und vielleicht sollte man auf explizite Blöcke einfach verzichten. Mein Interesse daran hat jedenfalls nachgelassen :)

Das Problem mit `break` und co lässt sich übrigens nicht so einfach lösen, man hat auf einmal nicht-lokale Anweisungen, aber das hat wie gesagt Neal Gafter bereits alles viel ausführlicher erklärt als ich es hier könnte. Diese drei Befehle (sogenannte exit continuations) machen Blöcke kompliziert.

Beispiel:

Code: Alles auswählen

# normale Schleife
for i in range(6):
  if i == 3: break

# angenommen, etwas wie
foreach(range(6)) do(i):
  if i == 3: break

# würde zu
def _(i):
  if i == 3: break
foreach(range(6), __do__=_)

# und foreach würde so aussehen:
def foreach(itr, __do__=None):
  if __do__:
    for i in itr: __do__(i)

# dann wäre das ein
SyntaxError: 'break' outside loop

Damit das `break` wie erwartet funktioniert, muss man dies machen:

Code: Alles auswählen

def foreach(itr, __do__=None):
  if __do__:
    for i in itr:
      try: __do__(i)
      except BreakSignal: break
      except ContinueSignal: continue
      except ReturnSignal, e: return e.value

# und break wird jetzt ersetzt:
def _(i):
  if i == 3: raise BreakSignal

Und selbst das ist noch keine vollständige Lösung.

Stefan
BlackJack

Beitragvon BlackJack » Sonntag 2. Dezember 2007, 16:21

Also meine Lösung ist einfach: Die Blöcke sind anonyme Funktionen und da gibt's auf "oberster" ebene kein ``break`` und kein ``continue`` und ``return`` gibt den Rückgabewert des Blocks/der anonymen Funktion zurück. Wenn man bei dem ``foreach`` erwartet, dass das ``break`` funktioniert, will man mehr als anonyme Funktionen und eine viel tiefgreifendere Änderung der Sprache. Und zwar in eine Richtung die von Guido wahrscheinlich nicht mitgetragen wird.

Diese Art der Iteration mit Blöcken verträgt sich aber auch IMHO nicht mit Python. Das ist mehr eine "push API" wo man Funktionen übergibt, die dann von aussen mit den Werten gefüttert werden, während Python, insbesondere mit Generatoren, fast durchgängig eine "pull API" beim Iterieren bietet, wo man sich die Werte aus einem Iterator/Generator heraus holt. Beides in einer Sprache zu haben verträgt sich nicht besonders, weil man dann fast zwangsläufig Code schreiben muss um zwischen beiden Varianten zu "übersetzen".

Zumindest für mich skaliert die Art von Python auch besser und es fällt mir leicht "lazy"-Auswertungen damit zu basteln und unterschiedlich "zusammen zu stecken". Es hat grosse Ähnlickeit mit Pipes auf der Shell-Ebene.
Benutzeravatar
Leonidas
Administrator
Beiträge: 16023
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Beitragvon Leonidas » Sonntag 2. Dezember 2007, 16:48

sma hat geschrieben:mit Grammatik meinte ich eine Implementierung, nicht eine Beschreibung. Das es diese im Python-Standard gibt, war mir bekannt. Ich hätte halt vermutet, dass jedes Parser-Rahmenwerk in Python, das etwas auf sich gibt, mal mit einem Python-Parser als Beispiel kommt. Ist aber nicht so.

Ja, da hast du recht. Das liegt aber teilweise auch daran, dass man mit einigen Frameworks einfach keinen Python-Parser bauen kann. Das war ja einer der Kritikpunkte zu Pyparsing die zu ZestyParser geführt haben. Teilweise liegt das aber auch daran, dass sich die Syntax alle ein, zwei Jahre mal geringfügig ändert. Jedoch wenn du einen Parsergenerator wie PLY nimmst und die Python-Grammatik dann kannst du damit höchstwarscheinlich recht einfach einen Parser für Python bekommen.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Beitragvon sma » Sonntag 2. Dezember 2007, 17:09

BlackJack, mir gefällt das mit den exit-continuations auch nicht und ich wollte nicht vorschlagen, dass so in Python einzubauen. Aber es ist notwendig, wenn man wirklich first class "Blöcke" zu einer Sprache hinzufügen möchte. Smalltalk, das solche Blöcke hat, hat einfach kein `break` oder `continue` :) Die non local returns verstehen die meisten intuitiv, auch wenn es manchmal komisch wirkt, dass man auf diese Weise eine Methode mehr als einmal beenden kann. Und ob Java jemals Blöcke (a.k.a. Closures) bekommt, steht in den Sternen.

Leonidas, Pyparsing ist meines Wissen ein combinator parser und damit eigentlich rein funktional und zustandsfrei und das beißt sich mit der Notwendigkeit, Zustände für die Einrückungstiefen zu verwalten.

Tatsächlich liegt das Problem beim Parsen von Python-Code ja im Lexer (wenn man diese traditionelle Aufteilung hat), der doch bitte INDENT- und DEDENT-Token erzeugen soll, damit der eigentliche Parser es schön einfach hat.

Viele Parsergeneratoren (ANTLR etwa) wollen aber den Lexer gleich mit bauen. Nicht weiter schwer, schließlich sind kontextfreie Grammatiken ein Superset von regulären Ausdrücken, die man traditionell für Lexer benutzt. Doch auf einmal wird es schwer, hier einzugreifen.

Statt jedoch jedes Mal selbst einen Lexer für Einrückungen zu bauen, hätte ich gerne etwas fertiges (in der Theorie, in der Praxis habe ich das, da ich mir vor einiger Zeit einen Python-Interpreter in Java gebaut hatte)

Stefan
BlackJack

Beitragvon BlackJack » Sonntag 2. Dezember 2007, 18:54

Schau doch mal in's `tokenize`-Modul in der Standardbibliothek.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Beitragvon sma » Montag 3. Dezember 2007, 09:53

BlackJack hat geschrieben:Schau doch mal in's `tokenize`-Modul in der Standardbibliothek.

Danke für den Tipp.

Code: Alles auswählen

from tokenize import generate_tokens, tok_name

code = """
def test():
    return [
        42
    ]
"""

readline = iter(code.splitlines(True)).next
for t in generate_tokens(readline):
    print (tok_name[t[0]],) + t[1:]

Mich wundert allerdings, das jede Zeile ein \n enthalten muss - das war nicht explizit so dokumentiert und sorgt für komische NL-Tokens zusätzlich zu NEWLINE-Tokens.
BlackJack

Beitragvon BlackJack » Montag 3. Dezember 2007, 10:03

Das mit den Zeilenenden ist eben das verhalten von der `readline()`-Methode auf Dateiobjekten. Insofern ist es indirekt schon dokumentiert.

Wer ist online?

Mitglieder in diesem Forum: Google [Bot], snafu, Sophus