Kann man sich eigene Statement´s programmieren?

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.
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Sonntag 9. November 2008, 23:25

str1442 hat geschrieben:Mir ist schon klar, das andere Sprachen (und insbesondere Funktionale) diese Dinge anders umsetzen, aber viele Sprachen nutzen Statements (oder ähnliche Dinge) eben nicht wie Funktionen. Ein Statement ist eben eine Anweisung an die Sprache (bzw deren Interpreter / Compiler), aber nichts, was unmittelbar mit den zu verarbeitenden Daten / Objekten zu tun hat. Statements fallen aus der Reihe, das wollte ich damit ausdrücken.
Ich setze im folgenden mal an dass man Statements in Python und Makros in Lisp gleichsetzt, da beide eine Art Sonderstellung in den entsprechenden Sprachen gegenüber den Funktionen haben (was nicht heißt dass Makros Statements sind - es sind Expressions).

Nun, es ist eben so dass man in Python für die Arbeit mit Daten Funktionen verwendet, aber davon kann man nicht schließen dass es generell so ist. Gerade Makros Arbeiten wie Statements auf Daten (dem Code) und transformieren sie irgendwie. So gesehen bietet einem die Möglichkeit eigene Statements zu definieren einen Vorteil der grob vergleichbar damit ist welchen Vorteil es gibt, dass man eigene Typen, Objekte definieren kann. Natürlich muss man nicht OO programmieren und man muss nicht eigene "Statements" definieren können, aber es gibt einem die Flexibilität die es möglich macht. Mit Makros kann man etwa OOP in eine Sprache bringen, die kein OOP von Haus aus unterstützt. Es gibt noch mehr Einsatzzwecke, aber ich muss auch sagen, dass ich bisher selbst nicht genau weiß, was mit Makros machbar ist, was sonst zwar auch möglich aber komplizierter wäre; ich habe auch jahrelang Sprachen ohne Makros verwendet, jetzt ist es nicht ganz einfach diese zusätzliche Flexibilität an das eigene Denken einzupassen.

Eine interessante Sache die mir zu dem Thema einfällt ist, wie man in Scheme Lokigoperatoren implementieren könnte. So sind ``and`` und ``or`` Makros, da sie ihre Parameter nicht unbedingt auswerten müssen (``and bricht ab, wenn der erste Parameter False ist ab, ``or`` bricht ab wenn der erste Parameter True ist). Andererseits könnte aber ``not`` durchaus eine Funktion sein, da sie den Wert auswertet und danach negiert. Jedoch besteht in der Nutzung dieser Operatoren keinerlei Unterschied, sie verhalten sich exakt so wie ``and``, ``or`` und ``not`` in Python, die alle drei Statements sind, obwohl das ``not`` die selbe Semantik wie eine Funktion hat.

Wie sma sagte, was man als Statement rausnimmt und was man zur Expression macht ist recht arbiträr vom Designer festgelegt und wird durch dessen Ideen und auch dessen Inspirationen und Pläne bedingt.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Sonntag 9. November 2008, 23:27

Mal ein Beispiel:

Code: Alles auswählen

def f(x, y):
    erg = x+y
    return erg

reduce(f, range(10))
Das ist ja nichts weiter als ein eigener Nachbau von operator.add, aber darum solls nicht gehen.

Der Interpreter sieht das ganze nun so, wie es ist. (bzw kompiliert, gebytekompiliert, weiß der Henker wie).
Er sieht es absolut.

Logisch gesehen ist aber folgendes zu erkennen:

Code: Alles auswählen

# Teil des Programmes.

# def: Ab hier ist es *kein* Teil des Programmes mehr
def f(x, y):
    # Eine Subroutine, dh ein eigenes Programm, Bereich ist abgeschlossen.
    erg = x + y
    # Teil des Programmes
    
    # Tätigkeit des *Interpreters* wird ausgelöst.
    return erg

# Wieder Teil des eigentlichen Programmes

# Ausführung einer Tätigkeit
reduce(f, range(10))
Was ich damit ausdrücken will: Durch Verwendung von def wird quasi ein eigenes Unterprogramm geöffnet. Das ist aus Sicht des Interpreters natürlich eine Tätigkeit. Aber für das Programm praktisch irrelevant. Ist dem Programm ja egal, ob da nun direkt lambda: x+y oder operator.add oder was auch immer steht. Oder ob man den Code zwanzigmal wiederholen muss. Das Programm würde das gleiche machen. Es ist eine Vereinfachung (Zentralisierung) durch den Interpreter, aber für die einzelnen Abschnitte des Codes macht es (außer bei eventuellen Wiederholungen) keinen Unterschied. Würde man "def" als eine Funktion betrachten, würde die Dinge tun (nämlich Funktion verfügbar machen aus einem Unterprogramm), die eigentlich nur den Interpreter was angingen, im Großen und Ganzen für das Programm aber egal sein können (sofern man es verkraften könnte, zigmal zu copy & pasten). Eine Funktion als eine Tätigkeit *im Programm* Ist nur der Funktionskörper. *Wie* der Wert nun das eigentliche Programm erreicht, ist egal. Das macht *der Interpreter*, mittel des Return Statements.
Statements tun also bei der Betrachtung Dinge, die für den Interpreter (und den genervten Programmierer) wichtig sind, aber für die eigentlichen Funktionen des Programmes nicht relevant sind (da man den Programmcode ja auch als kompletten (aber sehr großen Block) ohne Statements schreiben könnte, zb so:)

Code: Alles auswählen

a = 0
for i in xrange(10):
    a += i
