heim - Internet-Setup
Was ist ein Funktionsname in der Programmierung? Programmierfunktionen

Ziel der Arbeit: 1) Studieren Sie die Regeln zur Beschreibung von Funktionen; 2) Kenntnisse im Umgang mit Funktionen beim Schreiben von Programmen in C++ erwerben.

Theoretische Informationen

Das Hauptmodul von Programmen in C++ ist die Funktion.

Funktion- ein logisch abgeschlossenes, eindeutig gestaltetes Fragment eines Programms, das einen Namen hat. Mithilfe von Funktionen können Sie große Rechenaufgaben in kleinere aufteilen.

Jedes C++-Programm enthält notwendigerweise eine Funktion namens main, die den Hauptteil des Programms darstellt. Für alle anderen Funktionen sollten, sofern im Programm vorhanden, Prototypen deklariert werden – schematische Notationen, die dem Compiler den Namen und die Form jeder Funktion im Programm mitteilen.

Die Syntax für einen Funktionsprototyp mit Parametern lautet:

return_value_type Funktionsname (parameter_list_with_type_indication);

Funktionen in C++ sind Standardfunktionen (Bibliothek) und vom Benutzer programmierbar.

Standartfunktionen

Beschreibungen von Standardfunktionen finden Sie in Dateien, die über die Direktive #include in das Programm eingebunden werden. Solche Dateien werden Header-Dateien genannt; sie haben die Erweiterung h.

Die Bezugnahme auf einen Funktionsnamen im Hauptprogramm wird als Funktionsaufruf bezeichnet.

Der Aufruf von Funktionen führt zur Ausführung einer Aktion oder zur Berechnung eines Werts, der dann im Programm verwendet wird.

y = sin(x); //Sinusberechnungsfunktion

Funktionsdefinition

Im Allgemeinen sind Funktionen wie folgt definiert:

return_value_type Funktionsname (Typparametername,..., Typparametername)

body_function

Programmierbare Funktionen

Funktionen, die der Programmierer selbst erstellt, vereinfachen das Schreiben von Programmen, weil sie:

    helfen, wiederholtes Programmieren zu vermeiden, da dieselbe Funktion in verschiedenen Programmen verwendet werden kann;

    Erhöhen Sie den Grad der Modularität des Programms und erleichtern Sie so das Lesen, das Vornehmen von Änderungen und das Korrigieren von Fehlern.

Beispiel9 .1. Erstellen wir eine Funktion, die 65 „*“-Zeichen hintereinander ausgibt. Damit diese Funktion in einem bestimmten Kontext funktioniert, ist sie im Programm zum Drucken von Briefbögen enthalten. Das Programm besteht aus den Funktionen main() und stars().

// Briefkopf

#enthalten

const int Limit=65;

leere Sterne(leer); // Funktionsprototyp stars()

cout<<"Moscow Institute of Electronic Engineering"<

// Definition der Funktion stars()

für (count=1; count<=Limit; count++)

Wir haben uns ein Beispiel einer einfachen Funktion angesehen, die keine Argumente hat und keine Werte zurückgibt.

Funktionsparameter

Schauen wir uns ein Beispiel für die Verwendung von Funktionsparametern an.

Beispiel9. 2. Schreiben wir die Funktion space(), dessen Argument die Anzahl der Leerzeichen ist, die diese Funktion drucken soll.

#Adresse „Selenograd“ definieren

#Name definieren „Moscow Institute Electronic Engineering“

#define Abteilung „Informatik und Programmierung“

const int LIMIT=65;

#enthalten

Leerraum (int Zahl);

cout<

Leerzeichen=(LIMIT - strlen(name))/2; // Berechnen Sie, wie viele

// brauche Leerzeichen

cout<

space((LIMIT - strlen(department))/2); // Argument – ​​Ausdruck

cout<

//Definition der Funktion stars()

für (count=1; count<=LIMIT; count++)

//Definition der Funktion space()

Leerzeichen (int Zahl)

für (count=1; count<=number; count++)

Die Zahlenvariable wird als formales Argument bezeichnet. Diese Variable nimmt beim Aufruf der Funktion den Wert des eigentlichen Arguments an. Mit anderen Worten, formales Argument ist eine Variable in der Definition des aufgerufenen Unterprogramms und eigentliches Argument ist der spezifische Wert, der dieser Variablen vom aufrufenden Programm zugewiesen wird.

Wenn eine Funktion mehr als ein Argument für die Kommunikation benötigt, können Sie zusammen mit dem Funktionsnamen eine durch Kommas getrennte Liste von Argumenten angeben:

void printnum(int i, int j)

