함수형 프로그래밍은 수학적 순수성과 수수께끼 같은 특성으로 인해 수십 년 동안 컴퓨터 공학 애호가들 사이에서 가장 인기가 높았으며, 데이터 과학자와 박사 과정 지원자들은 컴퓨터 연구실에 묻혀 있었습니다. 그러나 이제는 Python, Julia, Ruby, Clojure 및 Javascript와 같은 현대 언어 덕분에 르네상스를 경험하고 있습니다.
자바스크립트를 말씀하시는 건가요? 이 웹 스크립팅 언어? 좋아요!
자바스크립트는 오랫동안 사라지지 않은 중요한 기술임이 입증되었습니다. 이는 주로 backbone.js, jQuery, Dojo, underscore.js 등과 같이 확장하는 일부 프레임워크 및 라이브러리로 인해 다시 태어날 수 있는 기능 때문입니다. 이는 함수형 프로그래밍 언어로서의 Javascript의 진정한 정체성과 직접적인 관련이 있습니다. Javascript의 함수형 프로그래밍에 대한 이해는 중요하며 모든 수준의 프로그래머에게 꽤 오랫동안 유용할 것입니다.
왜요? 함수형 프로그래밍은 매우 강력하고 견고하며 우아합니다. 대규모 데이터 구조에 매우 유용하고 효율적입니다. 클라이언트 측 스크립팅 언어인 Javascript는 DOM을 기능적으로 조작하고, API 응답을 구성하고, 점점 더 복잡해지는 웹사이트를 처리할 때 기타 작업을 완료하는 데 매우 유용합니다.
이 책에서는 함수형 프로그래밍으로 Javascript 웹 애플리케이션을 구축하는 방법, Javascript의 숨겨진 기능을 활용하는 방법, 더욱 강력한 코드를 작성하는 방법 등 Javascript의 함수형 프로그래밍에 대해 알아야 할 모든 것을 배우게 됩니다. 프로그램이 더 작기 때문에 코드를 유지 관리하기가 더 쉽고, 더 빨리 다운로드할 수 있으며, 비용도 더 저렴합니다. 또한 함수형 프로그래밍의 핵심 개념과 이를 Javascript에 적용하는 방법, Javascript를 함수형 언어로 사용할 때 몇 가지 문제를 피하는 방법, Javascript에서 함수형 프로그래밍과 객체지향 프로그래밍을 혼합하는 방법을 배우게 됩니다.
하지만 시작하기 전에 실험을 해보자.
예
아마도 Javascript에서 함수형 프로그래밍을 소개하는 가장 좋은 방법은 빠른 예제를 사용하는 것입니다. 우리는 Javascript로 몇 가지 작업을 수행할 것입니다. 하나는 전통적인 기본 접근 방식을 사용하고 다른 하나는 함수형 프로그래밍을 사용합니다. 그런 다음 이 두 가지 방법을 비교해 보겠습니다.
신청 - 전자상거래 사이트
리얼리즘을 추구하기 위해 전자상거래 홈페이지, 통신판매 커피빈 회사를 만들 예정입니다. 이 웹사이트에서는 다양한 품질과 가격을 지닌 여러 종류의 커피를 판매합니다.
명령적 방법
먼저 프로그램 작성을 시작합니다. 이 예제를 실용적으로 만들려면 데이터를 보관할 몇 가지 개체를 만들어야 합니다. 필요한 경우 데이터베이스에서 값을 가져올 수 있습니다. 그러나 이제는 정적으로 정의되었다고 가정합니다.
// create some objects to store the data. var columbian = {  name: 'columbian', basePrice: 5 }; var frenchRoast = { name: 'french roast', basePrice: 8 }; var decaf = { name: 'decaf', basePrice: 6 }; // 我们将使用辅助函数计算价格 // 根据size打印到一个HTML的列表中 function printPrice(coffee, size) { if (size == 'small') { var price = coffee.basePrice + 2; } else if (size == 'medium') { var price = coffee.basePrice + 4; } else { var price = coffee.basePrice + 6; } // create the new html list item var node = document.createElement("li"); var label = coffee.name + ' ' + size; var textnode = document.createTextNode(label+' price: $'+price); node.appendChild(textnode); document.getElementById('products').appendChild(node); } // 现在我们只需根据咖啡的各种价格和size的组合调用printPrice函数 printPrice(columbian, 'small'); printPrice(columbian, 'medium'); printPrice(columbian, 'large'); printPrice(frenchRoast, 'small'); printPrice(frenchRoast, 'medium'); printPrice(frenchRoast, 'large'); printPrice(decaf, 'small'); printPrice(decaf, 'medium'); printPrice(decaf, 'large');
보시다시피 이 코드는 매우 기본적입니다. 이제 이 세 가지보다 더 많은 종류의 커피가 있다면 어떨까요? 20명, 심지어 50명이라면 어떨까요? 사이즈가 더 많으면 어떻게 되나요? 유기물과 무기물이 있다면 어떨까요? 이렇게 하면 엄청난 양의 코드가 빠르게 추가됩니다!
이 방법을 사용하면 기계가 모든 커피 종류와 크기를 인쇄할 수 있습니다. 이것이 이 명령형 접근 방식을 취하는 데 있어 기본적인 문제입니다.
함수형 프로그래밍
명령형 코드는 문제를 해결하기 위해 수행해야 할 작업을 단계별로 컴퓨터에 알려주는 반면, 함수형 프로그래밍은 문제를 수학적으로 설명하고 나머지는 컴퓨터가 수행하도록 합니다.
보다 기능적인 방법으로 동일한 애플리케이션을 다음과 같이 작성할 수 있습니다.
// 从接口中分解数据和逻辑 var printPrice = function(price, label) { var node = document.createElement("li"); var textnode = document.createTextNode(label+' price: $'+price); node.appendChild(textnode); document.getElementById('products 2').appendChild(node); } // 为每种咖啡创建函数对象 var columbian = function(){ this.name = 'columbian'; this.basePrice = 5; }; var frenchRoast = function(){ this.name = 'french roast'; this.basePrice = 8; }; var decaf = function(){ this.name = 'decaf'; this.basePrice = 6; }; // 为每种size通过字面量创建对象 var small = { getPrice: function(){return this.basePrice + 2}, getLabel: function(){return this.name + ' small'} }; var medium = { getPrice: function(){return this.basePrice + 4}, getLabel: function(){return this.name + ' medium'} }; var large = { getPrice: function(){return this.basePrice + 6}, getLabel: function(){return this.name + ' large'} }; // 将所有咖啡的种类和size放到数组里 var coffeeTypes = [columbian, frenchRoast, decaf]; var coffeeSizes = [small, medium, large]; // 创建由上面内容组成的新对象,并把它们放到一个新数组里 var coffees = coffeeTypes.reduce(function(previous, current) { var newCoffee = coffeeSizes.map(function(mixin) { // `plusmix`是函数式的minxin, 见第7章 var newCoffeeObj = plusMixin(current, mixin); return new newCoffeeObj(); }); return previous.concat(newCoffee); },[]); // 现在我们已经定义了如何获得所有咖啡种类和size组合方式的价格,现在可以直接打印它们了 coffees.forEach(function(coffee){ printPrice(coffee.getPrice(),coffee.getLabel()); });
먼저 명확하게 해야 할 점은 이 코드가 더 모듈화되어 있다는 것입니다. 이제 크기를 추가하거나 커피 유형을 추가하는 것은 다음 코드만큼 간단합니다.
var peruvian = function(){ this.name = 'peruvian'; this.basePrice = 11; }; var extraLarge = { getPrice: function(){return this.basePrice + 10}, getLabel: function(){return this.name + ' extra large'} }; coffeeTypes.push(Peruvian); coffeeSizes.push(extraLarge);
咖啡对象的数组和size对象的数组混合(mix)到了一起,也就是他们的方法和成员变量被组合到了一块儿 ——通过一个叫“plusMinxin”的自定义函数(详见第七章)。这些咖啡类型的类(columbian, frenchRoast, decaf)包含了成员变量, 而这些size对象(small, medium, large)包含了获取名称和计算价格的方法。 ”混合”(minxing)这个动作通过一个map操作来起作用,也就是对数组中的每一个成员执行一个纯函数并返回一个新的函数, 然后这些返回的函数被放到了一个reduce函数中被操作,reduce也是一个高阶函数,和map有些像, 只是reduce把数组里的所有元素处理后组合到了一个东西里面。最终,新的数组包含了所有可能的种类和size的组合, 这个数组通过forEach方法遍历,forEach也是一个高阶函数,它会让数组里面每一个对象作为参数执行一遍回调函数。 在这个例子里,这个回调函数是一个匿名函数,它获取这些对象后,以对象的getPrice()和getLabel() 两个方法的返回值作为参数调用printPrice函数。
实际上,我们可以让这个例子更加函数式:去掉coffees变量,并将函数串到一起链式调用,这也是函数式编程的一个小技巧。
coffeeTypes.reduce(function(previous, current) { var newCoffee = coffeeSizes.map(function(mixin) { // `plusMixin` function for functional mixins, see Ch.7 var newCoffeeObj = plusMixin(current, mixin); return new newCoffeeObj(); }); return previous.concat(newCoffee); },[]).forEach(function(coffee) { printPrice(coffee.getPrice(),coffee.getLabel()); });
这样,控制流没有像命令式代码那样从头到尾的顺序进行。在函数式编程里,map函数和其它高阶函数代替了for和while循环, 只有少量关键的代码是在顺序执行。 这使得新接触的人在阅读这样范式的代码有些困难,但是一旦你能够欣赏它,你就会发现这根本没啥难的, 而且这样写看起来更好。
这个例子仅仅是刚开始展露Javascript中函数式编程能做什么。通过这本书,你将会看到更多函数式实现的强悍的例子。
总结
首先,采用函数式风格的优点已经明确了。 其次,不要害怕函数式编程。的确,它往往被认为是编程语言的纯逻辑形式,但是我们不需要理解lambda演算也能够在日常任务中应用它。 实际上,通过把我们的程序拆分成小的片段,它们变得更容易被理解、维护,也更加可靠。 map和reduce函数是Javascript中不太被知道的内建函数,然而我们将要关注它们。
Javascript是一个脚本语言,可交互,易使用,不需要编译。我们甚至不需要下载任何开发软件, 你最喜欢的浏览器就可以作为开发环境的解释器。
感兴趣吗?好,我们开始!