Gleiches gilt hier für for, man könnte rein theoretisch auch ohne for arbeiten, indem man 10mal hinternander a+= i schreibt und zwischendrin i immer hochzählt. Das Ergebnis ist das gleiche, und so für das Programm irrelevant. Dagegen macht eben diese Codelogik an sich etwas aus, und ist auf die eine oder andere Art und Weise Teil des Programmes.
`if` und `for` sind in C *Funktionen*!? Wäre mir neu.
Öhm... Nein, aber ihr Aufruf sieht ähnlich aus, vergessen wir das lieber :oops: :D
Aber warum muss es den geben?
Weil Statements Dinge durch den Interpreter tun, aber nicht (wie Funktionen (nicht als Funktionen, aber als das Stück Programmcode, das xyz tut)) etwas für die Sprache. Es sind Zugriffe auf Features der Sprache, die aber für die reine Logik nicht existieren müssen. So meinte ich das auch mit den "Daten". Gut, heute möchte sicherlich keiner mehr Assembler schreiben, aber es würde rein theoretisch funktionieren. Die eigentliche Logik bleibt unberührt von Statements. Oder ohne Klassen arbeiten, geht heute noch recht gut. Trotzdem ist es per "class" möglich. Weil es ein Feature der Sprache ist, nicht aber, weil ein Objekt jetzt unbedingt nur Logik gehören müsste.
Es kommt zu "überflüssigem" wie dem ``a if c else b`` weil das in anderen Sprachen schon vom "normalen" `if` abgedeckt wird. Ausdrücke sind Konstrukte, die zu einem Ergebnis ausgewertet werden. Damit ist das Konstrukt per Definition ein Ausdruck. Es ist ja überhaupt nicht an Zuweisungen gebunden -- man kann es überall verwenden, wo ein Wert bzw. eben ein Ausdruck erwartet wird.
Achso, naja gut. Überflüssig habe ich übringens nur genutzt, um nicht andauerend die gleichen Wörter wiederholen zu müssen. Sowas mag ich selbst nicht gern lesen, drum wollt ichs euch ersparen.
Wieso sollte es keine Objekte geben, die "Interna" der Sprache repräsentieren? Und wieso muss man irgendwo aufhören?
Sollen nicht, klar kann man das machen. Oder man macht es anders. In Python hat man wenn man will Zugriff auf Internas. Auch lernt man mit der Zeit viele Dinge, die man eher nicht braucht oder die gut zu wissen sind, bzw schön für das Verständnis der Sprache (globals(), locals() (wobei ich das gerne in Komvination mit pdb nutze), type und dessen Verwendung zur Klassenerzeugung, ...), aber man muss nichts benutzen (oder sollte sogar nicht). Würde man das class Statement entfernen, und jeder müsste sich über type() seine Klassen erzeugen, wäre das unschön, aber möglich. Man muss nur wissen, wie Metaklassen genau funktionieren. Genauso wäre das doch mit Exceptions: Ich könnte raise verwenden *oder* eine Methode auf das Exception Objekt. Will ich aber nicht (wi wohl Io, wenn du es anmerkst, das ich bisher nicht kenne) jemanden zwanghaft mit Sprachinterna's "belästigen", kann ich auch einfach raise verwenden. Oder würdest du ernsthaft Klassen über type() erzeugen? (Wobei ich nicht bezweifle, das Methoden auf ein Exception Objekt auch eine Schöne Sache sein können)
Und in Python gibt's doch auch eine Menge (magische) Methoden, die in die Bedeutung von Statements eingreifen.
Richtig, ich könnte die aber theoretisch auch direkt aufrufen.
... man kann nur keine neue Syntax einführen.
Was auch gut so ist :D
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Sonntag 9. November 2008, 23:43

str1442 hat geschrieben:Würde man das class Statement entfernen, und jeder müsste sich über type() seine Klassen erzeugen, wäre das unschön, aber möglich. Man muss nur wissen, wie Metaklassen genau funktionieren.
Nein, wieso? Es ist nur etwas weniger Syntaktischer Zucker, aber wenn ich sage dass es in meiner Sprache Klassen über ``type()`` erstellt werden und wie die Parameter zu verstehen ist, ist das überhaupt kein Problem.

Wenn ich Klassen vererbe muss ich auch nicht genau wissen wie die C3 MRO funktioniert. Ich weiß dass sie funktioniert und ich weiß wo ich nachschauen muss wenn sie nicht das tut was ich will, aber ansonsten kann ich gegeben dass wenn ich ``class SomethingElse(Something)`` das passiert was ich will.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
BlackJack

Montag 10. November 2008, 09:17

@str1442: Ich verstehe Dich immer noch nicht so richtig. Was willst Du mit Sätzen wie "Der Interpreter sieht das ganze nun so, wie es ist. […] Er sieht es absolut." sagen? Zumal der Interpreter den Code da nicht sieht, sondern irgend welchen Bytecode, der von der Sprachdefinition noch nicht einmal festgelegt wird.

Und mindestens die ``def``-Zeile ist Teil des ausgeführten Programms weil ``def`` in Python eine ausführbare Anweisung (Statement) ist und keine Deklaration. Die Anweisung bindet den Namen `f` an ein Funktionsobjekt, dass den Code repräsentiert. Der wird zwar erst beim Aufruf ausgeführt, ist aber sehr wohl Teil des Programms und keineswegs irrelevant. Wenn das nicht passiert könnte man den Code später nicht ausführen.

Deine beiden Beispiele kämen auch ohne die Funktionalität der Anweisungen aus, aber spätestens wenn die Obergrenze der Schleife nicht fest steht, kannst Du sie nicht so einfach durch mehrmaliges hinschreiben entfernen, weil Du ja nicht weisst wie oft Du wiederholen musst. Und ein ``if`` kannst Du schon mal gar nicht von der Programmlogik trennen.