( cout<<"Координаты точек”<< i << j <

Der Eingabewert der Funktion kann aufgrund der Anwesenheit verarbeitet werden Streit; Der Ausgabewert wird mit dem Schlüsselwort return zurückgegeben.

Programmierferne Anwender stoßen prinzipiell selten auf die Konzepte von Funktionen und Prozeduren und werden mit etwas Mathematischem und Bürokratisch-Medizinischem in Verbindung gebracht. In der Programmierung arbeiten viele Sprachen mit diesen Konzepten, allerdings können selbst Experten manchmal den Unterschied zwischen einer Funktion und einer Prozedur nicht klar verstehen. Wie bei diesem Gopher: Er ist da, aber niemand sieht ihn. Mal sehen, ob die Unterschiede so unsichtbar sind.

Was bedeuten die Begriffe Funktion und Verfahren?

  • Funktion Beim Programmieren wird ein Unterprogramm so oft von anderen Unterprogrammen aufgerufen, wie erforderlich.
  • Verfahren- ein benannter Teil des Programms (Unterprogramm), der von nachfolgenden Teilen des Programms in der erforderlichen Anzahl wiederholt aufgerufen wird.

Vergleich von Funktion und Verfahren

Der Hauptunterschied zwischen einer Funktion und einer Prozedur ist das Ergebnis, das sie zurückgibt. Tatsächlich sind sowohl Funktionen als auch Prozeduren logisch unteilbare Blöcke, aus denen der Programmcode besteht. Eine Funktion gibt einen Wert zurück, eine Prozedur in den meisten Programmiersprachen nicht oder (z. B. in C) einen leeren Wert. Im letzteren Fall (in C) wird eine Prozedur als untergeordnete Version einer Funktion betrachtet.

Der Funktionsheader enthält das Wort „Funktion“, einen Bezeichner (den eigentlichen Namen der Funktion), optional eine Liste von Parametern und unbedingt den Typ des Ergebnisses. Der Funktionskörper muss einen Operator enthalten, der dem Funktionsnamen einen Wert zuweist, den er als Ergebnis zurückgibt. Der Prozedurkopf enthält das Wort „Prozedur“, eine Kennung (Prozedurname) und optional eine Liste von Parametern.

Ein Funktionsaufruf wird als Teil von Ausdrücken ausgeführt, bei denen diese Ausdrücke verwendet werden; ein Prozeduraufruf erfordert eine separate Anweisung.

Eine Prozedur wird nur über ihren Namen aufgerufen, während der Name einer Funktion mit ihrem Wert verknüpft ist. In Algorithmusdiagrammen wird ein Funktionsaufruf in einem Ausgabeblock oder in einem Prozessblock dargestellt, ein Prozeduraufruf wird in einem speziellen „vordefinierten Prozess“-Block dargestellt.

Der Unterschied zwischen einer Funktion und einer Prozedur in der Programmierung ist wie folgt:

  • Eine Funktion gibt einen Wert zurück, eine Prozedur nicht.
  • Der Funktionsheader muss den Ergebnistyp enthalten.
  • Der Funktionskörper muss eine Anweisung enthalten, die dem Funktionsnamen einen Wert zuweist.
  • Der Aufruf einer Prozedur erfordert eine separate Anweisung; der Aufruf einer Funktion ist als Teil eines Ausdrucks möglich.
  • Der Prozedurname wird benötigt, um sie aufzurufen, der Funktionsname wird benötigt, um einen Wert zuzuweisen.
  • In Algorithmusdiagrammen wird ein Prozeduraufruf in einem separaten Block dargestellt, ein Funktionsaufruf in einem Prozess- oder Ausgabeblock.

Die Grundlage jedes Computerprogramms sind Algorithmen, ausgedrückt als Befehle. Die Person, die den Code schreibt, sagt: Nehmen Sie dies, machen Sie dies, dies und das und geben Sie dann das Ergebnis dort aus und ruhen Sie sich aus. Damit Befehle in Programmen nicht zu einem Durcheinander verschmelzen und miteinander interagieren können, werden sie in sogenannte Funktionen und Prozeduren gruppiert. Wir werden uns mit diesen Konzepten vertraut machen.

Was ist eine Funktion?

Funktionsnamen werden verwendet: 1) zur Erstellung von Dokumentationen; 2) für eine API, also eine Schnittstelle zur Verbindung mit einem Programm oder einem gesamten Betriebssystem einer beliebigen Anwendung. Daher ist es sinnvoll, noch einmal daran zu erinnern, dass diese Namen verständlich und möglichst passend zu den durchgeführten Handlungen vergeben werden sollten.

Fassen wir zusammen

Funktionen sind also eine Art Container zum Gruppieren von Algorithmen. Sie:

  1. verantwortlich für bestimmte Aufgaben;
  2. mit anderen Objekten interagieren;
  3. sind die konzeptionelle Grundlage moderner Programmierung, so pathetisch sie auch klingen mag.

Prozeduren sind eigentlich dieselben Funktionen, allerdings „leere“, die nichts zurückgeben (das ist ihr Hauptunterschied). Hierbei handelt es sich um Hilfswerkzeuge zur Durchführung routinemäßiger Tätigkeiten sowie zur Einsparung von Platz, Aufwand und Zeit.

Frühere Veröffentlichungen:

Nicht umsonst habe ich diesen Artikel „Funktionen als integraler Bestandteil der Programmierung“ genannt, denn ohne sie hat meiner Meinung nach keine Sprache das Recht zu existieren. Was ist es? Eine Funktion ist die Hauptkomponente eines gut geschriebenen Programms. Es erleichtert nicht nur das Lesen von Code, sondern verändert auch die Idee der strukturierten Programmierung radikal. Mit Hilfe von Funktionen können Sie einzelne Programmteile wiederverwenden, indem Sie ihnen beliebige Parameter übergeben. Ohne dieses Wunder des Programmierelements ist kein seriöses Programm vorstellbar.

Ich erzähle Ihnen kurz, wie es funktioniert. Eine Funktion ist ein Befehlsblock, den Ihr Programm aufrufen kann. Wenn auf den Header dieses Blocks (Funktionsname) zugegriffen wird, wird er ausgeführt und führt einige vom Programmierer festgelegte Aktionen aus. Danach gibt dieser Block den empfangenen Wert zurück und übergibt ihn an das Hauptprogramm. Lassen Sie es mich in der Praxis erklären.

Grob gesagt sieht es so aus. Lassen Sie es mich kurz erklären. Wir erstellen eine Variable und weisen ihr das Ergebnis der Ausführung der Funktion myfunc zu, die wiederum den Wert der Quadrierung einer Zahl berechnet. Funktionen werden nicht sofort beim Programmstart ausgeführt, sondern erst beim Aufruf. Es ist vielleicht etwas verwirrend, aber so ist es.

Wie rufe ich eine Funktion auf?

