Sprachen mit einer klaren Unterscheidung zwischen Subroutinen, die rein funktional, mutierend, zustandsverändernd usw. sind?

8

In letzter Zeit bin ich immer frustrierter geworden, dass es in den meisten modernen Programmiersprachen, mit denen ich gearbeitet habe (C / C ++, C #, F #, Ruby, Python, JS und mehr), kaum Sprachunterstützung gibt, um festzustellen, was Ein Unterprogramm reicht tatsächlich aus.

Betrachten Sie den folgenden einfachen Pseudocode:

var x = DoSomethingWith(y);

Wie bestimme ich, was der Aufruf von DoSomethingWith (y) tatsächlich bewirkt ? Wird es y mutieren oder wird es eine Kopie von y zurückgeben ? Hängt es vom globalen oder lokalen Zustand ab oder ist es nur von y abhängig ? Wird es den globalen oder lokalen Staat verändern? Wie wirkt sich die Schließung auf das Ergebnis des Anrufs aus?

In allen Sprachen, auf die ich gestoßen bin, kann fast keine dieser Fragen durch bloßes Betrachten der Signatur des Unterprogramms beantwortet werden, und es gibt fast nie Unterstützung für die Kompilierung oder Laufzeit. Normalerweise besteht die einzige Möglichkeit darin, dem Autor der API Ihr Vertrauen zu schenken und zu hoffen, dass die Dokumentations- und / oder Namenskonventionen zeigen, was die Unterroutine tatsächlich tut.

Meine Frage lautet: Gibt es heute Sprachen, die symbolisch zwischen diesen Arten von Szenarien unterscheiden und die Einschränkungen für die Kompilierungszeit für den Code festlegen, den Sie tatsächlich schreiben können?

(Natürlich gibt es in den meisten modernen Sprachen eine gewisse Unterstützung dafür, wie z. B. unterschiedliche Ebenen des Umfangs und der Schließung, die Trennung zwischen statischem Code und Instanzcode, Lambda-Funktionen usw. Aber zu oft scheinen diese miteinander in Konflikt zu geraten. Beispielsweise ist eine Lambda-Funktion normalerweise entweder rein funktional und gibt einfach einen Wert zurück, der auf Eingabeparametern basiert, oder mutiert die Eingabeparameter auf irgendeine Weise. In der Regel ist es jedoch möglich, über eine Lambda-Funktion auf statische Variablen zuzugreifen, was wiederum möglich ist Geben Sie Zugriff auf Instanzvariablen, und dann bricht alles auseinander.)

Christian Palmstierna
quelle
1
Beachten Sie, dass "rein funktional" nicht eindeutig ist. Sie meinen wahrscheinlich "rein" (frei von Nebenwirkungen); "funktional" impliziert ein Programmierparadigma, das Funktionen als erstklassige Objekte behandelt und Funktionen höherer Ordnung ermöglicht. Diese Funktionen müssen nicht unbedingt rein sein, und die meisten funktionalen Programmiersprachen ermöglichen unreine Funktionen.
tdammers
Das klingt nach einer wichtigen semantischen Unterscheidung. Was ich mit funktional meine, ist im mathematischen Sinne, dh dass die Routine nur von den Eingabedaten abhängt und keine anderen Daten im Programm liest oder in diese schreibt. Ist "reine Funktion" ein korrekterer Begriff, um dies zu beschreiben?
Christian Palmstierna

Antworten:

10

Ja, du willst dir Haskell ansehen. Es macht genau das, was Sie wollen. Alle Funktionen sind standardmäßig rein und können den Status nur mithilfe von Monaden ändern. Auch Haskell hat sehr starke statische Garantien für alle möglichen Dinge http://learnyouahaskell.com/

Zachary K.
quelle
Ah! Ich habe viele gute Dinge über Haskell gehört, bin aber nicht dazu gekommen, mich eingehend damit zu befassen. Vielleicht passt es zu meiner Rechnung :)
Christian Palmstierna
Es scheint dort zu sein, wo viele interessante Dinge vor sich gehen
Zachary K
5
Monaden können den Status nicht mehr als andere Datentypen mutieren. Sie unterliegen denselben Reinheitsbeschränkungen wie der Rest der Sprache. Schließlich haben sie nichts Besonderes an sich, abgesehen von etwas Syntaxzucker. Was sie tun können , ist eine bequeme Abstraktion über Code bereitzustellen, der den Status durch verkettete oder rekursive Funktionsaufrufe akkumuliert. Wenn dieser Code gegen die Laufzeit kompiliert wird, läuft er auf imperativen Code hinaus, ähnlich wie der monadische Code aussieht. Es ist immer noch eine Abstraktion und der Code ist immer noch 100% rein.
tdammers
4
Außerdem sind Funktionen standardmäßig nicht rein, sondern rein, Punkt. Haskell kann keine unreinen Funktionen ausdrücken.
tdammers
3
@CPX: Es gibt mindestens drei branchenstarke Webframeworks für Haskell (Yesod, Happstack und Snap) und eine Fülle von Bibliotheken für praktisch alle von Ihnen genannten Aufgaben (bei SOAP sind Sie sich jedoch nicht sicher). Die durchschnittliche Codequalität im Haskell-Ökosystem ist in der Regel ausgezeichnet. Das größte Problem, das ich vorhersehen kann, besteht darin, genügend Programmierer zu finden, um so etwas über einen längeren Wartungszeitraum zu unterstützen.
tdammers
6