Andererseits kann man die beiden Beispiel auch in einer Sprache ohne Anweisungen trotzdem ohne Wiederholungen im Quelltext ausdrücken:

Code: Alles auswählen

Range
f := method(x, y, x + y)
0 to(9) asList reduce(a, b, f(a, b)) println

a := 0
0 to(9) foreach(i,
    a = a + i
)
a println
Die Begründung warum es den Unterschied zwischen Anweisung und Funktionen bzw. Methoden geben muss verstehe ich wieder hinten und vorne nicht. Anweisungen tun etwas durch den Interpreter aber nicht für die Sprache!? Sprachbeschreibungen bestehen bei den meisten Sprachen mit Anweisungen in der Regel erst einmal aus den Grunddatentypen und dann den Anweisungen. Erst danach kommen die Funktionen aus der Standardbibliothek. Die Anweisungen sind wesentlicher Bestandteil der Syntax dieser Sprachen.

Assembler ist ein denkbar schlechtes Beispiel, denn das besteht ja *nur* aus Anweisungen für den in Silikon gegossenen Interpreter eine Abstraktionsschicht tiefer. Funktionen gibt's auf der Ebene ja gar nicht.

Wenn Du es auf das möglichst wenig beschränken willst (ohne bei Brainfuck zu landen), braucht man so etwas wie Funktionsaufrufe und eine bedingte Auswertung. Alles andere ist syntaktischer Zucker.

Wenn mich jemand zwingen würde mit `type()` Klassen zu erzeugen würde ich auch meutern, aber keine Statements zu haben, heisst ja nicht, dass das so umständlich sein muss.

Ich sehe auch keine "Belästigung" mit Sprachinterna, wenn ich statt ``raise Exception("blah)"`` in Io ``Exception raise("blah")`` schreiben muss. Man könnte ja genau so gut sagen dass einen Python damit "belästigt", das ``raise`` als Anweisung geschrieben werden muss.

Mal ein Io-Beispiel eines Stacks mit Datenelementen als verkettete Liste von `Node`-Objekten:

Code: Alles auswählen

#!/usr/bin/env io
EmptyStackError := Exception clone

Node := Object clone do(
    data ::= nil
    next ::= nil
    with := method(data, next, Node clone setData(data) setNext(next))
)

Stack := Object clone do(
    top ::= nil
    size ::= 0
    
    push := method(object,
        self setTop(Node with(object, self top))
        self setSize(self size + 1)
    )
    
    peek := method(
        self top ifNil(EmptyStackError raise("can't peek empty stack")) data
    )
    
    pop := method(
        node := self top
        if(node isNil,
            EmptyStackError raise("can't pop from empty stack")
        ,
            self setSize(self size - 1)
            self setTop(node next)
            node data
        )
    )
    
    asString := method(
        elements := List clone
        node := self top
        while(node isNil not,
            elements append(node data)
            node = node next
        )
        self type .. " with(" .. elements reverse join(", ") .. ")"
    )
    
    with := method(
        result := call target clone
        call evalArgs foreach(v, result push(v))
    )
)

stack := Stack clone println    # Stack with()
stack push(42) push(23) println # Stack with(42, 23)
stack peek println              # 23
stack pop println               # 23
stack pop println               # 42
try(
    stack peek println
) catch(EmptyStackError,
    "Ooops..." println          # Ooops...
) pass
Keine Anweisungen und Schlüsselworte, nur Objekte und Nachrichten, wobei Nachrichten selbst auch wieder Objekte sind. Wo wäre bei dem Quelltext jetzt etwas durch Anweisungen gewonnen? Ausser dass die Syntax der Sprache umfangreicher würde? Die informale Grammatik der gesamten Sprache sieht so aus:

Code: Alles auswählen

exp        ::= { message | terminator }
message    ::= symbol [arguments]
arguments  ::= "(" [exp [ { "," exp } ]] ")"
symbol     ::= identifier | number | string
terminator ::= "\n" | ";"
Es gibt keine `class`-"Anweisung" weil Io keine Klassen hat, aber man könnte sich natürlich auch eine `class`- und eine `def`-Methode auf `Object` definieren, die eine ähnliche "Syntax" wie Python haben, also beispielsweise:

Code: Alles auswählen

class(Foo, Object, Bar,         # "Klassenname" und Basis"klassen"
    def(methodName, arg1, arg2,
        self baz := arg1 + arg2
    )
)
Wie man sieht ist dadurch aber nicht wirklich etwas gewonnen. Nur eine leicht andere Schreibweise.
lunar

Montag 10. November 2008, 11:26

BlackJack hat geschrieben:Assembler ist ein denkbar schlechtes Beispiel, denn das besteht ja *nur* aus Anweisungen für den in Silikon gegossenen Interpreter eine Abstraktionsschicht tiefer.
Aha ... :D
BlackJack

Montag 10. November 2008, 11:52

Dammit… *Silizium* natürlich. Ich hatte mir schon gedacht, dass ich das, was ich gestern Nacht so gegen 2 Uhr zusammengeschrieben habe, besser liegen lasse und morgens noch einmal überfliege, bevor ich es abschicke. Und dann sowas… :oops:
lunar

Montag 10. November 2008, 13:15

Bleibt nur noch die Frage, an was du nachts um zwei gedacht hast, wenn dieses Wort den Weg in dein Posting gefunden hat ;) Ich glaube jetzt einfach mal, dass es nur eine fehlgeleitete Übersetzung des englischen Silicone war ;)
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Montag 10. November 2008, 20:28