Um eine Funktion aufzurufen, müssen Sie sie erstellen. Obwohl es auch integrierte Funktionen gibt. Zum Beispiel dies: cos, sin, md5, count, abs usw. Um sie aufzurufen, müssen Sie der Variablen lediglich den gewünschten Wert zuweisen.

Ein Funktionsargument ist der Wert, den Sie ihm beim Aufruf übergeben. Funktionsargumente werden in Klammern gesetzt. Beim Erstellen einer Funktion geben Sie bedingte Namen für die Argumente an. Dann können diese Namen im Hauptteil der Funktion als lokale Variablen verwendet werden. Kehren wir zu den Funktionen zurück, die der Benutzer selbst erstellt. Das geht ganz einfach. Zunächst wird der Funktionskörper erstellt:

Funktion hello() ( echo „Hello, world!“; )

Dann rufen wir sie an. Wenn es außerdem keine Parameter hat, setzen wir einfach Klammern. Um diese Funktion aufzurufen, verwenden wir nur die Zeile: Hallo();. Jede Funktion kann auch mithilfe eines reservierten Worts einen Wert zurückgeben zurückkehren. Diese Anweisung stoppt die Ausführung der Funktion und sendet den Rückgabewert an das aufrufende Programm. Funktion sum($first, $second) ($r=$first + $second; return $r;) echo sum(2,5); Das Ergebnis der Programmausführung ist gleich 7. Lokale und globale Variablen

Wie in jeder anderen Programmiersprache gibt es Variablen, die nur innerhalb einer Funktion verfügbar sind, und Variablen, die im Code des Programms selbst verfügbar sind. Solche Variablen werden lokal bzw. global genannt. Innerhalb einer Funktion können Sie nicht einfach auf eine Variable zugreifen, die außerhalb der Funktion erstellt wurde. Wenn Sie dies versuchen, erstellen Sie eine neue Variable mit demselben Namen, die jedoch lokal für diese Funktion ist.

$per="Dima"; function primer() // Führt Folgendes aus: Anzeige einer lokalen Variablen ( echo "My name is ".$per; ) echo primer();

In diesem Fall erscheint auf dem Bildschirm der Satz „Mein Name ist“. Das bedeutet, dass die Variable $per innerhalb der Primer-Funktion erstellt wurde und ihr standardmäßig ein Nullwert zugewiesen wurde. Um solche Pfosten zu vermeiden, müssen Sie den Operator verwenden global. Korrigieren wir den obigen Code entsprechend:

$per="Dima"; function primer() // Führt aus: Anzeige einer globalen Variablen ( global $per; echo "My name is ".$per; ) echo primer();

Jetzt sollte alles gut sein – Problem gelöst. Vergessen Sie jedoch nicht, dass, wenn eine Funktion den Wert einer externen Variablen ändert, sich eine solche Änderung auf das gesamte Programm auswirkt. Sie müssen diesen Operator also sorgfältig verwenden!

Funktionen von zwei oder mehr Argumenten

Einige der an eine Funktion übergebenen Argumente können optional gemacht werden, wodurch die Funktion weniger anspruchsvoll wird. Das folgende Beispiel verdeutlicht dies deutlich:

… Funktion Schriftart($text, $size=5) // Führen Sie Folgendes aus: Geben Sie die Schriftgröße aus ( echo " „.$text.“"; ) Schriftart("Hallo
",1); Schriftart("Hallo
",2); Schriftart("Hallo
",3); Schriftart("Hallo
",4); Schriftart("Hallo
",5); Schriftart("Hallo
",6); Schriftart("Hallo
");

Standardmäßig ist die Schriftgröße 5. Wenn wir den zweiten Parameter der Funktion weglassen, entspricht er diesem Wert.

Abschluss

Bevor ich mich verabschiede, möchte ich Sie auf einen Ratschlag aufmerksam machen. Es besteht darin, alle von Ihnen geschriebenen Funktionen in einer Datei zusammenzufassen (z. B. function.php). Und dann müssen Sie in der Datei, in der Sie die Funktion aufrufen müssen, nur noch function.php einbinden und schon ist alles einsatzbereit. Dadurch wird es viel einfacher, die Logik in Ihrem Programm zu verstehen. Um eine Verbindung herzustellen, verwenden Sie:

include_once("function.php");

require_once("function.php");

Wenn Sie den Kern des in diesem Artikel behandelten Problems verstehen, bin ich sicher, dass Sie die Funktionen in Ihren Programmen problemlos nutzen können. Dies dient wiederum dazu, sie anpassungsfähiger und wiederverwendbar zu machen.

Dies ist der dritte Artikel in der Reihe „Kategorientheorie für Programmierer“.

Wer braucht Typen?

In der Community herrscht Uneinigkeit über die Vorteile von statischem gegenüber dynamischem Tippen und starkem gegenüber schwachem Tippen. Lassen Sie mich die Tippauswahl mit einem Gedankenexperiment veranschaulichen. Stellen Sie sich Millionen von Affen mit Tastaturen vor, die voller Freude zufällige Tasten drücken, Programme schreiben, kompilieren und ausführen.

Mit Maschinensprache wird jede von den Affen erzeugte Kombination von Bytes akzeptiert und ausgeführt. In höheren Sprachen ist es jedoch sehr wichtig, dass der Compiler in der Lage ist, lexikalische und grammatikalische Fehler zu erkennen. Viele Programme werden einfach abgelehnt und die Affen werden keine Bananen mehr haben, aber der Rest wird eine bessere Chance haben, sinnvoll zu sein. Die Typprüfung stellt eine weitere Barriere gegen unsinnige Programme dar. Während außerdem in dynamisch typisierten Sprachen Typkonflikte erst zur Laufzeit erkannt werden, werden Typkonflikte in stark typisierten, statisch überprüften Sprachen während der Kompilierungszeit erkannt, wodurch viele fehlerhafte Programme aussortiert werden, bevor sie ausgeführt werden können.

