Unveränderlichkeit oder Veränderlichkeit sind keine Konzepte, die in der funktionalen Programmierung sinnvoll sind.
Der rechnerische Kontext
Dies ist eine sehr gute Frage, die eine interessante Fortsetzung (kein Duplikat) einer anderen aktuellen Frage darstellt: Was ist der Unterschied zwischen Zuordnung, Bewertung und Namensbindung?
Anstatt einzeln auf Ihre Aussagen zu antworten, versuche ich hier, Ihnen einen strukturierten Überblick darüber zu geben, worum es geht.
Es gibt verschiedene Punkte, die berücksichtigt werden müssen, um Sie zu beantworten, darunter:
Der funktionale Programmierstil scheint albern, weil Sie ihn mit einem zwingenden Programmiererauge sehen. Aber es ist ein anderes Paradigma, und Ihre imperativen Konzepte und Wahrnehmungen sind fremd und fehl am Platz. Die Compiler haben keine derartigen Vorurteile.
Die Endschlussfolgerung ist jedoch, dass es möglich ist, Programme auf rein funktionale Weise zu schreiben, auch für maschinelles Lernen. Die funktionale Programmierung hat jedoch nicht das Konzept, Daten zu speichern. Ich scheine in diesem Punkt mit anderen Antworten nicht einverstanden zu sein.
In der Hoffnung werden einige trotz der Länge dieser Antwort interessiert sein.
Computerparadigmen
Die Frage betrifft die funktionale Programmierung (auch bekannt als anwendungsbezogene Programmierung), ein spezifisches Berechnungsmodell, dessen theoretischer und einfachster Vertreter die Lambda-Rechnung ist.
Wenn Sie auf einem theoretischen Niveau bleiben, gibt es viele Berechnungsmodelle: die Turing-Maschine (TM), die RAM-Maschine und andere , die Lambda-Rechnung, die kombinatorische Logik, die rekursive Funktionstheorie, Semi-Thue-Systeme usw. Je leistungsfähiger die Berechnung Modelle haben sich in Bezug auf das, was sie ansprechen können, als gleichwertig erwiesen, und das ist der Kern der
Church-Turing-These .
Ein wichtiges Konzept ist die gegenseitige Reduzierung von Modellen, die die Grundlage für die Ermittlung der Äquivalenzen bilden, die zur These von Church-Turing führen. Aus Sicht des Programmierers ist das Reduzieren eines Modells auf ein anderes so ziemlich das, was normalerweise als Compiler bezeichnet wird. Wenn Sie die Logikprogrammierung als Berechnungsmodell verwenden, unterscheidet sie sich erheblich von dem Modell des PCs, den Sie in einem Geschäft gekauft haben, und der Compiler übersetzt in Logikprogrammiersprache geschriebene Programme in das von Ihrem PC dargestellte Rechenmodell (ziemlich genau) der RAM-Computer).
Dies bedeutet jedoch nicht, dass die beiden Modelle auf die gleiche Weise vorgehen oder dass ein für eines sinnvolles Konzept als solches auf ein anderes übertragen werden kann. Typischerweise hat ein Berechnungsschritt in einem TM wenig Beziehung zu einem ( -) - Reduktionsschritt in Lambda Calculus, obwohl sie interübersetzbar sind. Das Konzept der optimalen Bewertung der Lambda-Expression ist weit entfernt von Komplexitätsproblemen in TM-Modellen.β
In der Praxis tendieren die von uns verwendeten Programmiersprachen dazu, Konzepte unterschiedlicher theoretischer Herkunft zu mischen und dies so zu versuchen, dass ausgewählte Teile eines Programms gegebenenfalls von den Eigenschaften eines Modells profitieren können. In ähnlicher Weise können Personen, die Systeme erstellen, unterschiedliche Sprachen für unterschiedliche Komponenten auswählen, um die Sprache am besten an die jeweilige Aufgabe anzupassen.
Daher sehen Sie selten ein Programmierparadigma in einem reinen Zustand in einer Programmiersprache. Programmiersprachen werden immer noch nach dem vorherrschenden Paradigma klassifiziert, aber die Eigenschaften der Sprache können beeinträchtigt werden, wenn Konzepte aus anderen Paradigmen beteiligt sind, wodurch häufig Unterscheidungen und konzeptionelle Probleme verwischt werden.
Normalerweise werden Sprachen wie Haskell und ML oder CAML als funktional angesehen, aber sie können zwingendes Verhalten zulassen ... Warum sollte man sonst von der " rein funktionalen Teilmenge " sprechen ?
Dann kann man behaupten, dass Sie dies oder jenes in meiner funktionalen Programmiersprache tun können, aber es beantwortet nicht wirklich eine Frage zur funktionalen Programmierung, wenn es sich auf das stützt, was als außerfunktional angesehen werden kann.
Die Antworten sollten sich ohne die Extras genauer auf ein bestimmtes Paradigma beziehen.
Was ist eine Variable?
Ein weiteres Problem ist die Verwendung von Terminologie. In der Mathematik ist eine Variable eine Entität, die in einem bestimmten Bereich für einen unbestimmten Wert steht. Es wird für verschiedene Zwecke verwendet. In einer Gleichung verwendet, kann es für jeden Wert stehen, so dass die Gleichung verifiziert wird. Diese Vision wird in der Logikprogrammierung unter dem Namen " logische Variable " verwendet, wahrscheinlich weil die Namensvariable bereits bei der Entwicklung der Logikprogrammierung eine andere Bedeutung hatte.
In der traditionellen imperativen Programmierung wird eine Variable als eine Art Container (oder Speicherort) verstanden, der die Darstellung eines Werts speichern und möglicherweise seinen aktuellen Wert durch einen anderen ersetzen kann.
In der funktionalen Programmierung hat eine Variable den gleichen Zweck wie in der Mathematik als Platzhalter für einen Wert, der noch bereitgestellt werden muss. In der traditionellen imperativen Programmierung wird diese Rolle tatsächlich von Konstanten gespielt (nicht zu verwechseln mit Literalen, bei denen es sich um bestimmte Werte handelt, die mit einer für diesen Wertebereich spezifischen Notation ausgedrückt werden, wie z. B. 123, true, ["abdcz", 3.14]).
Variablen jeglicher Art sowie Konstanten können durch Bezeichner dargestellt werden.
Der Wert der imperativen Variablen kann geändert werden, und das ist die Grundlage für die Veränderlichkeit. Die Funktionsvariable kann nicht.
Programmiersprachen ermöglichen normalerweise das Erstellen größerer Entitäten aus den kleineren in der Sprache.
Imperative Sprachen ermöglichen es solchen Konstrukten, Variablen einzuschließen, und das gibt Ihnen veränderbare Daten.
Wie man ein Programm liest
Ein Programm ist im Grunde eine abstrakte Beschreibung Ihres Algorithmus in einer Sprache, sei es ein pragmatisches Design oder eine paradigmatisch reine Sprache.
Im Prinzip können Sie jede Aussage für das nehmen, was sie abstrakt bedeuten soll. Dann übersetzt der Compiler dies in eine geeignete Form, die der Computer ausführen kann, aber das ist in erster Näherung nicht Ihr Problem.
Natürlich ist die Realität etwas härter, und es ist oft gut, eine Vorstellung davon zu haben, was passiert, um Strukturen zu vermeiden, mit denen der Compiler für eine effiziente Ausführung nicht umgehen kann. Aber das ist schon eine Optimierung ... für die Compiler sehr gut sein können, oft besser als Programmierer.
Funktionale Programmierung und Veränderlichkeit
Die Veränderlichkeit basiert auf der Existenz imperativer Variablen, die Werte enthalten können, die durch Zuweisung geändert werden müssen. Da diese in der funktionalen Programmierung nicht vorhanden sind, kann alles als unveränderlich angesehen werden.
Die Funktionsprogrammierung befasst sich ausschließlich mit Werten.
Ihre ersten vier Aussagen zur Unveränderlichkeit sind größtenteils richtig, beschreiben aber mit zwingender Sicht etwas, das nicht zwingend ist. Es ist ein bisschen so, als würde man mit Farben in einer Welt beschreiben, in der jeder blind ist. Sie verwenden Konzepte, die der funktionalen Programmierung fremd sind.
Sie haben nur reine Werte, und ein Array von Ganzzahlen ist ein reiner Wert. Um ein anderes Array zu erhalten, das sich nur in einem Element unterscheidet, müssen Sie einen anderen Array-Wert verwenden. Das Ändern eines Elements ist nur ein Konzept, das in diesem Kontext nicht existiert. Möglicherweise haben Sie eine Funktion, die ein Array und einige Indizes als Argument hat und ein Ergebnis zurückgibt, das nahezu identisch ist und sich nur dort unterscheidet, wo dies durch die Indizes angezeigt wird. Es ist jedoch immer noch ein unabhängiger Array-Wert. Wie diese Werte dargestellt werden, ist nicht Ihr Problem. Vielleicht "teilen" sie viel in der zwingenden Übersetzung für den Computer ... aber das ist die Aufgabe des Compilers ... und Sie möchten nicht einmal wissen, für welche Art von Maschinenarchitektur er kompiliert.
Sie kopieren keine Werte (es macht keinen Sinn, es ist ein Fremdwort). Sie verwenden nur Werte, die in den Domänen vorhanden sind, die Sie in Ihrem Programm definiert haben. Entweder beschreiben Sie sie (als Literale) oder sie sind das Ergebnis der Anwendung einer Funktion auf einige andere Werte. Sie können ihnen einen Namen geben (wodurch eine Konstante definiert wird), um sicherzustellen, dass derselbe Wert an verschiedenen Stellen im Programm verwendet wird. Beachten Sie, dass die Funktionsanwendung nicht als Berechnung, sondern als Ergebnis der Anwendung auf die angegebenen Argumente wahrgenommen werden sollte. Schreiben 5+2
oder Schreiben ist 7
gleich. Was mit dem vorherigen Absatz übereinstimmt.
Es gibt keine zwingenden Variablen. Eine Zuordnung ist nicht möglich. Sie können Namen nur an Werte binden (um Konstanten zu bilden), im Gegensatz zu imperativen Sprachen, in denen Sie Namen an zuweisbare Variablen binden können.
Ob dies mit Kosten verbunden ist, ist völlig unklar. Zum einen betrifft Ihr Hinweis auf Komplexität zwingende Paradigmen. Es ist nicht als solches für die funktionale Programmierung definiert, es sei denn, Sie lesen Ihr funktionales Programm als ein zwingendes, was nicht die Absicht des Designers ist. In der Tat soll die Funktionsansicht Sie nicht über solche Probleme beunruhigen und sich auf das konzentrieren, was berechnet wird. Es ist ein bisschen wie Spezifikation versus Implementierung.
Der Compiler muss sich um die Implementierung kümmern und klug genug sein, um das, was zu tun ist, am besten an die Hardware anzupassen, die es tun wird, was auch immer es ist.
Ich sage nicht, dass sich Programmierer darüber niemals Sorgen machen. Ich sage auch nicht, dass Programmiersprachen und Compilertechnologie so ausgereift sind, wie wir es uns wünschen könnten.
Beantwortung der Fragen
Sie ändern keinen vorhandenen Wert (Alien-Konzept), sondern berechnen neue Werte, die sich bei Bedarf unterscheiden, möglicherweise indem Sie ein zusätzliches Element als Liste verwenden.
Das Programm kann neue Daten erhalten. Der springende Punkt ist, wie Sie das in der Sprache ausdrücken. Sie können beispielsweise davon ausgehen, dass das Programm mit einem bestimmten Wert arbeitet, der möglicherweise unbegrenzt groß ist und als Eingabestream bezeichnet wird. Es ist ein Wert, der dort sitzen soll (ob es bereits vollständig bekannt ist oder nicht, ist nicht Ihr Problem). Dann haben Sie eine Funktion, die ein Paar zurückgibt, das aus dem ersten Element des Streams und dem Rest des Streams besteht.
Sie können dies verwenden, um Netzwerke von Kommunikationskomponenten auf rein anwendungsbezogene Weise aufzubauen (Coroutinen).
Maschinelles Lernen ist nur ein weiteres Problem, wenn Sie Daten sammeln und Werte ändern müssen. In der funktionalen Programmierung tun Sie das nicht: Sie berechnen nur neue Werte, die sich entsprechend den Trainingsdaten entsprechend unterscheiden. Die resultierende Maschine funktioniert ebenfalls. Was Sie sich Sorgen machen, ist die Berechnung der Zeit- und Raumeffizienz. Aber auch dies ist ein anderes Problem, das idealerweise vom Compiler behandelt werden sollte.
Schlussbemerkungen
Aus den Kommentaren oder anderen Antworten geht klar hervor, dass praktische funktionale Programmiersprachen nicht rein funktional sind. Dies spiegelt die Tatsache wider, dass unsere Technologie noch verbessert werden muss, insbesondere beim Kompilieren.
Ist es möglich, rein anwendungsbezogen zu schreiben? Die Antwort ist seit ungefähr 40 Jahren bekannt und lautet "Ja". Der eigentliche Zweck der Denotationssemantik, wie sie in den 1970er Jahren auftrat, bestand genau darin, Sprachen in einen rein funktionalen Stil zu übersetzen (zu kompilieren), der mathematisch besser verstanden und daher als bessere Grundlage für die Definition der Semantik von Programmen angesehen wurde.
Das Interessante daran ist, dass die imperative Programmierstruktur, einschließlich der Variablen, durch Einführung geeigneter Wertebereiche wie eines Datenspeichers in einen funktionalen Stil übersetzt werden kann. Und trotz des funktionalen Stils bleibt es dem Code der tatsächlichen Compiler, die im imperativen Stil geschrieben sind, überraschend ähnlich.