C und C ++ unterstützen zumindest einen Teil des Problems nur sehr begrenzt über das constSchlüsselwort. Dies allein kontrolliert zwar nicht die Reinheit, kann jedoch (insbesondere in C ++) verwendet werden, um dem Compiler mitzuteilen, dass eine bestimmte Datenstruktur nicht über einen bestimmten Zeiger geändert werden soll. Einige Compiler (z. B. gcc) bieten auch ein 'pure'-Attribut als Spracherweiterung, um die Reinheit vollständig durchzusetzen. (Siehe diese Frage für Details).

Die Programmiersprache D unterstützt das explizite Deklarieren der Reinheit von Funktionen, und Compiler überprüfen und erzwingen die Reinheit (dh der Versuch, eine nicht reine Funktion aus einer reinen Funktion heraus aufzurufen, führt zu einem Compilerfehler).

Haskell ist völlig rein, das heißt, die Sprache selbst kann keine unreinen Funktionen ausdrücken, und es gibt kein Konzept einer "Routine". Alles, was mit reinen Funktionen allein nicht gelöst werden kann, wird an die (unreine) Laufzeit ausgelagert; Ein Programm mit Nebenwirkungen wird implementiert, indem (ausschließlich aus reinen Konstrukten) eine verzögerte Datenstruktur erstellt wird, die das Programmverhalten beschreibt, und diese dann zur Laufzeit übergeben wird. Die Haskell-Community experimentiert aktiv mit einem ganzen Zoo von Programmiersprachen, von denen einige rein und andere explizit rein sind.

Es mag noch mehr geben, aber das sind die, mit denen ich vertraut bin.

tdammers
quelle
gcc unterstützt explizite Reinheitserklärungen als Erweiterung wie diese
Nutzlos
Die const-Member-Funktionen von C ++ steuern die Reinheit, wenn Ihre Klassen keine nicht-const-Member-Funktionen haben. Alle Daten werden konstant und dann ist alles rein funktional.
tp1
@ tp1: Nicht ganz. Eine const-Member-Funktion kann leicht Nebenwirkungen hervorrufen, indem sie nicht-const-statische Methoden anderer Klassen aufruft, andere Klassen instanziiert oder einfach freie Funktionen aufruft oder globale Variablen ändert. Dies funktioniert nur, wenn Sie Ihre gesamte Codebasis auf all-const sperren, einschließlich aller von Ihnen verwendeten Bibliotheken (sogar STL).
tdammers
@tdammers: Die Sperre für alle Konstanten ist nicht sehr schlecht, da die Daten durch Konstruktorparameter auf verschiedene Werte initialisiert werden können. Es erfordert jedoch, dass sich alle Ihre Daten in Ihren Klassen befinden und nicht in globalen Variablen.
tp1
@ tp1: Es ist auch sehr unpraktisch, weil es die Anzahl der Bibliotheken, die Sie sicher verwenden können, auf praktisch Null reduziert.
tdammers
1

Felix hat anscheinend drei Kategorien:

  1. Funktionen

    There is a rule for functions: 
    
    A function introduced by a fun binder is not allowed to have any side effects.
    
    The compiler does not enforce this rule, but it does take advantage of it when
    optimising your code. 
    
  2. Verfahren

    Procedures do not return a value, and may and generally should have side-effects.
    
  3. Generatoren

    A generator is a function that may have side effects.
    
Yakiv
quelle
Das klingt nach einer Sprache, die versucht, genau das zu tun, wonach ich suche. Es ist bedauerlich, dass Funktionen nicht vom Compiler erzwungen werden (nach einigen Jahren professioneller Arbeit habe ich ein Misstrauen gegenüber den Bibliotheken anderer Programmierer entwickelt ;-)). Da es so neu ist, kann ich auch daran basteln, aber es klingt nicht nach einem Kandidaten für die kommerzielle / berufliche Entwicklung :(
Christian Palmstierna
1
@ChristianPalmstierna: Bin gerade über diese jahrelange Frage gestolpert. Wie so viele interessante Eigenschaften ist die Entscheidung, ob eine Funktion rein ist, leider gleichbedeutend mit der Lösung des Halteproblems. Beachten Sie, dass dies nicht unbedingt bedeutet, dass Sie es nicht tun können, sondern nur, dass es unendlich viele reine Funktionen gibt, deren Reinheit der Compiler nicht beweisen kann und daher als unrein ablehnen muss, obwohl dies nicht der Fall ist. (Dies unterscheidet sich jedoch nicht von der statischen Typüberprüfung. Normalerweise gibt es unendlich viele Programme, die sicher sind, aber nicht als typsicher erwiesen werden können. Hält uns nicht vom Tippen ab.)
Jörg W Mittag