Die Frage ist also: Wollen wir, dass Affen glücklich sind oder richtige Programme entwickeln?
(Anmerkung des Übersetzers: Kein Grund, beleidigt zu sein, der Autor mag einfach weniger langweilige Metaphern als RNG und „zufällige Bytefolgen“ und bezeichnet Programmierer nicht als Affen.)

Typischerweise besteht das Ziel des Gedankenexperimentes mit dem Tippaffen darin, das Gesamtwerk Shakespeares zu schaffen (Anmerkung des Übersetzers: oder Tolstois Krieg und Frieden). Wenn Sie Rechtschreibung und Grammatik in einer Schleife überprüfen, erhöhen sich Ihre Erfolgschancen erheblich. Das Analogon zur Typprüfung geht sogar noch weiter: Nachdem Romeo zum Menschen erklärt wurde, stellt die Typprüfung sicher, dass ihm keine Blätter wachsen und er mit seinem starken Gravitationsfeld keine Photonen einfängt.

Für die Zusammensetzbarkeit werden Typen benötigt

Die Kategorientheorie untersucht die Zusammensetzung von Pfeilen. Es können nicht einfach zwei beliebige Pfeile kombiniert werden: Das Zielobjekt eines Pfeils muss mit dem Quellobjekt des nächsten übereinstimmen. Beim Programmieren übergeben wir Ergebnisse von einer Funktion an eine andere. Das Programm funktioniert nicht, wenn die zweite Funktion die von der ersten Funktion erhaltenen Daten nicht richtig interpretieren kann. Damit ihre Zusammensetzung funktioniert, müssen beide Funktionen zusammenpassen. Je stärker das Typsystem der Sprache ist, desto besser kann diese Anpassung beschrieben und automatisch überprüft werden.

Das einzige ernsthafte Argument, das ich gegen die starke statische Typisierung höre, ist, dass sie möglicherweise einige Programme ablehnt, die semantisch korrekt sind. In der Praxis kommt dies äußerst selten vor (Anmerkung des Übersetzers: Um Verwirrung zu vermeiden, stelle ich fest, dass der Autor nicht berücksichtigt hat oder nicht damit einverstanden ist, dass es viele Stile gibt und dass Duck-Typing, das Programmierern in Skriptsprachen vertraut ist, auch ein Recht auf Leben hat . Andererseits ist Duck-Typing in einem strengen Typsystem durch Vorlagen, Merkmale, Typklassen und Schnittstellen möglich. Es gibt viele Technologien, sodass die Meinung des Autors nicht als völlig falsch angesehen werden kann.) und auf jeden Fall enthält jede Sprache eine Art Hintertür, um das Typsystem zu umgehen, wenn es wirklich notwendig ist. Sogar Haskell hat unsicheren Zwang. Aber solche Designs müssen mit Bedacht eingesetzt werden. Franz Kafkas Figur Gregor Samsa durchbricht das Typensystem, als er sich in einen Riesenkäfer verwandelt, und wir alle wissen, wie das endet (Anmerkung des Übersetzers: schlecht :).

Ein weiteres Argument, das ich oft höre, ist, dass starkes Tippen den Programmierer zu sehr belastet. Ich kann dieses Problem nachvollziehen, da ich selbst mehrere Iteratordeklarationen in C++ geschrieben habe, mit der Ausnahme, dass es eine Technologie, Typinferenz, gibt, die es dem Compiler ermöglicht, die meisten Typen aus dem Kontext abzuleiten, in dem sie verwendet werden. In C++ können Sie eine Variable auto deklarieren und der Compiler leitet den Typ für Sie ab.

In Haskell sind Typanmerkungen, außer in seltenen Fällen, optional. Programmierer neigen ohnehin dazu, sie zu verwenden, da Typen viel über die Semantik Ihres Codes aussagen können und Typdeklarationen Ihnen helfen, Kompilierungsfehler zu verstehen. In Haskell ist es üblich, ein Projekt durch die Entwicklung von Typen zu starten. Später bilden Typanmerkungen die Grundlage der Implementierung und werden zu vom Compiler garantierten Kommentaren.

Starke statische Typisierung wird oft als Entschuldigung dafür verwendet, Code nicht zu testen. Manchmal hört man Haskell-Programmierer sagen: „Wenn der Code erstellt wird, ist er korrekt.“ Natürlich gibt es keine Garantie dafür, dass ein typrichtiges Programm korrekt ist und das richtige Ergebnis liefert. Aufgrund dieser Einstellung übertraf Haskell in einer Reihe von Studien andere Sprachen in der Codequalität nicht wesentlich, wie man erwarten könnte. Es scheint, dass in einem kommerziellen Umfeld die Notwendigkeit, Fehler zu beheben, nur bis zu einem bestimmten Qualitätsniveau besteht, das hauptsächlich mit der Wirtschaftlichkeit der Softwareentwicklung und der Toleranz des Endbenutzers zusammenhängt und sehr wenig mit der Programmiersprache oder -entwicklung zu tun hat Methodik. Ein besserer Maßstab wäre es, zu messen, wie viele Projekte hinter dem Zeitplan zurückliegen oder mit stark eingeschränkter Funktionalität geliefert werden.

Nun zu der Behauptung, dass Unit-Tests die starke Typisierung ersetzen können. Schauen wir uns eine gängige Refactoring-Praxis in stark typisierten Sprachen an: Ändern des Typs eines Arguments in eine Funktion. In stark typisierten Sprachen reicht es aus, die Deklaration dieser Funktion zu ändern und dann etwaige Build-Fehler zu beheben. In schwach typisierten Sprachen kann die Tatsache, dass eine Funktion nun andere Daten erwartet, nicht dem Aufrufer zugeschrieben werden.

