ホームページ >ウェブフロントエンド >jsチュートリアル >JavaScript 関数プログラミング (3)
2 番目の記事では、Maybe、Either、IO などのいくつかの一般的な Functor を紹介しました。おそらく 2 番目の記事を読んだ多くの人は疑問を持つでしょう。卵? 』
実際、副作用が少ない関数型コードの書き方を学びたいだけなら、最初の記事を読めば十分です。ここの 2 番目の記事と 3 番目の記事は、いくつかの機能理論の実践に焦点を当てています。はい、多くのホイールがすでに構築されており、実装は非常に簡単であるため、これらを実際の運用に適用するのは困難です (ただし不可能ではありません)。たとえば、現在フロントエンドで大規模に使用されている Promise の非同期呼び出し仕様は、実際には Monad の一種です (後で説明します)。ますます成熟している Redux は
Fluxのバリアントとして実装されています。そしてその中心となる概念はステートマシンと関数プログラミングでもあります。 1. Monad
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 はモナドの一種である」とだけ覚えて、この章を飛ばしてください。 関数
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的内容"))
ここではファイルの読み取りと印刷という 2 つの IO が関係しているため、最終的には 2 つの IO 層が得られます。これを実行したい場合は、
cat("file").__value().__value(); //=> 读取文件并打印到控制台
恥ずかしいですよね? 100 個の IO 操作を行う場合、100 個の __value() を連続して記述する必要がありますか?
もちろん、それほど洗練されていないはずはありません。join メソッドを実装しましょう。その機能は、Functor の層を剥がしてコンテンツを公開することです。
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 メソッドを使用すると、もう少しエレガントになります。
var cat = compose(join, map(print), readFile); cat("file").__value(); //=> 读取文件并打印到控制台
メソッドはファンクターをフラット化できます。この機能を持つファンクターを一般にモナドと呼びます。 ここでは Functor パッケージングの層を非常に単純に削除していますが、エレガントな
プログラマとして、map の後に join を常に手動で呼び出して余分なパッケージを削除することはできません。そうしないと、コードが長くなります。次のようになります: var doSomething = compose(join, map(f), join, map(g), join, map(h));
したがって、期待するチェーン呼び出しを実現するには、chain と呼ばれるメソッドが必要です。これは、冗長なパッケージを削除するために、map を呼び出した後に自動的に join を呼び出します。これは、Monad の主要な機能でもあります。
はは、お気づきかもしれませんが、
chainはPromiseの
thenに似ていませんか?はい、確かにそれらの動作は一貫しています (
then
(本来は Promise を例にして以下にいくつかの例を書きたかったのですが、ここを見ている人なら誰でもさまざまな Promise チェーンを上手に書けるはずなので、0w0 は書きません) 要するに、 Monad を使用すると、入れ子地獄を回避し、IO やその他の非同期タスクなど、深く入れ子になった関数型プログラミングを簡単に実行できます。 2. 関数型プログラミングの応用
さて、関数型プログラミングの基本理論の紹介はこれで終わりです。さらに詳しく知りたい場合は、Haskell や Lisp などのより本格的な関数型言語を学ぶことをお勧めします。質問に答えてみましょう: 実際のアプリケーションにおける関数型プログラミングの用途は何ですか?1. React
React が人気の理由を尋ねたい場合、「パフォーマンスが良い」、「クールな」、「サードパーティのコンポーネントが豊富である」と言う人もいるかもしれません。しかし、これらは最も重要なことではありません。最も重要なことは、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)!