@str1442: Ich verstehe Dich immer noch nicht so richtig. Was willst Du mit Sätzen wie "Der Interpreter sieht das ganze nun so, wie es ist. […] Er sieht es absolut." sagen? Zumal der Interpreter den Code da nicht sieht, sondern irgend welchen Bytecode, der von der Sprachdefinition noch nicht einmal festgelegt wird.
Für den Interpreter ist der Quellcode "Data". Beziehungsweise das daraus erstellte $COMPILAT.

Der Interpreter hat intern Funktionen und Tätigkeiten, und arbeitet mit den kompletten Daten. In seiner Funktion als Interpreter führt er natürlich bei Statements intern diverse Tätigkeiten durch.
Und mindestens die ``def``-Zeile ist Teil des ausgeführten Programms weil ``def`` in Python eine ausführbare Anweisung (Statement) ist und keine Deklaration. Die Anweisung bindet den Namen `f` an ein Funktionsobjekt, dass den Code repräsentiert. Der wird zwar erst beim Aufruf ausgeführt, ist aber sehr wohl Teil des Programms und keineswegs irrelevant. Wenn das nicht passiert könnte man den Code später nicht ausführen.
Natürlich. Aber das eigentliche Programm ist ja die Logik. Diese wiederrum nutzt Funktionen (oder Klassen, oder ...), um weitere Logik zu implementieren. Schlussendlich kommt man bei Python Standard Funktionen und Ausdrücken an, die wiederrum selbst eine interne Logik haben und sich eventuell an $INTERPRETER_SPRACHE-Libs bedienen.

Das ist erstmal das Programm. Jede Funktion ist nun eine Subroutine, die in sich abgeschlossen ist (meistens, aber global Zugriffe usw bilden hier keine Ausnahme; moment.). Der Interpreter wiederrum verbindet nun jedes kleine Unterprogramm und macht die Funktionen verfügbar. Eben durch Statements, wie zb def. Def an sich gehört aber nicht zum Programm: Es steht natürlich in der src, aber es greift auf eine Interpreterfunktion zu. Der Interpreter verbindet so alles miteinander.

Würde ich ohne Funktionen arbeiten wollen, könnte ich die Subroutine auch (stark zersplittert zwar) einfach so ins Modul schreiben. Die *Logik* bleibt mehr oder weniger die gleiche, außer, das man sich eine Heidenarbeit machen würde.

Eine Funktion hat also unmittelbar etwas mit Logik innerhalb eines Programmes zu tun, ein Statement dagegen ist essenziell für den Interpreter.

Genauso bei "class": class greift intern auf Interpreter Features zurück, dieser aber ruft type auf, was die *Logik* einer Klasse implementiert. Würde man nun direkt mit type() Klassen erstellen, wäre die Logik von Klassen unmittelbar ins Programm verstrickt. Klassen sich auch nur ein Feature Objekt-Orientierter Sprachen. Und ich kann ein Hello World Programm sowohl in Java als auch in C schreiben, mit syntaktischen Unterschieden, aber mit dem (mehr oder weniger) gleichem Ergebnis.

Bei while und co wird auch nur auf ein Interpreter Feature zurückgegriffen, die eigentliche Logik des Programmes könnte man vermutlich auch anders implementieren. Deswegen ist
Assembler
auch kein schlechtes Beispiel, da ich ja genau das aussagen wollte: In Assembler gibt es nichtmal Funktionen. Trotzdem *könnte* man komplexere Programme schreiben. Eben weil man Logik in ein Programm gießt, nicht irgendwelche Sprach Features.
aber spätestens wenn die Obergrenze der Schleife nicht fest steht, kannst Du sie nicht so einfach durch mehrmaliges hinschreiben entfernen weil Du ja nicht weisst wie oft Du wiederholen musst. Und ein ``if`` kannst Du schon mal gar nicht von der Programmlogik trennen.
Ich könnte aber auch BASIC::goto (bofh!!1) verwenden. Das Ergebnis wäre von der Logik her das gleiche. Ich greife auf ein anderes Sprach Feature zurück, und vermeide so if und while / for. goto gibts in Python ja auch nicht. Trotzdem kann ich zu BASIC Programmen logisch aquivalenten Code schreiben.

Der entscheidende Unterschied, den ich zu Funktionen (als eigenständige Subroutinen, die mitteln Sprach Features mit dem Rest der Logik verbunden werden bzw erst lauffähig gemacht werden (return)) mache, ist der, das Funktionen *Programm*logik enthalten sollten. *Nicht* Sprach-Logik. Diese Funktionen greifen wiederrum auf andere Funktionen zu (die mittels der Sprache integriert werden), die aber selbst wieder nur Programmlogik festlegen.

Deswegen ist eine Funktion für das Programm eine Logiktätigkeit. Statements sind Sprachtätigkeiten.
Die Anweisungen sind wesentlicher Bestandteil der Syntax dieser Sprachen.
Ja. Der Sprachen.
Ich sehe auch keine "Belästigung" mit Sprachinterna, wenn ich statt ``raise Exception("blah)"`` in Io ``Exception raise("blah")`` schreiben muss. Man könnte ja genau so gut sagen dass einen Python damit "belästigt", das ``raise`` als Anweisung geschrieben werden muss.
Es gibt viele Wege, etwas zu tun. Aber Dinge sollten konsequent getan werden. In Python ist man sehr dynamisch unterwegs (und hat damit ziemliche Vollmachten), aber Python hält Dinge recht einfach und transparent. Natürlich könnte man solche Exception Methoden nutzen. Dagegen ist ja auch nichts einzuwenden. Im Gegenteil, wenn eine Sprache einem soetwas konsequent bereitstellt und man direkt auf Sprachinternas zugreifen sollte, warum nicht? Aber Python will in erster Linie ja eine transparente, einfache Sprache bleiben, die einem trotzdem nichts versperrt, oder irr ich? Insofern ist der Weg über Statements als Abstraktion zu Sprachfeatures doch absolut ok. Ich würde eine Sprache wie Io und deren Umsetzungen natürlich nicht ablehnen; nur in vielen Sprachen ist der Weg über Statements als Trennung zwischen Programm und Sprachlogik eine gute Lösung. (Io sieht übringens nett aus)

@Leonidas:

Gut, man muss es nicht wissen, aber ich persönlich würde, wäre mir das neu, sehr gerne erfahren, warum das immer type() sein muss und ob ich da nicht noch was anderes nutzen kann, etc usw ;) Man wird eben automatisch tiefer in Sprach Internas
"reingezogen".
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Montag 10. November 2008, 21:00