Unit-Tests können einige der Inkonsistenzen aufdecken, aber Tests sind fast immer ein probabilistischer und kein deterministischer Prozess (Anmerkung des Übersetzers: Vielleicht meinten sie eine Reihe von Tests: Sie decken nicht alle möglichen Eingaben ab, sondern eine bestimmte repräsentative Stichprobe.) Tests sind ein schlechter Ersatz für den Nachweis der Richtigkeit.

Was sind Typen?

Die einfachste Beschreibung von Typen ist, dass es sich um Wertemengen handelt. Der Typ Bool (denken Sie daran, dass konkrete Typen in Haskell mit einem Großbuchstaben beginnen) entspricht einer Menge von zwei Elementen: True und False. Der Char-Typ ist die Menge aller Unicode-Zeichen, beispielsweise „a“ oder „ą“.

Mengen können endlich oder unendlich sein. Der String-Typ, der im Wesentlichen ein Synonym für die Char-Liste ist, ist ein Beispiel für eine unendliche Menge.

Wenn wir x als Ganzzahl deklarieren:
x::Integer
wir sagen, dass es ein Element der Menge der ganzen Zahlen ist. Integer ist in Haskell eine unendliche Menge und kann für Arithmetik beliebiger Genauigkeit verwendet werden. Es gibt auch eine endliche Menge von Int, die einem Maschinentyp entspricht, wie int in C++.

Es gibt einige Feinheiten, die die Gleichsetzung von Typen mit Mengen erschweren. Es gibt Probleme mit polymorphen Funktionen, die zyklische Definitionen haben, und auch mit der Tatsache, dass man nicht eine Menge aller Mengen haben kann; aber wie versprochen werde ich kein strenger Mathematiker sein. Wichtig ist, dass es eine Kategorie von Mengen namens Set gibt, mit der wir arbeiten werden.
In Set sind Objekte Mengen und Morphismen (Pfeile) Funktionen.

Sets sind eine besondere Kategorie, da wir in ihre Objekte hineinschauen können und dies uns dabei hilft, vieles intuitiv zu verstehen. Wir wissen zum Beispiel, dass die leere Menge keine Elemente enthält. Wir wissen, dass es spezielle Mengen eines Elements gibt. Wir wissen, dass Funktionen Elemente einer Menge Elementen einer anderen Menge zuordnen. Sie können zwei Elemente in eins abbilden, aber nicht ein Element in zwei. Wir wissen, dass die Identitätsfunktion jedes Element der Menge auf sich selbst abbildet und so weiter. Ich habe vor, all diese Informationen nach und nach zu vergessen und stattdessen alle diese Konzepte in rein kategorischer Form auszudrücken, das heißt in Form von Objekten und Pfeilen.

In einer idealen Welt könnten wir einfach sagen, dass Typen in Haskell Mengen sind und Funktionen in Haskell mathematische Funktionen dazwischen sind. Es gibt nur ein kleines Problem: Die Mathematikfunktion führt keinen Code aus – sie kennt nur die Antwort. Eine Funktion in Haskell muss die Antwort berechnen. Dies ist kein Problem, wenn die Antwort in einer endlichen Anzahl von Schritten erhalten werden kann, egal wie groß sie sind. Es gibt jedoch einige Berechnungen, die eine Rekursion erfordern und möglicherweise nie abgeschlossen werden. Wir können nicht terminierende Funktionen in Haskell nicht einfach verbieten, weil die Unterscheidung, ob eine Funktion terminiert oder nicht – das berühmte Halteproblem – unlösbar ist. Aus diesem Grund hatten Informatiker die brillante Idee oder einen schmutzigen Hack, je nach Sichtweise, jeden Typ um einen speziellen Wert namens „Bottom“ zu erweitern (Anmerkung des Übersetzers: Dieser Begriff (unten) klingt auf Russisch irgendwie dumm. Wenn jemand eine gute Option kennt, schlagen Sie sie bitte vor.), was mit _|_ oder in Unicode ⊥ bezeichnet wird. Dieser „Wert“ entspricht einer unvollständigen Berechnung. Also eine Funktion deklariert als:
f::Bool -> Bool
kann True, False oder _|_ zurückgeben; Letzteres bedeutet, dass die Funktion nie abgeschlossen wird.

Interessanterweise ist es, sobald man „bottom“ in das Typsystem akzeptiert, praktisch, jeden Laufzeitfehler als „bottom“ zu behandeln und einer Funktion sogar zu erlauben, „bottom“ explizit zurückzugeben. Letzteres geschieht üblicherweise mit dem undefinierten Ausdruck:
f::Bool -> Bool f x = undefiniert
Diese Definition besteht die Typprüfung, da undefiniert nach unten ausgewertet wird, was in allen Typen, einschließlich Bool, enthalten ist. Sie können sogar schreiben:
f::Bool -> Bool f = undefiniert
(ohne x), da unten auch ein Mitglied vom Typ Bool -> Bool ist.

Funktionen, die Bottom zurückgeben können, werden als partielle Funktionen bezeichnet, im Gegensatz zu regulären Funktionen, die gültige Ergebnisse für alle möglichen Argumente zurückgeben.

Aus diesem Grund heißt die Kategorie der Haskell-Typen und -Funktionen Hask und nicht Set. Aus theoretischer Sicht ist dies eine Quelle endloser Komplikationen, daher werde ich an dieser Stelle mein Metzgermesser verwenden und Schluss machen. Aus pragmatischer Sicht können Sie die nicht terminierenden Funktionen und den unteren Teil ignorieren und Hask so behandeln, als wäre es ein richtiges Set.

Warum brauchen wir ein mathematisches Modell?

