Functional programming language
Functional programming languages are those that facilitate the use of functional programming paradigms. Simply put, if it has the characteristics required for functional programming, it can be called a functional language. In most cases, programming style actually determines whether a program is functional.
What makes a language functional?
Functional programming cannot be implemented in C language. Functional programming cannot be implemented in Java (excluding those that approximate functional programming through a lot of workarounds). These languages do not contain constructs to support functional programming. They are purely object-oriented, strictly non-functional languages.
At the same time, object-oriented programming cannot be used in pure functional languages, such as Scheme, Haskell and Lisp.
However, some languages support both modes. Python is a famous example, but there are others: Ruby, Julia, and most interestingly, Javascript. How do these languages support these two such different design patterns? They contain features required by both programming paradigms. However, for Javascript, the functional characteristics seem to be hidden.
But in fact, functional languages require a little more than the above. What are the characteristics of functional languages?
特点 | 命令式 | 函数式 |
---|---|---|
编程风格 | 一步一步地执行,并且要管理状态的变化 | 描述问题和和所需的数据变化以解决问题 |
状态变化 | 很重要 | 不存在 |
执行顺序 | 很重要 | 不太重要 |
主要的控制流 | 循环、条件、函数调用 | 函数调用和递归 |
主要的操作单元 | 结构体和类对象 | 函数作为一等公民的对象和数据集 |
The syntax of functional languages must take into account specific design patterns, such as type inference systems and anonymous functions. In general, the language must implement lambda calculus. And the interpreter's evaluation strategy must be non-strict, call-on-demand (also called lazy execution), which allows for immutable data structures and non-strict, lazy evaluation.
译注:这一段用了一些函数式编程的专业词汇。lambda演算是一套函数推演的形式化系统(听起来很晕), 它的先决条件是内部函数和匿名函数。非严格求值和惰性求值差不多一个意思,就是并非严格地按照运算规则把所有元素先计算一遍, 而是根据最终的需求只计算有用的那一部分,比如我们要取有一百个元素的数组的前三项, 那惰性求值实际只会计算出一个具有三个元素是数组,而不会先去计算那个一百个元素的数组。
Advantages
Functional programming will be a huge inspiration when you finally master it. This kind of experience will take your future career as a programmer to a higher level, regardless of whether you will actually become a full-time functional programmer.
But we’re not talking about how to learn to meditate now; we’re talking about how to learn a very useful tool that will make you a better programmer.
Overall, what are the real practical advantages of using functional programming?
Cleaner code
Functional programming is cleaner, simpler, and smaller. It simplifies debugging, testing and maintenance.
For example, we need a function that converts a two-dimensional array into a one-dimensional array. If we only used imperative techniques, we would write it like this:
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 }
Now using functional techniques, it can be written like this:
merge2dArrayIntoOne2 = (arrays) -> arrays.reduce (memo, item) -> memo.concat item , []
var merge2dArrayIntoOne2 = function(arrays) { return arrays.reduce( function(p,n){ return p.concat(n); }, []); };
译注:原著中代码有误,调用reduce函数时少了第二个参数空数组,这里已经补上。
Both functions take the same inputs and return the same output, but the functional example is cleaner.
Modular
Functional programming forces you to break large problems into smaller cases that solve the same problem, which means the code will be more modular. Modular programs have clearer descriptions, are easier to debug, and are simpler to maintain. Testing will also be easier because each module's code can be independently checked for correctness.
Reusability
Due to its modular nature, functional programming has many common auxiliary functions. You'll find that many of the functions here can be reused in a number of different applications.
In the following chapters, many of the most common functions will be covered. However, as a functional programmer, you will inevitably write your own library of functions that will be used again and again. For example, a function used to search configuration files between rows can also be used to search a Hash table if it is well designed.
Reduce coupling
Coupling is a large number of dependencies between modules in a program. Since functional programming follows the writing of first-class, higher-order pure functions, which have no side effects on global variables and are completely independent of each other, coupling is greatly reduced. Of course, functions will inevitably depend on each other, but changing one function will not affect the others, as long as the one-to-one mapping of inputs and outputs remains correct.
Mathematical Correctness
The last point is more theoretical. Because it is rooted in lambda calculus, functional programming can be mathematically proven correct. This is a huge advantage for some researchers who need programs to prove growth rates, time complexity, and mathematical correctness.
Let’s take a look at the Fibonacci Sequence. Although it's rarely used outside of proof-of-concept problems, it's a great way to explain the concept. The standard way to evaluate a Fibonacci sequence is to create a recursive function, like this:
fibonnaci(n) = fibonnaci(n-2) + fibonnaci(n–1)
You also need to add a general situation:
return 1 when n < 2
This allows the recursion to terminate and allow each step in the recursive call stack to accumulate from here.
Detailed steps are listed below
var fibonacci = function(n) { if (n < 2) { return 1; }else { return fibonacci(n - 2) + fibonacci(n - 1); } } console.log( fibonacci(8) ); // Output: 34
However, with the help of a lazy execution function library, it is possible to generate an infinite sequence by defining the members of the entire sequence through mathematical equations. Only those members we ultimately need are calculated at the end.
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);
如果有一个函数式编程的工具库,代码可以进一步被简化:
The way to determine whether a function can be written in a more functional way is to look for loops and temporary variables, such as the "words" and "count" variables in the previous example. We can often replace loops and temporary variables with higher-order functions, which we will explore later in this chapter.
Is Javascript a functional programming language?
Now there is one last question we need to ask ourselves, is Javascript a functional language or a non-functional language?
Javascript is arguably the most popular but least understood functional programming language in the world. Javascript is a functional programming language dressed in C. Its syntax is definitely similar to C, which means it uses C's block syntax and infix word order. And it has the worst name of any living language. You don't have to imagine it to see how many people are confused by the relationship between Javascript and Java, as if its name hints at what it will be! But actually it has very little in common with Java. However, there are still some ideas to force Javascript into an object-oriented language. For example, libraries such as Dojo and ease.js have done a lot of work to try to abstract Javascript to make it suitable for object-oriented programming. Javascript comes from the 1990s, when the world was clamoring for object-oriented programming. We were told that Javascript was an object-oriented language because we wanted it to be, but in fact it was not.
Its true identity can be traced back to its prototypes: Scheme and Lisp, two classic functional programming languages. Javascript has always been a functional programming language. Its functions are first-class citizens and can be nested, it has closures and composite functions, and it allows rationalization and monads. All of these are key to functional programming. Here are some more reasons why Javascript is a functional language:
• Javascript's lexicon includes the ability to pass functions as parameters, has a type inference system, supports anonymous functions, higher-order functions, closures, etc. These characteristics are crucial to the structure and behavior that make up functional programming.
• Javascript is not a pure object-oriented language. Most of its object-oriented design patterns are completed by copying Prototype objects. This is a weak object-oriented programming model. European Computer Manufacturers Association Script (ECMAScript) - the official form and standard implementation of Javascript - has the following statement in version 4.2.1 of the specification:
"Javascript does not have real classes like C, Smalltalk, and Java, but it supports constructors for creating objects. Generally speaking, in class-based object-oriented languages, state is carried by instances, methods are carried by classes, and inheritance is only for Structure and behavior. In EMACScript, state and methods are carried by objects, and structure, behavior and state are inherited. ”
• Javascript is an interpreted language. Javascript's interpreter (sometimes called an "engine") is very similar to Scheme's interpreter. They are both dynamic, both have flexible data types that are easy to compose and transport, both evaluate code into expression blocks, and both handle functions in a similar way.
In other words, Javascript is indeed not a purely functional language. It lacks lazy evaluation and built-in immutable data. This is due to the fact that most interpreters are called by name rather than on demand. Javascript is also not very good at handling recursion due to the way it handles tail calls. But all of these issues can be mitigated with a few minor considerations. Non-strict evaluation that requires infinite sequences and lazy evaluation can be achieved through a library called Lazy.js. Immutability can be achieved simply through programming skills, but it is not restricted by relying on the language level but requires programmer self-discipline. Tail recursion elimination can be achieved through a method called Trampolining. These issues will be explained in Chapter 6.
There have always been many debates about whether Javascript is a functional language, an object-oriented language, both, or neither, and these debates will continue.
Finally, functional programming is a way of writing concise code through clever changes, combinations, and use of functions. And Javascript provides a good way to achieve this. If you really want to tap into the full potential of Javascript, you have to learn how to use it as a functional language.