str1442 hat geschrieben:Genauso bei "class": class greift intern auf Interpreter Features zurück, dieser aber ruft type auf, was die *Logik* einer Klasse implementiert. Würde man nun direkt mit type() Klassen erstellen, wäre die Logik von Klassen unmittelbar ins Programm verstrickt. Klassen sich auch nur ein Feature Objekt-Orientierter Sprachen.
Nein. Wie du selbst sagst, braucht es keinen syntaktischen Zucker, und somit kann man in C genauso objektorientiert programmieren wie irgendwo anders. Scheme selbst ist gar nicht objektorientiert, hat aber 7 verschiedene Objektsysteme, Common Lisp hat hingegen eines, dass von den Möglichkeiten die es bietet seinesgleichen sucht. Aber das ist alles nur Syntax, semantisch lässt sich das wie in C mit Structs und Funktionen nachbauen.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Montag 10. November 2008, 21:14

Nein. Wie du selbst sagst, braucht es keinen syntaktischen Zucker, und somit kann man in C genauso objektorientiert programmieren wie irgendwo anders. Scheme selbst ist gar nicht objektorientiert, hat aber 7 verschiedene Objektsysteme, Common Lisp hat hingegen eines, dass von den Möglichkeiten die es bietet seinesgleichen sucht. Aber das ist alles nur Syntax, semantisch lässt sich das wie in C mit Structs und Funktionen nachbauen.
Das man das kann, ist mir natürlich klar. Mir ging es aber um die Unterstützung (zb durch syntactic sugar) direkt durch Sprachen.

Im Vergleich mit Basic: Ich weiß zwar nicht, ob es in BASIC Funktionen gab (konnte auf die Schnelle nichts ähnliches finden), aber die könnte man mittels goto und einer globalen Variablen als return Variable auch emulieren.

Mir ging es mehr um den Vergleich beim "Hello Word" mit Java, da man hier ja gezwungen ist, pseudoobjectorientiert zu arbeiten :D
BlackJack

Dienstag 11. November 2008, 09:05

@str1442: Man kann nicht alle Python-Anweisungen durch "statisches Ersetzen" beseitigen. ``for``/``while``-Schleifen deren Wiederholungen nicht statisch ermittelt werden können und ``if``/``else``, sowie Ausnahmebehandlung müssen mindestens erhalten bleiben, sonst kann man keine brauchbaren Programme schreiben. Die kann man letztendlich unter dem Begriff "bedingte Sprunganweisungen" zusammenfassen. Und das ist *Programmlogik* und nicht irgend etwas was nichts mit dem Programm sondern nur der Sprache zu tun hat.

Bei Assembler hängst Du mich komplett ab. Versuch mal ein Assemblerprogramm ohne Anweisungen zu schreiben. Das geht gar nicht, also kann man sie nicht von der Programmlogik trennen.

Und ich für meinen Teil giesse meine Programme auch in Sprach-Features. Was mir eine Sprache an Werkzeugen bietet, beeinflusst deutlich *wie* ich die Logik strukturiere und umsetze.

