두 번째 글에서는 Maybe, Either, IO, 어쩌면 등 몇 가지 일반적인 Functor를 소개했습니다. 두 번째 기사에는 의심이 생길 것입니다.
"이런 것들이 무슨 소용이 있습니까?" 』
사실 부작용이 적은 기능적 코드 작성을 배우고 싶다면 첫 번째 기사를 읽는 것으로 충분합니다. 두 번째 기사와 세 번째 기사는 몇 가지 기능적 이론 실습에 초점을 맞추고 있습니다. 예, 많은 휠이 이미 제작되어 구현하기가 매우 쉽기 때문에 실제 생산에 적용하기는 어렵습니다(그러나 불가능하지는 않습니다). 예를 들어, 현재 프런트 엔드에서 널리 사용되는 Promise의 비동기 호출 사양은 실제로 일종의 Monad입니다(나중에 논의할 예정). 점점 더 성숙해지는 Redux는 Flux의 변형으로 구현됩니다. , 핵심 개념도 상태 머신과 함수형 프로그래밍입니다.
모나드에 대한 소개와 튜토리얼이 인터넷에 넘쳐나는데, 아래 글보다 더 좋은 글이 많아서 여기서는 간단하게 모나드를 소개하겠습니다. -물론, 이해하기 쉽다는 단점은 엄격하지 않다는 점 양해해주세요/w
Promise의 사양을 알고 계시다면 놀라운 기능을 기억하셔야 합니다. Promise:
doSomething() .then(result => { // 你可以return一个Promise链! return fetch('url').then(result => parseBody(result)); }) .then(result => { // 这里的result是上面那个Promise的终值 }) doSomething() .then(result => { // 也可以直接return一个具体的值! return 123; }) .then(result => { // result === 123 })
Promise의 콜백 함수의 경우 값을 직접 반환하거나 새 Promise를 반환할 수 있지만 후속 콜백 함수의 경우 둘 다 동일합니다. 이는 오랫동안 비판을 받아온 nodejs의 중첩 지옥을 깔끔하게 해결합니다.
사실 Promise는 일종의 모나드입니다. 예, 매일 많은 Promise를 작성해야 할 수도 있지만, 지금까지는 매일 사용하는 이것이 실제로 함수형이라는 사실을 몰랐습니다. 매우 높게 들리는 개념.
이제 실제로 모나드를 구현해 보겠습니다. 읽고 싶지 않다면 "Promise는 일종의 Monad입니다"만 기억하고 이 장을 건너뛰세요.
cat 함수를 작성해 보겠습니다. 이 함수의 기능은 Linux 명령줄의 cat과 동일합니다. 파일을 읽어서 내용을 인쇄합니다. 여기에서 IO 구현에 대해서는 이전 기사를 참조하세요:
import fs from 'fs'; import _ from 'lodash'; var map = _.curry((f, x) => x.map(f)); var compose = _.flowRight; var readFile = function(filename) { return new IO(_ => fs.readFileSync(filename, 'utf-8')); }; var print = function(x) { return new IO(_ => { console.log(x); return x; }); } var cat = compose(map(print), readFile); cat("file") //=> IO(IO("file的内容"))
여기에는 두 개의 IO가 관련되어 있으므로(파일 읽기 및 인쇄) 최종 결과는 두 개를 얻습니다. IO 레이어를 실행하려면
cat("file").__value().__value(); //=> 读取文件并打印到控制台
만 호출하면 됩니다. 100개의 IO 작업이 필요하다면, 그렇게 해야 할까요? 100 __value( )를 쓰시겠습니까?
물론 우아하지 않을 수는 없습니다. 조인 메서드를 구현해 보겠습니다. 그 기능은 Functor의 레이어를 벗겨내고 내용을 우리에게 노출시키는 것입니다.
rree조인 메서드를 사용한 후입니다. , 좀 더 우아하게:
var join = x => x.join(); IO.prototype.join = function() { return this.__value ? IO.of(null) : this.__value(); } // 试试看 var foo = IO.of(IO.of('123')); foo.join(); //=> IO('123')
join 메소드는 Functor를 평면화할 수 있습니다(평탄화). , 우리는 일반적으로 이 기능을 갖춘 Functor를 Monad라고 부릅니다.
Functor 패키징 레이어를 아주 간단하게 제거하는 방법이 있지만, 우아한 프로그래머로서 map 🎜>join 중복된 패키징을 제거합니다. 그렇지 않으면 코드는 다음과 같습니다: var cat = compose(join, map(print), readFile);
cat("file").__value();
//=> 读取文件并打印到控制台
그래서 우리가 기대하는 연결 호출을 달성하려면 chain이라는 메서드가 필요합니다. 이 메서드는 map 다음에 호출됩니다. 자동으로 Join을 호출하여 중복된 패키징을 제거합니다. 는 Monad의 주요 기능이기도 합니다.
var doSomething = compose(join, map(f), join, map(g), join, map(h));
하하, 나오는 게 보이시죠?
은 Promise의 then과 유사합니까? 예, 실제로 동작이 일관됩니다(그러면에는 좀 더 많은 논리가 있고 중첩 수준 수를 기록하고 Promise와 일반 반환 값을 구별합니다). Promise는 실제로 기능적인 Thought입니다. (원래는 Promise를 예로 들어 아래에 몇 가지 예시를 작성하고 싶었는데, 여기 보시는 분들은 다양한 Promise 체인을 능숙하게 작성할 수 있을 것 같아서 0w0은 쓰지 않겠습니다.)
간단히 말하면 Monad를 사용하면 중첩 지옥을 피하고 IO 및 기타 비동기 작업과 같이 깊게 중첩된 함수형 프로그래밍을 쉽게 수행할 수 있습니다.
2. 함수형 프로그래밍의 응용
이제 리액트가 왜 인기가 있냐고 물으신다면 '성능이 좋다', '멋지다'라고 말씀하실 수도 있겠네요. ", " "풍부한 타사 구성 요소", "신규" 등이 있지만 이것이 가장 중요한 것은 아닙니다. 가장 중요한 것은 React가 기능 및 상태 기계라는 새로운 개념을 프런트 엔드 개발에 도입한다는 것입니다.
React에서 "순수 컴포넌트"를 작성하는 방법을 살펴보겠습니다.
var Text = props => ( <p style={props.style}>{props.text}</p> )
咦这不就是纯函数吗?对于任意的 text 输入,都会产生唯一的固定输出,只不过这个输出是一个 virtual DOM 的元素罢了。配合状态机,就大大简化了前端开发的复杂度:
state => virtual DOM => 真实 DOM
在 Redux 中更是可以把核心逻辑抽象成一个纯函数 reducer:
reducer(currentState, action) => newState
关于 React+Redux(或者其它FLUX架构)就不在这里介绍太多了,有兴趣的可以参考相关的教程。
2、Rxjs
Rxjs 从诞生以来一直都不温不火,但它函数响应式编程(Functional Reactive Programming,FRP)的理念非常先进,虽然或许对于大部分应用环境来说,外部输入事件并不是太频繁,并不需要引入一个如此庞大的 FRP 体系,但我们也可以了解一下它有哪些优秀的特性。
在 Rxjs 中,所有的外部输入(用户输入、网络请求等等)都被视作一种 『事件流』:
--- 用户点击了按钮 --> 网络请求成功 --> 用户键盘输入 --> 某个定时事件发生 --> ......
举个最简单的例子,下面这段代码会监听点击事件,每 2 次点击事件产生一次事件响应:
var clicks = Rx.Observable .fromEvent(document, 'click') .bufferCount(2) .subscribe(x => console.log(x)); // 打印出前2次点击事件
其中 bufferCount 对于事件流的作用是这样的:
是不是很神奇呢?Rxjs 非常适合游戏、编辑器这种外部输入极多的应用,比如有的游戏可能有『搓大招』这个功能,即监听用户一系列连续的键盘、鼠标输入,比如上上下下左右左右BABA,不用事件流的思想的话,实现会非常困难且不优雅,但用 Rxjs 的话,就只是维护一个定长队列的问题而已:
var inputs = []; var clicks = Rx.Observable .fromEvent(document, 'keydown') .scan((acc, cur) => { acc.push(cur.keyCode); var start = acc.length - 12 < 0 ? 0 : acc.length - 12; return acc.slice(start); }, inputs) .filter(x => x.join(',') == [38, 38, 40, 40, 37, 39, 37, 39, 66, 65, 66, 65].join(','))// 上上下下左右左右BABA,这里用了比较奇技淫巧的数组对比方法 .subscribe(x => console.log('!!!!!!ACE!!!!!!'));
当然,Rxjs 的作用远不止于此,但可以从这个范例里看出函数响应式编程的一些优良的特性。
3、Cycle.js
Cycle.js 是一个基于 Rxjs 的框架,它是一个彻彻底底的 FRP 理念的框架,和 React 一样支持 virtual DOM、JSX 语法,但现在似乎还没有看到大型的应用经验。
本质的讲,它就是在 Rxjs 的基础上加入了对 virtual DOM、容器和组件的支持,比如下面就是一个简单的『开关』按钮:
import xs from 'xstream'; import {run} from '@cycle/xstream-run'; import {makeDOMDriver} from '@cycle/dom'; import {html} from 'snabbdom-jsx'; function main(sources) { const sinks = { DOM: sources.DOM.select('input').events('click') .map(ev => ev.target.checked) .startWith(false) .map(toggled => <p> <input type="checkbox" /> Toggle me <p>{toggled ? 'ON' : 'off'}</p> </p> ) }; return sinks; } const drivers = { DOM: makeDOMDriver('#app') }; run(main, drivers);
当然,Cycle.js 这种『侵入式』的框架适用性不是太广,因为使用它就意味着应用中必须全部或者大部分都要围绕它的理念设计,这对于大规模应用来说反而是负担。
既然是完结篇,那我们来总结一下这三篇文章究竟讲了些啥?
第一篇文章里,介绍了纯函数、柯里化、Point Free、声明式代码和命令式代码的区别,你可能忘记得差不多了,但只要记住『函数对于外部状态的依赖是造成系统复杂性大大提高的主要原因』以及『让函数尽可能地纯净』就行了。
第二篇文章,或许是最没有也或许是最有干货的一篇,里面介绍了『容器』的概念和 Maybe、Either、IO 这三个强大的 Functor。是的,大多数人或许都没有机会在生产环境中自己去实现这样的玩具级 Functor,但通过了解它们的特性会让你产生对于函数式编程的意识。
软件工程上讲『没有银弹』,函数式编程同样也不是万能的,它与烂大街的 OOP 一样,只是一种编程范式而已。很多实际应用中是很难用函数式去表达的,选择 OOP 亦或是其它编程范式或许会更简单。但我们要注意到函数式编程的核心理念,如果说 OOP 降低复杂度是靠良好的封装、继承、多态以及接口定义的话,那么函数式编程就是通过纯函数以及它们的组合、柯里化、Functor 等技术来降低系统复杂度,而 React、Rxjs、Cycle.js 正是这种理念的代言人,这可能是大势所趋,也或许是昙花一现,但不妨碍我们去多掌握一种编程范式嘛0w0
以上就是JavaScript函数式编程(三)的内容,更多相关内容请关注PHP中文网(www.php.cn)!