Als Programmierer sind Sie mit der Syntax und Grammatik einer Programmiersprache vertraut. Diese Aspekte der Sprache werden normalerweise gleich zu Beginn der Sprachspezifikation formal beschrieben. Aber Bedeutung und Semantik von Sprache sind viel schwieriger zu beschreiben; Diese Beschreibung umfasst viel mehr Seiten, ist selten formal genug und fast nie vollständig. Daher die nie endenden Diskussionen unter Sprachjuristen und der gesamten Heimindustrie von Büchern, die sich mit der Interpretation der Feinheiten von Sprachstandards befassen.

Es gibt formale Mittel zur Beschreibung der Semantik einer Sprache, aber aufgrund ihrer Komplexität werden sie hauptsächlich für vereinfachte, akademische Sprachen und nicht für die wahren Giganten der industriellen Programmierung verwendet. Eines dieser Werkzeuge heißt operative Semantik und beschreibt die Mechanismen der Programmausführung. Es definiert einen formalisierten, idealisierten Interpreten. Die Semantik industrieller Sprachen wie C++ wird typischerweise durch informelles Denken beschrieben, oft im Sinne einer „abstrakten Maschine“.

Das Problem besteht darin, dass es sehr schwierig ist, irgendetwas über Programme zu beweisen, die operative Semantik verwenden. Um eine Eigenschaft eines Programms anzuzeigen, müssen Sie es im Wesentlichen durch einen idealisierten Interpreter „ausführen“.

Es spielt keine Rolle, dass Programmierer niemals offiziell die Richtigkeit beweisen. Wir „denken“ immer, dass wir die richtigen Programme schreiben. Niemand sitzt an einer Tastatur und sagt: „Oh, ich schreibe einfach ein paar Zeilen Code und schaue, was passiert.“ (Anmerkung des Übersetzers: Oh, wenn nur...) Wir glauben, dass der von uns geschriebene Code bestimmte Aktionen ausführt, die zu den gewünschten Ergebnissen führen. Wenn dies nicht der Fall ist, sind wir meist sehr überrascht. Das bedeutet, dass wir tatsächlich über die Programme nachdenken, die wir schreiben, und das tun wir normalerweise, indem wir einen Interpreter in unserem Kopf ausführen. Es ist nur sehr schwierig, den Überblick über alle Variablen zu behalten. Computer sind gut darin, Programme auszuführen, Menschen nicht! Wenn wir es wären, bräuchten wir keine Computer.

Aber es gibt eine Alternative. Sie wird denotationale Semantik genannt und basiert auf der Mathematik. In der denotationalen Semantik wird für jede sprachliche Konstruktion eine mathematische Interpretation beschrieben. Wenn Sie also eine Eigenschaft eines Programms beweisen möchten, beweisen Sie einfach einen mathematischen Satz. Sie denken, dass der Beweis von Theoremen schwierig ist, aber tatsächlich entwickeln wir Menschen seit Tausenden von Jahren mathematische Methoden, sodass eine Menge angesammeltes Wissen genutzt werden kann. Außerdem sind die Probleme, auf die wir beim Programmieren stoßen, im Vergleich zu den Theoremen, die professionelle Mathematiker beweisen, recht einfach, wenn nicht sogar trivial. (Anmerkung des Übersetzers: Zum Beweis: Der Autor versucht nicht, Programmierer zu beleidigen.)

Betrachten Sie die Definition der Fakultätsfunktion in Haskell, einer Sprache, die sich leicht für die Denotationssemantik eignet:
Tatsache n = Produkt
Ein Ausdruck ist eine Liste von ganzen Zahlen von 1 bis n. Die Produktfunktion multipliziert alle Elemente einer Liste. Genau wie die Definition von Fakultät aus dem Lehrbuch. Vergleichen Sie dies mit C:
int fact(int n) ( int i; int result = 1; for (i = 2; i<= n; ++i) result *= i; return result; }
Soll ich fortfahren? (Anmerkung des Übersetzers: Der Autor hat ein wenig geschummelt, indem er eine Bibliotheksfunktion in Haskell übernommen hat. Tatsächlich bestand kein Grund zum Schummeln; eine ehrliche Beschreibung ist per Definition nicht schwieriger):
Fakt 0 = 1 Fakt n = n * Fakt (n - 1)
Okay, ich gebe gleich zu, dass es ein billiger Schuss war! Faktorial hat eine offensichtliche mathematische Definition. Der kluge Leser fragt sich vielleicht: Was ist das mathematische Modell zum Lesen eines Zeichens von der Tastatur oder zum Senden eines Pakets über ein Netzwerk? Lange Zeit war dies eine unangenehme Frage, die zu recht verwirrenden Erklärungen führte. Die Denotationssemantik schien für eine erhebliche Anzahl wichtiger Probleme, die zum Schreiben nützlicher Programme notwendig waren und die leicht durch die operative Semantik gelöst werden konnten, ungeeignet zu sein. Der Durchbruch kam aus der Kategorientheorie. Eugenio Moggi entdeckte, dass rechnerische Effekte in Monaden umgewandelt werden können. Dies erwies sich als wichtige Beobachtung, die nicht nur der Denotationssemantik neues Leben einhauchte und rein funktionale Programme komfortabler machte, sondern auch neue Informationen über die traditionelle Programmierung lieferte. Ich werde später über Monaden sprechen, wenn wir kategorischere Werkzeuge entwickeln.

Einer der wichtigen Vorteile eines mathematischen Modells für die Programmierung ist die Möglichkeit, einen formalen Beweis für die Korrektheit der Software durchzuführen. Dies mag beim Schreiben von Verbrauchersoftware nicht so wichtig erscheinen, aber es gibt Bereiche der Programmierung, in denen die Kosten eines Fehlers enorm sein können oder in denen Menschenleben gefährdet sind. Aber auch beim Schreiben von Webanwendungen für das Gesundheitssystem können Sie die Idee zu schätzen wissen, dass die Funktionen und Algorithmen aus der Haskell-Standardbibliothek mit Korrektheitsnachweisen geliefert werden.

