Funktionale Programmiersprache
Funktionale Programmiersprachen sind solche, die die Verwendung funktionaler Programmierparadigmen erleichtern. Einfach ausgedrückt: Wenn sie die für die funktionale Programmierung erforderlichen Eigenschaften aufweist, kann sie als funktionale Sprache bezeichnet werden. In den meisten Fällen bestimmt der Programmierstil tatsächlich, ob ein Programm funktionsfähig ist.
Was macht eine Sprache funktionsfähig?
Funktionale Programmierung kann nicht in der Sprache C implementiert werden. Funktionale Programmierung kann nicht in Java implementiert werden (mit Ausnahme derjenigen, die die funktionale Programmierung durch viele Problemumgehungen annähern). Diese Sprachen enthalten keine Konstrukte zur Unterstützung der funktionalen Programmierung. Es handelt sich um rein objektorientierte, streng nicht funktionale Sprachen.
Gleichzeitig kann objektorientierte Programmierung nicht in rein funktionalen Sprachen wie Scheme, Haskell und Lisp verwendet werden.
Einige Sprachen unterstützen jedoch beide Modi. Python ist ein berühmtes Beispiel, aber es gibt noch andere: Ruby, Julia und, was am interessantesten ist, Javascript. Wie unterstützen diese Sprachen diese beiden so unterschiedlichen Entwurfsmuster? Sie enthalten Funktionen, die für beide Programmierparadigmen erforderlich sind. Bei Javascript scheinen die Funktionsmerkmale jedoch verborgen zu sein.
Aber tatsächlich erfordern funktionale Sprachen etwas mehr als das oben Genannte. Was sind die Merkmale funktionaler Sprachen?
特点 | 命令式 | 函数式 |
---|---|---|
编程风格 | 一步一步地执行,并且要管理状态的变化 | 描述问题和和所需的数据变化以解决问题 |
状态变化 | 很重要 | 不存在 |
执行顺序 | 很重要 | 不太重要 |
主要的控制流 | 循环、条件、函数调用 | 函数调用和递归 |
主要的操作单元 | 结构体和类对象 | 函数作为一等公民的对象和数据集 |
Die Syntax funktionaler Sprachen muss bestimmte Entwurfsmuster berücksichtigen, z. B. Typinferenzsysteme und anonyme Funktionen. Im Allgemeinen muss die Sprache die Lambda-Rechnung implementieren. Und die Bewertungsstrategie des Interpreters muss eine nicht strenge Call-on-Demand-Strategie sein (auch als verzögerte Ausführung bezeichnet), was unveränderliche Datenstrukturen und eine nicht strenge, verzögerte Bewertung ermöglicht.
译注:这一段用了一些函数式编程的专业词汇。lambda演算是一套函数推演的形式化系统(听起来很晕), 它的先决条件是内部函数和匿名函数。非严格求值和惰性求值差不多一个意思,就是并非严格地按照运算规则把所有元素先计算一遍, 而是根据最终的需求只计算有用的那一部分,比如我们要取有一百个元素的数组的前三项, 那惰性求值实际只会计算出一个具有三个元素是数组,而不会先去计算那个一百个元素的数组。
Vorteile
Funktionale Programmierung wird eine große Inspiration sein, wenn Sie sie endlich beherrschen. Diese Art von Erfahrung wird Ihre zukünftige Karriere als Programmierer auf ein höheres Niveau heben, unabhängig davon, ob Sie tatsächlich ein Vollzeit-Funktionsprogrammierer werden.
Aber wir reden jetzt nicht darüber, wie man das Meditieren lernt; wir reden darüber, wie man ein sehr nützliches Werkzeug erlernt, das Sie zu einem besseren Programmierer macht.
Was sind insgesamt die wirklichen praktischen Vorteile der Verwendung funktionaler Programmierung?
Cleaner-Code
Funktionale Programmierung ist sauberer, einfacher und kleiner. Es vereinfacht das Debuggen, Testen und Warten.
Zum Beispiel benötigen wir eine Funktion, die ein zweidimensionales Array in ein eindimensionales Array umwandelt. Wenn wir nur Imperativtechniken verwenden würden, würden wir es so schreiben:
function merge2dArrayIntoOne(arrays) { var count = arrays.length; var merged = new Array(count); var c = 0; for (var i = 0; i < count; ++i) { for (var j = 0, jlen = arrays[i].length; j < jlen; ++j) { merged[c++] = arrays[i][j]; } } return merged }
Mit funktionalen Techniken kann es nun so geschrieben werden:
merge2dArrayIntoOne2 = (arrays) -> arrays.reduce (memo, item) -> memo.concat item , []
var merge2dArrayIntoOne2 = function(arrays) { return arrays.reduce( function(p,n){ return p.concat(n); }, []); };
译注:原著中代码有误,调用reduce函数时少了第二个参数空数组,这里已经补上。
Beide Funktionen nehmen dieselben Eingaben entgegen und geben dieselben Ausgaben zurück, aber das Funktionsbeispiel ist übersichtlicher.
Modular
Funktionale Programmierung zwingt Sie dazu, große Probleme in kleinere Fälle zu unterteilen, die das gleiche Problem lösen, was bedeutet, dass der Code modularer wird. Modulare Programme verfügen über klarere Beschreibungen, sind einfacher zu debuggen und einfacher zu warten. Auch das Testen wird einfacher, da der Code jedes Moduls unabhängig auf Korrektheit überprüft werden kann.
Wiederverwendbarkeit
Aufgrund seines modularen Charakters verfügt die funktionale Programmierung über viele gemeinsame Hilfsfunktionen. Sie werden feststellen, dass viele der hier aufgeführten Funktionen in verschiedenen Anwendungen wiederverwendet werden können.
In den folgenden Kapiteln werden viele der häufigsten Funktionen behandelt. Als funktionaler Programmierer schreiben Sie jedoch zwangsläufig Ihre eigene Funktionsbibliothek, die immer wieder verwendet wird. Beispielsweise kann eine Funktion zum Durchsuchen von Konfigurationsdateien zwischen Zeilen auch zum Durchsuchen einer Hash-Tabelle verwendet werden, wenn diese gut gestaltet ist.
Kopplung reduzieren
Kopplung ist eine große Anzahl von Abhängigkeiten zwischen Modulen in einem Programm. Da die funktionale Programmierung dem Schreiben erstklassiger reiner Funktionen höherer Ordnung folgt, die keine Nebenwirkungen auf globale Variablen haben und völlig unabhängig voneinander sind, wird die Kopplung stark reduziert. Natürlich hängen Funktionen zwangsläufig voneinander ab, aber die Änderung einer Funktion hat keine Auswirkungen auf die anderen, solange die Eins-zu-Eins-Zuordnung von Ein- und Ausgängen korrekt bleibt.
Mathematische Korrektheit
Der letzte Punkt ist eher theoretischer Natur. Da die funktionale Programmierung auf der Lambda-Rechnung basiert, kann ihre Richtigkeit mathematisch nachgewiesen werden. Dies ist ein großer Vorteil für einige Forscher, die Programme zum Nachweis von Wachstumsraten, Zeitkomplexität und mathematischer Korrektheit benötigen.
Werfen wir einen Blick auf die Fibonacci-Folge. Obwohl es außerhalb von Proof-of-Concept-Problemen selten verwendet wird, ist es eine großartige Möglichkeit, das Konzept zu erklären. Die Standardmethode zur Auswertung einer Fibonacci-Folge besteht darin, eine rekursive Funktion wie diese zu erstellen:
fibonnaci(n) = fibonnaci(n-2) + fibonnaci(n–1)
Sie müssen auch eine allgemeine Situation hinzufügen:
return 1 when n < 2
Dadurch kann die Rekursion beendet werden und jeder Schritt im rekursiven Aufrufstapel kann von hier aus akkumuliert werden.
Detaillierte Schritte sind unten aufgeführt
var fibonacci = function(n) { if (n < 2) { return 1; }else { return fibonacci(n - 2) + fibonacci(n - 1); } } console.log( fibonacci(8) ); // Output: 34
Mit Hilfe einer Lazy-Execution-Funktionsbibliothek ist es jedoch möglich, eine unendliche Sequenz zu generieren, indem die Mitglieder der gesamten Sequenz durch mathematische Gleichungen definiert werden. Am Ende werden nur die Mitglieder berechnet, die wir letztendlich benötigen.
var fibonacci2 = Lazy.generate(function() { var x = 1, y = 1; return function() { var prev = x; x = y; y += prev; return prev; }; }()); console.log(fibonacci2.length()); // Output: undefined console.log(fibonacci2.take(12).toArray()); // Output: [1, 1, 2, 3, 5,8, 13, 21, 34, 55, 89, 144] var fibonacci3 = Lazy.generate(function() { var x = 1, y = 1; return function() { var prev = x; x = y; y += prev; return prev; }; }()); console.log(fibonacci3.take(9).reverse().first(1).toArray()); //Output: [34]
第二个例子明显更有数学的味道。它依赖Lazy.js函数库。还有一些其它这样的库,比如Sloth.js、wu.js, 这些将在第三章里面讲到。
我插几句:后面这个懒执行的例子放这似乎仅仅是来秀一下函数式编程在数学正确性上的表现。 更让人奇怪的是作者还要把具有相同内部函数的懒加载写两遍,完全没意义啊…… 我觉得各位看官知道这是个懒执就行了,不必深究。
非函数式世界中的函数式编程
函数式和非函数式编程能混合在一起吗?尽管这是第七章的主题,但是在我们进一步学习之前, 还是要弄明白一些东西。
这本书并没要想要教你如何严格地用纯函数编程来实现整个应用。这样的应用在学术界之外不太适合。 相反,这本书是要教你如何在必要的命令式代码之上使用纯函数的设计策略。
例如,你需要在一段文本中找出头四个只含有字母的单词,稚嫩一些的写法会是这样:
var words = [], count = 0; text = myString.split(' '); for (i=0; count < 4, i < text.length; i++) { if (!text[i].match(/[0-9]/)) { words = words.concat(text[i]); count++; } } console.log(words);
函数式编程会写成这样:
var words = []; var words = myString.split(' ').filter(function(x){ return (! x.match(/[1-9]+/)); }).slice(0,4); console.log(words);
如果有一个函数式编程的工具库,代码可以进一步被简化:
La façon de déterminer si une fonction peut être écrite de manière plus fonctionnelle est de rechercher des boucles et des variables temporaires, telles que les variables "words" et "count" dans l'exemple précédent. Nous pouvons souvent remplacer les boucles et les variables temporaires par des fonctions d'ordre supérieur, que nous explorerons plus loin dans ce chapitre.
Javascript est-il un langage de programmation fonctionnel ?
Il reste maintenant une dernière question que nous devons nous poser : Javascript est-il un langage fonctionnel ou un langage non fonctionnel ?
Javascript est sans doute le langage de programmation fonctionnelle le plus populaire mais le moins compris au monde. Javascript est un langage de programmation fonctionnel habillé en C. Sa syntaxe est définitivement similaire à celle du C, ce qui signifie qu'elle utilise la syntaxe de bloc et l'ordre des mots infixes du C. Et elle porte le pire nom de toutes les langues vivantes. Vous n'avez pas besoin de l'imaginer pour voir combien de personnes sont confuses par la relation entre Javascript et Java, comme si son nom faisait allusion à ce que ce sera ! Mais en réalité, il a très peu de points communs avec Java. Cependant, il existe encore quelques idées pour forcer Javascript dans un langage orienté objet. Par exemple, des bibliothèques telles que Dojo et easy.js ont fait beaucoup de travail pour essayer d'abstraire Javascript afin de le rendre adapté à la programmation orientée objet. Javascript vient des années 1990, lorsque le monde réclamait à grands cris la programmation orientée objet. On nous disait que Javascript était un langage orienté objet parce que nous le souhaitions, mais en réalité ce n'était pas le cas.
Sa véritable identité remonte à ses prototypes : Scheme et Lisp, deux langages de programmation fonctionnels classiques. Javascript a toujours été un langage de programmation fonctionnel. Ses fonctions sont citoyennes de premier ordre et peuvent être imbriquées, il possède des fermetures et des fonctions composites, et il permet la rationalisation et les monades. Tous ces éléments sont essentiels à la programmation fonctionnelle. Voici quelques autres raisons pour lesquelles Javascript est un langage fonctionnel :
• Le lexique de Javascript inclut la possibilité de transmettre des fonctions en tant que paramètres, dispose d'un système d'inférence de type, prend en charge les fonctions anonymes, les fonctions d'ordre supérieur, les fermetures, etc. Ces caractéristiques sont cruciales pour la structure et le comportement qui composent la programmation fonctionnelle.
• Javascript n'est pas un langage purement orienté objet. La plupart de ses modèles de conception orientés objet sont complétés par la copie d'objets Prototype. Il s'agit d'un modèle de programmation orienté objet faible. Le script de l'European Computer Manufacturers Association (ECMAScript) - la forme officielle et l'implémentation standard de Javascript - contient la déclaration suivante dans la version 4.2.1 de la spécification :
"Javascript n'a pas de vraies classes comme C, Smalltalk et Java, mais il prend en charge les constructeurs pour créer des objets. De manière générale, dans les langages orientés objet basés sur les classes, l'état est porté par les instances, les méthodes sont portées par les classes et l'héritage est porté par des instances. uniquement pour la structure et le comportement. Dans EMACScript, l'état et les méthodes sont portés par les objets, et la structure, le comportement et l'état sont hérités »
• Javascript est un langage interprété. L'interpréteur de Javascript (parfois appelé « moteur ») est très similaire à l'interpréteur de Scheme. Ils sont tous deux dynamiques, ont tous deux des types de données flexibles faciles à composer et à transporter, évaluent tous deux le code en blocs d'expression et gèrent les fonctions de la même manière.
En d’autres termes, Javascript n’est en effet pas un langage purement fonctionnel. Il manque d’évaluation paresseuse et de données immuables intégrées. Cela est dû au fait que la plupart des interprètes sont appelés par leur nom plutôt que sur demande. Javascript n'est pas non plus très efficace pour gérer la récursivité en raison de la façon dont il gère les appels de fin. Mais tous ces problèmes peuvent être atténués par quelques considérations mineures. Une évaluation non stricte qui nécessite des séquences infinies et une évaluation paresseuse peut être réalisée via une bibliothèque appelée Lazy.js. L'immuabilité peut être obtenue simplement grâce à des compétences en programmation, mais elle n'est pas limitée par le niveau de langage mais nécessite l'autodiscipline du programmeur. L'élimination de la récursion de la queue peut être obtenue grâce à une méthode appelée trampoline. Ces problèmes seront expliqués au chapitre 6.
Il y a toujours eu de nombreux débats pour savoir si Javascript est un langage fonctionnel, un langage orienté objet, les deux, ou aucun des deux, et ces débats se poursuivront.
Enfin, la programmation fonctionnelle est une manière d'écrire du code concis grâce à des modifications, des combinaisons et une utilisation intelligentes de fonctions. Et Javascript constitue un bon moyen d’y parvenir. Si vous souhaitez vraiment exploiter tout le potentiel de Javascript, vous devez apprendre à l’utiliser comme langage fonctionnel.