Mit `goto` alleine kannst Du kein `if` und keine Schleifen-Anweisungen vermeiden, Du brauchst mindestens eine bedingte Sprunganweisung. Ich weiss aber auch nicht was es jetzt bringt eine Anweisung durch eine andere zu ersetzen. Denn `goto` ist in BASIC eine Anweisung. In Python kann man `goto` übrigens mit Ausnahmen nachbauen (http://entrian.com/goto/) und es sieht fast aus wie eine Anweisung. :-)

Deine Aufteilung in Python von Funktionen -> Programmlogik und Anweisung -> Sprachtätigkeiten die mit der Programmlogik nichts zu tun haben, hält bei bedingten Sprüngen nicht Stand. Auf der anderen Seite kann man die Polymorphie bei Methodenaufrufen auch als bedingte Sprünge ansehen ohne das eine Anweisung verwendet wird.

Io halte ich vom Sprachentwurf konsequenter als Python, weil er wesentlich einfacher ist. Die Syntax ist viel simpler gestrickt. Man hat Objekte, kann denen Nachrichten schicken und bekommt Objekte als Anworten, denen man wieder Nachrichten schicken kann usw. (Die API bei den Objekten in der Io-Standardbibliothek ist allerdings IMHO teilweise nicht so besonders gelungen.)

Wo fangen bei Dir Sprachinterna an und was ist direkter Zugriff darauf? Ob ich nun eine ``raise``-Anweisung verwende oder eine `raise`-Methode ist doch egal, ich habe in beiden Fällen nichts mit der Implementierung zu tun, die ein Sprachentwickler geschrieben hat. Und wenn einem eine Sprache ein Objekt in einer Methode zur Verfügung stellt, was den Aufruf selbst darstellt und zum Beispiel den Sender und den Empfänger kennt, ist das kein Sprachinterna mehr, sondern ganz normale API. Zum Beispiel habe ich nicht das Gefühl, dass ich im Stack-Beispiel tief in der Sprache rumwühle, wenn ich in `Stack with` nicht `Stack` klone, sondern den Empfänger der Nachricht. Das ist ja sozusagen die Klasse und in Python hätte ich an der Stelle auch eine `classmethod()` geschrieben.

Python versperrt einem gewisse Dinge durchaus. Guido wollte keinen ternären Operator in der Sprache haben, hat dann aber schliesslich aufgegeben, weil er gesehen hat, was sich die Leute für hässliche Alternativen basteln. Oder die ``with``-Anweisung: Wurde eingeführt, weil kaum jemand konsequent saubere Datei-Behandlung geschrieben hat, weil das Äquivalent zu ``with`` "Boilerplate"-Code ist, den man nicht so einfach selbst in einer Funktion verschwinden lassen kann. Beides könnte man sich in Io, Lisp, oder Scheme selber schreiben.
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Dienstag 11. November 2008, 21:38

@str1442: Man kann nicht alle Python-Anweisungen durch "statisches Ersetzen" beseitigen. ``for``/``while``-Schleifen deren Wiederholungen nicht statisch ermittelt werden können und ``if``/``else``, sowie Ausnahmebehandlung müssen mindestens erhalten bleiben, sonst kann man keine brauchbaren Programme schreiben. Die kann man letztendlich unter dem Begriff "bedingte Sprunganweisungen" zusammenfassen. Und das ist *Programmlogik* und nicht irgend etwas was nichts mit dem Programm sondern nur der Sprache zu tun hat.
Bei Assembler hängst Du mich komplett ab. Versuch mal ein Assemblerprogramm ohne Anweisungen zu schreiben. Das geht gar nicht, also kann man sie nicht von der Programmlogik trennen.
Das hab ich ja auch nie gesagt. Ich habe Assembler nur als Beispiel herangezogen, weil man dort eben außer einfachen Anweisungen gar nichts machen kann. Zumindest manuell. Sollte illustrieren, das Funktionen genauso wie Klassen nicht since creation vorhanden waren. Damit wollte ich klar machen, das eine Funktion ein eigenes Unterprogramm ist, und es sehr wohl von der Sprache abhängt, ob man überhaupt eine Funktion nutzen kann. Ohne Anweisungen (und damit Assembler-"Sprachfunktionen"; ich sag gleich was dazu) keine Funktionen, was ist also bei so einer Sprache die Funktion und wie lässt sie sich von einer "Anweisung" (was auch immer Sprache xyz sich genau dadrunter vorstellt) unterscheiden?

Zu Assembler selber: Wie du selbst sagst, greift Assembler direkt auf die "Hardwareebene" zu. Im Grunde ist Assembler eine Art Wrapper für die Hardwarefunktionen. Alle Anweisungen in Assembler greifen also direkt auf die Hardware und deren Funktionen zu, und abstrahieren somit mittels Anweisungen etwas von der Hardware.

Man stelle sich diesen Ansatz ein wenig erweitert vor: Angenommen, die Hardware könnte If Anweisungen sowie while / for Anweisungen ausführen. Mittels Anweisungen verschafft man sich einen Zugriff dadrauf. Genauso verstünde die Hardware das Konzept der Funktion.

Jetzt würde man Anweisungen für Hardwaretätigkeiten nutzen. Logik selber aber wären Funktionen, die mittels Anweisungen ja wieder implementiert werden. Diese Funktionen werden wiederrum in anderen Funktionen genutzt usw.

Würde man nun versuchen, zum Zweck der Generalisierung, diese Anweisungen selber in Funktionen zu legen, würde man intern ein Feature der Hardware (nämlich Funktionen) benutzen, um auf ein anderes zuzugreifen. Verletzt das nicht ziemlich stark die Abstraktion? Grade bei so generellen Dingen wie if, for, while?

Wenn Funktionen also in jeder Sprache (die das Funktionskonzept beherrscht) aus anderen Funktionen bestehen, bis man schließlich auf der niedrigsten Ebene irgendwenn ankommt (oder, wie Python es tut, andere Sprachen nutzt), so sind diese Funktion pure Logik *wenn* die genutzten Anweisungen (als Sprach "Features") nicht selber durch diese Logik dargestellt werden (als Funktionen). Wenn das der Fall ist, mag das nicht sonderlich tragisch sein, aber dann sind Sprache und Logik miteinander verbunden.
Ich weiss aber auch nicht was es jetzt bringt eine Anweisung durch eine andere zu ersetzen.
Pratisch (bis auf eventuell klarere Struktur usw) nichts. Weil man ja die Logik aquivalent bleibt (im Optimalfall). Theoretisch aber nutzt man damit ein Feature der Sprache, kann aber die Logik noch aquivalent implementieren. Diese ist ja selbst nichts weiter als in eine Struktur gequetschte Sequenz von Anweisungen, und die Struktur kann man austauschen, wie ich auch zuerst Milch in den Kaffee wie umgedreht schütten kann. Bestenfalls kommts aufs gleiche raus. Funktionen sind teil dieser Struktur. Würde ich diese Zugriffe auf Sprach Features (darauf läuft ja schlussendlich alles hinaus; und eine Ebene tiefer eben Zugriffe auf Hardware Funktionen, verschiebe in Register A, etc...) in Funktionen einlegen, ziehe ich mich doch praktisch "mit der eigenen Hand aus dem Sumpf".

Dagegen kann man es auch wie Io machen. Unterschied: Das geht nur auf "High-Level Ebene". Wie bei dem Beispiel mit dem Hardwarezugriff auf If und co kann man Sprachen erst ab einer bestimmten Ebene so gestalten, das es Exception Objekte gibt. Vermutlich, weil sie intern selbst auf eine andere Sprache und deren Anweisungen zurück greift, wenn ich zb eine Nachricht an ein Exception Objekt schicke. Ist aber nur meine Vermutung, die sich grade spontan ergeben hat.

Trotzdem kann man es auf die eine oder andere Art und Weise machen, was auch völlig ok ist. Anweisungen sind in "höheren" Sprachen natürlich etwas einschränkend und aus dem Design fallend, andererseits grenzen sie die Sprache von der Logik ab.
In Python kann man `goto` übrigens mit Ausnahmen nachbauen (http://entrian.com/goto/) und es sieht fast aus wie eine Anweisung. Smile
:o . Hab es mir mal angeschaut, ist aber nicht mit Ausnahmen gebaut (was mich auch gewundert hätte; man kann ja wirklich einfach "goto" wie ein Statement nutzen), sondern indem es direkt auf stdout zugreift und auf "goto" als String reagiert. Empfinde ich als unsauber; aber goto hat es auch nicht besser verdient :evil:
Deine Aufteilung in Python von Funktionen -> Programmlogik und Anweisung -> Sprachtätigkeiten die mit der Programmlogik nichts zu tun haben, hält bei bedingten Sprüngen nicht Stand.
Ja, man kann nicht ohne Anweisungen arbeiten. Insofern haben Anweisungen etwas mit der Logik zu tun, ja. Aber wie ich schon sagte: Es sind Sprach Tätigkeiten. Sie sind die "einfachste" Ebene, und if (+ den rest den man für einen if vergleich braucht) alleine vermag auch keine Wunder zu vollbringen. Insgesamt aber stellt die Sprache die Struktur für Logik bereit; die Logik wiederrum baut sich aus sich selber auf, also anderen Objekten / Funktionen / whatever. Auf der unteresten Ebene stehen Statements (und Speichermöglichkeiten). Insofern hatte ich Unrecht (bzw mich unklar ausgedrückt). Aber: Das wichtige ist, das all das nur Struktur ist, die die Sprache bereitstellt. Die eigentliche Logik sind ja pure Gedanken, um es so zu sagen. Diese bauen sich aus den einfachsten Dingen auf. Diese Dinge sollten aber klar austauschbar sein; manchmal auch nicht vorhanden. All das fügt sich dann in eine Struktur ein, die die Sprache definiert (Funktionen, ...). Aber man könnte theoretisch das ganze Gestrüpp auch in zb BASIC oder Assembler schreiben. Füge ich nun dinge wie "for" direkt als Funktion ein, zwänge ich die einfachste Logik (die keine Gedankenlogik ist; sondern eben "Boolsche-Logik" - einfachste Dinge eben) in diese Sprach Struktur (Funktion) rein, ist das bei höheren Sprachen erstmal in Ordnung. In dem Moment verbinde ich aber die Logik mit der tieferliegenden, einfachen Logik, indem ich die einfache Logik ja selber wieder als Gedankenlogik sehe, die in diese Sprachstruktur reingepresst gehört.

Wo fangen bei Dir Sprachinterna an und was ist direkter Zugriff darauf?
Alles, was in die Programmlogik der Sprache selber gehört, ist ein Sprachinterna. Alles, was kein Sprachinterna ist, ist folglich in die von der Sprache festgelegte Struktur eingebettet.
Ob ich nun eine ``raise``-Anweisung verwende oder eine `raise`-Methode ist doch egal, ich habe in beiden Fällen nichts mit der Implementierung zu tun, die ein Sprachentwickler geschrieben hat.
Ist ja auch so, ist nur ein anderer Herangehensweg.
Ich sehe auch grade einen Denkfehler von mir: ein Exception Objekt ist in der eigenen Sprache mindestens dargestellt, beugt sich also der Struktur der Sprache. Insofern ist eine Methode darauf auch ein gangbarer Weg. Aber irgendwo ist auch da die Grenze zwischen Sprachen-Logik und deren Struktur, sowie der von ihr für die neue, in ihr geschrieben Sprache, definierte Struktur, in die man dann "Gedankenlogik" einbettet. Je weiter man sich öffnet, desto näher kommt man dadran heran.

Entweder man bricht mit der Struktur seiner Sprache oder man versucht, fundamentale Dinge dieser Struktur in die eigene Struktur hineinbringen. Was ja auch mittels Abstraktion funktioniert, wodurch man halbwegs oberflächlich auf der Api der Sprache operieren kann... Ich glaube, ich habe die ganze Zeit von zwei verschiedenen Dingen gesprochen :?

Nun, dennoch, irgendwo ist der Punkt, an dem sich eine Sprache "selber auf dem Sumpf ziehen müsste", würde man Anweisungen in die Sprachstruktur reinzwängen. Je tiefer (näher an Hardware Ebene) man geht, desto näher ist man diesem Punkt.



Ich habe das nun alles so aufgeschrieben, wie es mir in die Gedanken kam. Da ich aktuell aber erstmal genug habe, poste ichs so.

EDIT:

Wo ich grade bei Hardware und deren Funktionen war: Gibts da eigentlich eine ganz gute Beschreibung zu, wie das in etwa funktioniert? Also nicht das Nullen und Einsen für An / Aus zeug, sondern wie konkret spricht Assembler die Hardware an? Und wie wird in Maschinencode eine Endlosschleife dargestellt? Würde mich interessieren.
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Mittwoch 12. November 2008, 09:33

str1442 hat geschrieben:Wo ich grade bei Hardware und deren Funktionen war: Gibts da eigentlich eine ganz gute Beschreibung zu, wie das in etwa funktioniert? Also nicht das Nullen und Einsen für An / Aus zeug, sondern wie konkret spricht Assembler die Hardware an? Und wie wird in Maschinencode eine Endlosschleife dargestellt? Würde mich interessieren.
Assembler spricht Hardware gar nicht an. Assembler wird zu Maschienencode assembliert und dieser wird von der jeweiligen CPU interpretiert. Du kannst dir dazu das Intel Architecture Software Developer’s Manual, Volume 2: Instruction Set Reference ansehen. Dort siehts du die Klasse der Sprungbefehle Jcc, die an die angegebene Adresse springen (natürlich steht nicht Jcc im Machienencode sondern die Binäre Darstellung des Befehles, die du ebenfalls in den Dokument nachschlagen kannst). Dazu setzen sie den Instruction Pointer (bei x86 wäre der in EIP) auf die Adresse und beim nächsten Maschienenbefehlszyklus wird von dort der nächste Befehl gelesen und ausgeführt. Alternativ wenn du keine Bedingung zum Prüfen hast, kannst du auch direkt JMP verwenden. Sieht in Pseudo-Code etwa so aus:

Code: Alles auswählen

0 ADD EAX,2
1 JMP 0
Du musst dann nachschauen was dass für ein ADD-Typ ist, und was für ein JMP-Typ und kannst dann das Programm quasi von Hand Assemblieren.

Edit: hier das in x86-Assembler (NASM):

Code: Alles auswählen

    section .text
global  _start
_start:
        add     eax,2
        jmp     _start
Wobei _start dann der Adresse von 0 entspricht.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Mittwoch 12. November 2008, 10:39

Bin über diese Aussage gestolpert ohne jetzt im Detail zu prüfen, ob sie danach nochmals diskutiert wurde:
Wenn Du es auf das möglichst wenig beschränken willst (ohne bei Brainfuck zu landen), braucht man so etwas wie Funktionsaufrufe und eine bedingte Auswertung. Alles andere ist syntaktischer Zucker.
Betrachtet man Funktionen als Objekte und Funktionsaufrufe als Nachrichten an Objekte, die Methoden ausführen, braucht man nichts weiter, wie ich am Beispiel von Smalltalks bedingten "Anweisungen" gezeigt hatte. Für Schleifen braucht man entweder Rekursion oder eine spezielle Nachricht `_Restart` wie es Self machte.

Betrachtet man die Welt funktional, braucht man ebenfalls keine bedingten Ausdrücke. Funktionen höherer Ordnung (für Bedingungen) und Rekursion (für Schleifen) reichen aus.

Code: Alles auswählen

# Mir egal, dass ich Python-Schlüsselworte benutze
def if(cond, then, else):
    return cond(then, else)

def true(then, else):
    return then()

def false(then, else):
    return else()

# Annahme: 3 < 4 liefert entweder true oder false
# Das 1/0 löse eine Ausnahme aus, wenn der Ausdruck ausgewertet wird
if(3 < 4, lambda:1, lamda:1/0)
Ich finde Io als Beispiel übrigens nicht so gelungen, weil die Sprache eine sehr merkwürdige "call by name" Auswertung ihrer Argumente durchführt, um Dinge wie "if" in der Sprache zu definieren. Das erscheint mir von der Theorie her unsauber und ist schwer und nur mit Implementationsdetails zu beschreiben. Der hier gezeigte funktionale Ansatz, den man etwa in der kaum bekannten Lehrsprache Pico wiederfindet, ist IMHO eleganter. Self, wovon Io abgeschaut ist, finde ich auch eleganter, allerdings ist auch hier der Kern nicht so einfach wie das funktionale Beispiel.

Ob ein Prozessor übrigens Anweisungen oder Funktionen hat, ist eine interessante Definitionsfrage. Man kann jede Instruktion als eine Funktion verstehen, die den gesamten Prozessorzustand als Eingabe und einen neuen Zustand als Ausgabe hat. Dann ist es eine Funktion. Das scheint mir ein sinnvolles und nicht so realitätsfernes Modell zu sein. Es ist jedenfalls ganzheitlicher als die Idee, Zustand getrennt von Anweisungen, die daran jeweils inkrementelle Änderungen vornehmen, zu betrachten. Gerade, wenn man die massiv parallelen Vorgänge in modernen Prozessoren irgendwie verstehen will.

Statt dagegen zu argumentieren, ist es IMHO hilfreich, sich einfach mal der abstrakteren (und uniformeren) Sichtweise zu öffnen und Dinge unter diesem Gesichtspunkt zu betrachten. Es sind verschiedene Denkschulen, aber sie beeinflussen stark, wie man sich bestimmten Problemen nähert und erweitern den Horizont.

Stefan
BlackJack

Mittwoch 12. November 2008, 11:16

@sma: Die Aussage mit dem Beschränken war darauf bezogen, dass die ganzen Anweisungen in Python laut str1442 "Sprachtätigkeiten" sind und nichts mit der Programmlogik zu tun haben.

Dass Anweisungen grundsätzlich nicht notwendig sind, ist glaube ich allen Beteiligten klar.

Was meinst Du bei Io mit den Implementierungsdetails? Ich finde das lässt sich mit (verketteten) Nachrichten, die an Objekte geschickt werden und erst beim Empfänger ausgewertet werden (oder eben auch nicht) ganz gut beschreiben.

Die Frage Anweisung oder Funktion kann man überall so oder so beantworten. Python-Anweisungen wie ``def`` oder ``class`` kann man als Funktionen auffassen, bei denen implizit der gesamte Interpreterzustand reingeht und ein veränderter wieder heraus kommt. Siehe Monaden in Haskell. Alles ist Funktion, zur Not nehmen wir den Zustand des Universums vor dem Aufruf und nach dem Aufruf als Werte. ;-)

In der Regel werden Assembler-Befehle halt als Anweisungen beschrieben.
Antworten