Clean- und Dirty-Funktionen

Was wir in C++ oder einer anderen imperativen Sprache Funktionen nennen, ist nicht dasselbe wie das, was Mathematiker Funktionen nennen. Eine mathematische Funktion ist einfach eine Abbildung von Werten auf Werte.

Wir können eine mathematische Funktion in einer Programmiersprache implementieren: Eine solche Funktion berechnet bei gegebenem Eingabewert den Ausgabewert. Eine Funktion zum Quadrieren einer Zahl multipliziert wahrscheinlich den Eingabewert mit sich selbst. Dies geschieht bei jedem Aufruf und es wird garantiert, dass bei jedem Aufruf mit demselben Argument das gleiche Ergebnis erzielt wird. Das Quadrat der Zahl ändert sich nicht mit den Mondphasen.

Darüber hinaus sollte das Berechnen des Quadrats einer Zahl nicht den Nebeneffekt haben, Ihrem Hund ein leckeres Leckerli zu bereiten. Die „Funktion“, die dies tut, kann nicht einfach durch eine mathematische Funktion modelliert werden.

In Programmiersprachen werden Funktionen, die bei denselben Argumenten immer das gleiche Ergebnis liefern und keine Nebenwirkungen haben, als rein bezeichnet. In einer rein funktionalen Sprache wie Haskell sind alle Funktionen rein. Dies erleichtert es, die denotative Semantik dieser Sprachen zu bestimmen und sie mithilfe der Kategorientheorie zu modellieren. Bei anderen Sprachen können Sie sich immer auf eine reine Teilmenge beschränken oder Nebenwirkungen separat betrachten. Später werden wir sehen, wie Monaden es uns ermöglichen, alle Arten von Effekten nur mithilfe reiner Funktionen zu modellieren. Wir verlieren also nichts, wenn wir uns auf mathematische Funktionen beschränken.

Beispiele für Typen

Sobald Sie entschieden haben, dass Typen Mengen sind, können Sie sich einige ziemlich exotische Beispiele einfallen lassen. Welcher Typ entspricht beispielsweise der leeren Menge? Nein, in C++ ist es nicht void, obwohl dieser Typ in Haskell Void heißt. Dies ist ein Typ, der mit keinem Wert gefüllt ist. Sie können eine Funktion definieren, die ein Void akzeptiert, aber Sie können sie niemals aufrufen. Um es aufzurufen, müssen Sie einen Wert vom Typ Void angeben, und dieser ist einfach nicht vorhanden. Was die Rückgabemöglichkeiten dieser Funktion betrifft, gibt es keine Einschränkungen. Es kann jeden Typ zurückgeben (obwohl dies niemals passieren wird, da es nicht aufgerufen werden kann). Mit anderen Worten: Es handelt sich um eine Funktion, deren Rückgabetyp polymorph ist. Die Haskellers nannten es:
absurd::Void -> a
(Anmerkung des Übersetzers: Es ist unmöglich, eine solche Funktion in C++ zu definieren: In C++ hat jeder Typ mindestens einen Wert.)

(Denken Sie daran, dass a eine Typvariable ist, die einen beliebigen Typ haben kann.) Dieser Name ist kein Zufall. Es gibt eine tiefere logische Interpretation von Typen und Funktionen, die als Curry-Howard-Isomorphismus bezeichnet wird. Der Typ „Leer“ steht für Unwahrheit, und die Funktion „Absurd“ stellt die Behauptung dar, dass aus einer Falschheit etwas folgt, wie in der lateinischen Phrase „ex falso sequitur quodlibet“. (Anmerkung des Übersetzers: Aus Falschheit folgt alles.)

Als nächstes kommt der Typ, der dem Singleton-Set entspricht. Dies ist ein Typ, der nur einen möglichen Wert hat. Diese Bedeutung ist einfach „es gibt“. Sie erkennen es vielleicht nicht sofort, aber in C++ ist es ungültig. Denken Sie über die Funktionen von und zu diesem Typ nach. Eine void-Funktion kann immer aufgerufen werden. Wenn es sich um eine reine Funktion handelt, wird immer das gleiche Ergebnis zurückgegeben. Hier ist ein Beispiel für eine solche Funktion:
int f44() ( return 44; )
Man könnte meinen, dass diese Funktion „nichts“ akzeptiert, aber wie wir gerade gesehen haben, kann eine Funktion, die „nichts“ akzeptiert, nicht aufgerufen werden, weil es keinen Wert gibt, der den Typ „nichts“ darstellt. Was akzeptiert diese Funktion? Vom Konzept her nimmt es einen Dummy-Wert an, der nur eine einzige Instanz hat, sodass wir ihn nicht explizit im Code angeben müssen. Haskell hat jedoch ein Symbol für diese Bedeutung: das leere Klammerpaar (). Aufgrund eines lustigen Zufalls (oder nicht eines Zufalls?) sieht der Aufruf einer Funktion aus void sowohl in C++ als auch in Haskell gleich aus. Darüber hinaus wird aufgrund von Haskells Liebe zur Kürze das gleiche Symbol () für den Typ, den Konstruktor und den einzelnen Wert verwendet, der einem Singleton-Set entspricht. Hier ist die Funktion in Haskell:
f44::() -> Ganzzahl f44() = 44
Die erste Zeile erklärt, dass f44 den Typ () namens „unit“ in den Typ Integer umwandelt. Die zweite Zeile gibt an, dass f44 den Mustervergleich verwendet, um den einzigen Konstruktor für einen, nämlich (), in die Zahl 44 zu konvertieren. Sie rufen diese Funktion auf, indem Sie den Wert () angeben:
f44()
Beachten Sie, dass jede einzelne Funktion der Auswahl eines Elements aus dem Zieltyp entspricht (hier wird Ganzzahl 44 ausgewählt). Tatsächlich können Sie sich f44 als eine andere Darstellung der Zahl 44 vorstellen. Dies ist ein Beispiel dafür, wie wir den direkten Verweis auf die Elemente einer Menge durch eine Funktion (einen Pfeil) ersetzen können. Funktionen von eins bis zu einem bestimmten Typ A stehen in einer Eins-zu-eins-Entsprechung mit den Elementen der Menge A.

Was ist mit Funktionen, die void zurückgeben, oder, in Haskell, eins zurückgeben? In C++ werden solche Funktionen für Nebeneffekte verwendet, aber wir wissen, dass solche Funktionen keine echten Funktionen im mathematischen Sinne des Wortes sind. Eine reine Funktion, die eins zurückgibt, tut nichts: Sie verwirft ihr Argument.

Mathematisch gesehen ordnet eine Funktion von einer Menge A zu einer Singleton-Menge jedes Element einem einzelnen Element dieser Menge zu. Für jedes A gibt es genau eine solche Funktion. Hier ist es für Integer:
fInt::Integer -> () fInt x = ()
Sie geben ihm eine beliebige Ganzzahl und es gibt eins zurück. Im Sinne der Kürze erlaubt Haskell die Verwendung eines Unterstrichs als Argument, der verworfen wird. Auf diese Weise ist es nicht nötig, sich einen Namen dafür auszudenken. Der obige Code kann wie folgt umgeschrieben werden:
fInt::Integer -> () fInt_ = ()
Beachten Sie, dass die Ausführung dieser Funktion nicht nur unabhängig vom übergebenen Wert ist, sondern auch unabhängig vom Typ des Arguments.

Funktionen, die für jeden Typ durch dieselbe Formel definiert werden können, werden als parametrisch polymorph bezeichnet. Sie können eine ganze Familie solcher Funktionen mit einer einzigen Gleichung implementieren, indem Sie einen Parameter anstelle eines konkreten Typs verwenden. Wie rufe ich eine polymorphe Funktion von einem beliebigen Typ zu einem auf? Natürlich nennen wir es Einheit:
Einheit::a -> () Einheit _ = ()
In C++ würden Sie es folgendermaßen implementieren:
Vorlage Leere Einheit(T) ()
(Anmerkung des Übersetzers: Um dem Compiler bei der Optimierung in Noop zu helfen, ist es besser so):
Vorlage Leere Einheit(T&&) ()
Als nächstes kommt in der „Typologie der Typen“ eine Menge aus zwei Elementen. In C++ heißt es bool und in Haskell, was nicht überraschend ist, Bool. Der Unterschied besteht darin, dass bool in C++ ein integrierter Typ ist, während er in Haskell wie folgt definiert werden kann:
data Bool = True | FALSCH
(Diese Definition sollte so gelesen werden: Bool kann entweder True oder False sein.) Prinzipiell wäre es möglich, diesen Typ in C++ zu beschreiben:
enum bool(true, false);
Aber eine C++-Aufzählung ist eigentlich eine Ganzzahl. Man könnte eine C++11-„Klassenaufzählung“ verwenden, aber dann müsste man den Wert mit dem Namen der Klasse qualifizieren: bool::true oder bool::false, ganz zu schweigen von der Notwendigkeit, den entsprechenden Header einzuschließen jede Datei, die es verwendet.

Reine Bool-Funktionen wählen einfach zwei Werte aus dem Zieltyp aus, einen entsprechend True und einen entsprechend False.

Funktionen in Bool werden Prädikate genannt. Beispielsweise enthält die Data.Char-Bibliothek in Haskell viele Prädikate wie IsAlpha oder isDigit. Es gibt eine ähnliche Bibliothek in C++ , die unter anderem die Funktionen isalpha und isdigit deklariert, diese aber einen int statt eines booleschen Werts zurückgeben. Die Präsensprädikate sind in definiert und heißen ctype::is(alpha, c) und ctype::is(digit, c).



 


Lesen:



Typisierte Programmiersprache Typ oder Formatbezeichner oder Konvertierungszeichen oder Steuerzeichen

Typisierte Programmiersprache Typ oder Formatbezeichner oder Konvertierungszeichen oder Steuerzeichen

Programmiersprache C++ Letzte Aktualisierung: 28.08.2017 Die Programmiersprache C++ ist eine kompilierte Hochsprache...

Arbeitsplan der russischen Post an den Neujahrsfeiertagen. Postarbeit an den Neujahrsfeiertagen

Arbeitsplan der russischen Post an den Neujahrsfeiertagen. Postarbeit an den Neujahrsfeiertagen

Die russische Post hat sich im 21. Jahrhundert zu einer universellen Institution entwickelt, die nicht nur beim Empfang von Briefen und Paketen hilft. Versorgungsleistungen, Renten,...

Tass: Abkürzung Dekodierung

Tass: Abkürzung Dekodierung

Dieser Begriff kommt vom italienischen abbreviatura und dem lateinischen brevis – kurz. In alten Büchern und Manuskripten war dies die Bezeichnung für eine Abkürzung...

Zertifikatsvorlagen leer, laden Sie die Vorlage „Ehrenurkunde“ zum Ausdrucken herunter

Zertifikatsvorlagen leer, laden Sie die Vorlage „Ehrenurkunde“ zum Ausdrucken herunter

Grüße, lieber Leser! Heute erzähle ich Ihnen, wie Sie einen Brief in Word erstellen. Bei meiner Arbeit musste ich eine große Anzahl von ... aufschreiben.

Feed-Bild RSS