cainiaoblog 23.2.171
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CNAME +1 -0
- package/MP_verify_o6dHCK5mIkxNpzvZ.txt +1 -0
- package/README.md +8 -0
- package/about/index.html +54 -0
- package/ads.txt +1 -0
- package/app/manual.html +104 -0
- package/appConfigs/config +1 -0
- package/archives/2018/index.html +48 -0
- package/archives/2019/index.html +48 -0
- package/archives/2019/page/2/index.html +48 -0
- package/archives/2020/index.html +48 -0
- package/archives/2021/index.html +48 -0
- package/archives/2022/index.html +48 -0
- package/archives/2023/index.html +48 -0
- package/archives/index.html +48 -0
- package/archives/page/2/index.html +48 -0
- package/archives/page/3/index.html +48 -0
- package/article/10047.html +148 -0
- package/article/10164.html +64 -0
- package/article/10624.html +436 -0
- package/article/10745.html +586 -0
- package/article/11674.html +71 -0
- package/article/11939.html +72 -0
- package/article/12238.html +86 -0
- package/article/1230.html +533 -0
- package/article/13265.html +69 -0
- package/article/13823.html +78 -0
- package/article/14125.html +72 -0
- package/article/14192.html +561 -0
- package/article/14375.html +85 -0
- package/article/14744.html +83 -0
- package/article/14936.html +451 -0
- package/article/1518.html +250 -0
- package/article/15428.html +71 -0
- package/article/15518.html +288 -0
- package/article/15569.html +418 -0
- package/article/1566.html +122 -0
- package/article/16017.html +156 -0
- package/article/16947.html +440 -0
- package/article/17096.html +82 -0
- package/article/17246.html +89 -0
- package/article/17575.html +350 -0
- package/article/179.html +757 -0
- package/article/18039.html +233 -0
- package/article/18071.html +375 -0
- package/article/18142.html +71 -0
- package/article/19909.html +160 -0
- package/article/20014.html +89 -0
- package/article/20021.html +389 -0
- package/article/20037.html +280 -0
- package/article/20056.html +95 -0
- package/article/20093.html +404 -0
- package/article/2021.html +607 -0
- package/article/20459.html +115 -0
- package/article/20613.html +263 -0
- package/article/20635.html +71 -0
- package/article/21317.html +71 -0
- package/article/21320.html +90 -0
- package/article/21782.html +548 -0
- package/article/22230.html +435 -0
- package/article/22237.html +347 -0
- package/article/22398.html +109 -0
- package/article/22422.html +270 -0
- package/article/22565.html +300 -0
- package/article/23046.html +250 -0
- package/article/23083.html +215 -0
- package/article/23195.html +297 -0
- package/article/23301.html +835 -0
- package/article/23589.html +285 -0
- package/article/23655.html +338 -0
- package/article/23864.html +76 -0
- package/article/23914.html +96 -0
- package/article/24169.html +135 -0
- package/article/24581.html +349 -0
- package/article/2475.html +500 -0
- package/article/24826.html +77 -0
- package/article/24924.html +247 -0
- package/article/25164.html +1495 -0
- package/article/25318.html +394 -0
- package/article/25710.html +1129 -0
- package/article/25734.html +83 -0
- package/article/26083.html +174 -0
- package/article/26147.html +283 -0
- package/article/26291.html +110 -0
- package/article/26354.html +71 -0
- package/article/26624.html +145 -0
- package/article/26639.html +298 -0
- package/article/26850.html +138 -0
- package/article/26940.html +258 -0
- package/article/27796.html +190 -0
- package/article/27800.html +161 -0
- package/article/28963.html +159 -0
- package/article/28976.html +247 -0
- package/article/29042.html +72 -0
- package/article/29596.html +81 -0
- package/article/29619.html +183 -0
- package/article/29623.html +329 -0
- package/article/29829.html +205 -0
- package/article/29abbd1c.html +112 -0
- package/article/30215.html +178 -0
- package/article/30552.html +78 -0
- package/article/30818.html +201 -0
- package/article/31330.html +364 -0
- package/article/3154.html +577 -0
- package/article/31695.html +348 -0
- package/article/31826.html +71 -0
- package/article/31843.html +132 -0
- package/article/32280.html +212 -0
- package/article/32431.html +74 -0
- package/article/32443.html +72 -0
- package/article/32732.html +122 -0
- package/article/3286.html +251 -0
- package/article/32960.html +198 -0
- package/article/33713.html +72 -0
- package/article/33899.html +615 -0
- package/article/34351.html +74 -0
- package/article/34372.html +89 -0
- package/article/34410.html +74 -0
- package/article/34431.html +118 -0
- package/article/3447.html +75 -0
- package/article/34474.html +142 -0
- package/article/34871.html +147 -0
- package/article/35397.html +175 -0
- package/article/3544.html +199 -0
- package/article/35503.html +115 -0
- package/article/35624.html +84 -0
- package/article/35697.html +384 -0
- package/article/35863.html +73 -0
- package/article/36037.html +253 -0
- package/article/36118.html +71 -0
- package/article/3621.html +119 -0
- package/article/36251.html +296 -0
- package/article/36642.html +352 -0
- package/article/37102.html +763 -0
- package/article/37336.html +351 -0
- package/article/37351.html +89 -0
- package/article/3760.html +294 -0
- package/article/37749.html +75 -0
- package/article/37789.html +83 -0
- package/article/38005.html +765 -0
- package/article/38009.html +284 -0
- package/article/38269.html +194 -0
- package/article/38344.html +88 -0
- package/article/38359.html +334 -0
- package/article/38423.html +379 -0
- package/article/38530.html +213 -0
- package/article/38743.html +268 -0
- package/article/38753.html +204 -0
- package/article/38881.html +71 -0
- package/article/39087.html +414 -0
- package/article/39309.html +84 -0
- package/article/39486.html +74 -0
- package/article/39637.html +306 -0
- package/article/39839.html +72 -0
- package/article/39937.html +152 -0
- package/article/39970.html +71 -0
- package/article/402.html +559 -0
- package/article/40698.html +71 -0
- package/article/40811.html +71 -0
- package/article/40964.html +462 -0
- package/article/41011.html +322 -0
- package/article/41152.html +78 -0
- package/article/41696.html +71 -0
- package/article/41717.html +281 -0
- package/article/41777.html +149 -0
- package/article/419.html +985 -0
- package/article/4233.html +136 -0
- package/article/4292.html +86 -0
- package/article/43302.html +96 -0
- package/article/43397.html +106 -0
- package/article/43724.html +281 -0
- package/article/43999.html +224 -0
- package/article/44426.html +77 -0
- package/article/44964.html +137 -0
- package/article/45004.html +123 -0
- package/article/4510.html +83 -0
- package/article/45156.html +320 -0
- package/article/45157.html +52 -0
- package/article/45267.html +87 -0
- package/article/45641.html +207 -0
- package/article/45960.html +75 -0
- package/article/46100.html +1262 -0
- package/article/46366.html +74 -0
- package/article/46771.html +96 -0
- package/article/46911.html +517 -0
- package/article/46948.html +137 -0
- package/article/46ee4850.html +349 -0
- package/article/47019.html +84 -0
- package/article/47532.html +94 -0
- package/article/4783.html +429 -0
- package/article/47972.html +72 -0
- package/article/48216.html +127 -0
- package/article/48252.html +185 -0
- package/article/48520.html +74 -0
- package/article/48722.html +75 -0
- package/article/48807.html +99 -0
- package/article/49126.html +323 -0
- package/article/50001.html +88 -0
- package/article/50058.html +151 -0
- package/article/50343.html +410 -0
- package/article/50510.html +82 -0
- package/article/51082.html +98 -0
- package/article/51677.html +111 -0
- package/article/5204.html +1085 -0
- package/article/52500.html +538 -0
- package/article/52689.html +259 -0
- package/article/52877.html +569 -0
- package/article/53125.html +191 -0
- package/article/53428.html +251 -0
- package/article/53524.html +74 -0
- package/article/53698.html +322 -0
- package/article/53803e6.html +93 -0
- package/article/53924.html +291 -0
- package/article/53962.html +253 -0
- package/article/54006.html +71 -0
- package/article/54018.html +88 -0
- package/article/54161.html +83 -0
- package/article/54178.html +69 -0
- package/article/54258.html +327 -0
- package/article/54295.html +214 -0
- package/article/54313.html +1976 -0
- package/article/54351.html +90 -0
- package/article/54604.html +80 -0
- package/article/54632.html +71 -0
- package/article/54912.html +756 -0
- package/article/54918.html +129 -0
- package/article/55119.html +136 -0
- package/article/55756.html +118 -0
- package/article/55769.html +136 -0
- package/article/55870.html +161 -0
- package/article/56139.html +92 -0
- package/article/56557.html +69 -0
- package/article/56604.html +69 -0
- package/article/56816.html +266 -0
- package/article/56926.html +153 -0
- package/article/57747.html +409 -0
- package/article/57965.html +475 -0
- package/article/58816.html +78 -0
- package/article/59059.html +435 -0
- package/article/59095.html +350 -0
- package/article/59349.html +244 -0
- package/article/59402.html +216 -0
- package/article/59448.html +55 -0
- package/article/59870.html +94 -0
- package/article/59877.html +208 -0
- package/article/5dbb6f41.html +86 -0
- package/article/6008.html +75 -0
- package/article/60327.html +126 -0
- package/article/6035.html +76 -0
- package/article/60428.html +71 -0
- package/article/60544.html +128 -0
- package/article/61319.html +95 -0
- package/article/61367.html +93 -0
- package/article/624d1918.html +292 -0
- package/article/62543.html +71 -0
- package/article/62887.html +169 -0
- package/article/63074.html +120 -0
- package/article/63266.html +311 -0
- package/article/63351.html +74 -0
- package/article/63542.html +104 -0
- package/article/63824.html +275 -0
- package/article/64051.html +653 -0
- package/article/64189b69.html +72 -0
- package/article/64354.html +75 -0
- package/article/64869.html +79 -0
- package/article/64923.html +596 -0
- package/article/65128.html +95 -0
- package/article/65295.html +79 -0
- package/article/6888.html +75 -0
- package/article/7065.html +84 -0
- package/article/7455.html +85 -0
- package/article/7a3ef8f.html +315 -0
- package/article/8140.html +188 -0
- package/article/8190.html +85 -0
- package/article/8225.html +78 -0
- package/article/84c9ccd6.html +159 -0
- package/article/8f9a9ae3.html +82 -0
- package/article/9174.html +73 -0
- package/article/9738.html +294 -0
- package/article/a4145266.html +71 -0
- package/article/bedea419.html +72 -0
- package/article/bf7e7421.html +78 -0
- package/article/c1867fbf.html +103 -0
- package/article/c5576490.html +70 -0
- package/article/dcb0db28.html +76 -0
- package/article/fc3b727a.html +79 -0
- package/atom.xml +445 -0
- package/baidusitemap.xml +1079 -0
- package/blocks/dns.txt +264 -0
- package/blocks/filter.txt +1070 -0
- package/blocks/script.js +42 -0
- package/categories/CDN/index.html +48 -0
- package/categories/IDE/index.html +48 -0
- package/categories/git/index.html +48 -0
- package/categories/hexo/index.html +48 -0
- package/categories//345/205/254/345/205/261/350/212/202/346/227/245/index.html +48 -0
- package/categories//345/210/206/344/272/253/index.html +48 -0
- package/categories//345/211/215/347/253/257/index.html +48 -0
- package/categories//345/211/215/347/253/257/page/2/index.html +48 -0
- package/categories//345/211/215/347/253/257//346/265/217/350/247/210/345/231/250/index.html +48 -0
- package/categories//345/211/215/347/253/257//346/265/217/350/247/210/345/231/250//347/275/221/347/273/234/345/215/217/350/256/256/index.html +48 -0
- package/categories//345/215/232/345/256/242/index.html +48 -0
- package/categories//345/267/245/345/205/267/index.html +48 -0
- package/categories//345/271/277/345/221/212/350/277/207/346/273/244/index.html +48 -0
- package/categories//345/271/277/345/221/212/350/277/207/346/273/244//350/247/206/351/242/221/347/224/265/350/247/206/345/260/217/350/257/264/346/274/253/347/224/273/350/265/204/350/256/257/351/237/263/344/271/220/index.html +48 -0
- package/categories//346/255/243/345/210/231/index.html +48 -0
- package/categories//346/265/217/350/247/210/345/231/250/index.html +48 -0
- package/categories//347/256/227/346/263/225/index.html +48 -0
- package/categories//350/265/204/350/256/257/index.html +48 -0
- package/categories//350/275/273/346/235/276/344/270/200/345/210/273/index.html +48 -0
- package/cdn/css/font-awesome.min.css +4 -0
- package/cdn/css/grids-responsive-min.css +7 -0
- package/cdn/css/normalize.css +349 -0
- package/cdn/css/pure-min.css +11 -0
- package/cdn/fonts/fontawesome-webfont.eot +0 -0
- package/cdn/fonts/fontawesome-webfont.svg +2671 -0
- package/cdn/fonts/fontawesome-webfont.ttf +0 -0
- package/cdn/fonts/fontawesome-webfont.woff +0 -0
- package/cdn/fonts/fontawesome-webfont.woff2 +0 -0
- package/cdn/js/Valine.min.js +17 -0
- package/cdn/js/canvas-nest.js +1 -0
- package/cdn/js/clipboard.min.js +7 -0
- package/cdn/js/crypto-js.js +25 -0
- package/cdn/js/jquery.min.js +2 -0
- package/css/default.css +1144 -0
- package/css/donate.css +338 -0
- package/css/hbe.style.css +749 -0
- package/css/style.css +2095 -0
- package/donate/index.html +39 -0
- package/google917dcf72f6e5c7f5.html +1 -0
- package/guestbook/index.html +68 -0
- package/hosts/ads.txt +149 -0
- package/hosts/flash.txt +9 -0
- package/hosts/github.txt +6 -0
- package/hosts/google.txt +16936 -0
- package/hosts/winrar.txt +2 -0
- package/img/alipay.svg +46 -0
- package/img/bitcoin.svg +135 -0
- package/img/blog.ico +0 -0
- package/img/blog.png +0 -0
- package/img/gaba.png +0 -0
- package/img/github.svg +1 -0
- package/img/like.svg +1 -0
- package/img/mp-mini.jpg +0 -0
- package/img/mp.png +0 -0
- package/img/paypal.svg +63 -0
- package/img/upy_logo.svg +59 -0
- package/img/wechat.svg +49 -0
- package/index.html +48 -0
- package/jd_root.txt +1 -0
- package/js/codeblock-resizer.js +51 -0
- package/js/dark.js +12 -0
- package/js/donate.js +87 -0
- package/js/fixedPage.js +106 -0
- package/js/gitment.browser.js +3751 -0
- package/js/search.js +86 -0
- package/js/share.js +60 -0
- package/js/smartresize.js +32 -0
- package/js/totop.js +12 -0
- package/kankan/index.html +77 -0
- package/lib/hbe.js +293 -0
- package/package.json +25 -0
- package/page/10/index.html +49 -0
- package/page/11/index.html +59 -0
- package/page/12/index.html +67 -0
- package/page/13/index.html +63 -0
- package/page/14/index.html +66 -0
- package/page/15/index.html +60 -0
- package/page/16/index.html +60 -0
- package/page/17/index.html +62 -0
- package/page/18/index.html +61 -0
- package/page/19/index.html +58 -0
- package/page/2/index.html +48 -0
- package/page/20/index.html +57 -0
- package/page/21/index.html +61 -0
- package/page/22/index.html +55 -0
- package/page/23/index.html +64 -0
- package/page/24/index.html +60 -0
- package/page/25/index.html +58 -0
- package/page/26/index.html +48 -0
- package/page/27/index.html +58 -0
- package/page/3/index.html +48 -0
- package/page/4/index.html +48 -0
- package/page/5/index.html +51 -0
- package/page/6/index.html +51 -0
- package/page/7/index.html +51 -0
- package/page/8/index.html +53 -0
- package/page/9/index.html +48 -0
- package/randomcall/README.html +1 -0
- package/randomcall/lucker.html +290 -0
- package/randomcall/lucky.png +0 -0
- package/robots.txt +22 -0
- package/search.xml +6740 -0
- package/sitemap.xml +2669 -0
- package/tags//345/216/237/345/210/233/index.html +48 -0
- package/tags//345/250/261/344/271/220/index.html +48 -0
- package/tags//346/211/213/345/206/231/index.html +48 -0
- package/tags//347/226/253/346/203/205/index.html +48 -0
- package/tags//350/275/254/350/275/275/index.html +48 -0
- package/tags//350/275/254/350/275/275/page/2/index.html +48 -0
- package/v.html +90 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
<!DOCTYPE html><html lang="zh-CN" data-dark><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport"><meta content="yes" name="apple-mobile-web-app-capable"><meta content="black-translucent" name="apple-mobile-web-app-status-bar-style"><meta content="telephone=no" name="format-detection"><meta name="description" content="前端壹菜鸟,记录工作难题,关注前端知识,收集精彩博文,做技术的搬运工"><meta name="msvalidate.01" content="0FE4D8B3381D3D87088996B886E1E2BD"><title>函数式编程入门教程 | 前端壹菜鸟</title><link rel="stylesheet" type="text/css" href="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/css/style.min.css"><link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/normalize.css@latest/normalize.min.css"><link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/purecss@latest/build/pure-min.min.css"><link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/purecss@latest/build/grids-responsive-min.css"><link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css"><link rel="icon" mask="" sizes="any" href="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/blog.ico"><link rel="Shortcut Icon" type="image/x-icon" href="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/blog.ico"><link rel="apple-touch-icon" href="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/blog.png"><link rel="apple-touch-icon-precomposed" href="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/blog.png"><link rel="alternate" type="application/atom+xml" href="/atom.xml"><script type="text/javascript" src="//cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"></script><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8385136408348258"></script><script src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" async></script><script>(function(){
|
|
2
|
+
var bp = document.createElement('script');
|
|
3
|
+
var curProtocol = window.location.protocol.split(':')[0];
|
|
4
|
+
if (curProtocol === 'https'){
|
|
5
|
+
bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
|
|
6
|
+
}
|
|
7
|
+
else{
|
|
8
|
+
bp.src = 'http://push.zhanzhang.baidu.com/push.js';
|
|
9
|
+
}
|
|
10
|
+
var s = document.getElementsByTagName("script")[0];
|
|
11
|
+
s.parentNode.insertBefore(bp, s);
|
|
12
|
+
})();
|
|
13
|
+
</script><script>(function(){
|
|
14
|
+
var src = 'https://jspassport.ssl.qhimg.com/11.0.1.js?d182b3f28525f2db83acfaaf6e696dba';
|
|
15
|
+
document.write('<script src="' + src + '" id="sozz"><\/script>');
|
|
16
|
+
})();
|
|
17
|
+
</script><script>var _hmt=_hmt||[];(function(){var hm=document.createElement("script");hm.src='https://hm.baidu.com/hm.js?011f0b44e8452bfa57fbfa23c5fe7683';var s=document.getElementsByTagName("script")[0];s.parentNode.insertBefore(hm, s);})();
|
|
18
|
+
</script><script async src="https://www.googletagmanager.com/gtag/js?id=UA-150860401-1"></script><script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);};gtag('js',new Date());gtag('config','UA-150860401-1');
|
|
19
|
+
</script><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/fixedPage.min.js"></script><meta name="generator" content="Hexo 6.3.0"></head><body><div class="body_container"><div id="header"><div class="site-name"><a id="logo" href="/.">前端壹菜鸟</a><p class="description">关注前端知识,收集精彩博文,做技术的搬运工</p></div><div id="nav-menu"><a class="current" href="/." target="_self"><i class="fa fa-home"> 首页</i></a><a href="/archives/" target="_self"><i class="fa fa-archive"> 归档</i></a><a href="/guestbook/" target="_self"><i class="fa fa-comments"> 留言</i></a><a href="/about/" target="_self"><i class="fa fa-user"> 关于</i></a><a href="https://laonongmin.online/" target="_target"><i class="fa fa-group"> 看看</i></a><a href="https://love.xuehuayu.cn/" target="_target"><i class="fa fa-heart"> LOVE</i></a></div></div><div class="pure-g" id="layout"><div class="pure-u-1 pure-u-md-3-4"><div class="content_container"><div class="post"><h1 class="post-title c-post">函数式编程入门教程</h1><div class="post-meta"><span class="date">2020-01-08</span><span> | </span><span class="tag"><a href="/tags/%E8%BD%AC%E8%BD%BD/">转载 </a></span><span> | </span><span class="category"><a href="/categories/%E5%89%8D%E7%AB%AF/">前端 </a></span><span id="busuanzi_container_page_pv"> | <span id="busuanzi_value_page_pv"><i class="fa fa-spinner"></i></span><span> 阅读</span></span><span class="post-time"><span class="post-meta-item-text"> | </span><span class="post-meta-item-icon"><i class="fa fa-keyboard-o"></i><span class="post-count"> 4k</span><span class="post-meta-item-text"> 字</span></span></span><span class="post-time"> | <span class="post-meta-item-icon"><i class="fa fa-hourglass-half"></i><span class="post-count"> 15</span><span class="post-meta-item-text"> 分钟</span></span></span></div><a class="disqus-comment-count" href="/article/14936.html#vcomment"><span class="waline-comment-count" data-path="/article/14936.html"></span><span> 条评论</span></a><div class="post-content"><blockquote>
|
|
20
|
+
<p>作者: 阮一峰<br><code>原文链接:http://www.ruanyifeng.com/blog/2017/02/fp-tutorial.html</code></p>
|
|
21
|
+
</blockquote>
|
|
22
|
+
<hr>
|
|
23
|
+
<p>你可能听说过函数式编程(Functional programming),甚至已经使用了一段时间。</p>
|
|
24
|
+
<p>但是,你能说清楚,它到底是什么吗?</p>
|
|
25
|
+
<span id="more"></span>
|
|
26
|
+
|
|
27
|
+
<p><img src="http://www.ruanyifeng.com/blogimg/asset/2017/bg2017022201.jpg" alt="img"></p>
|
|
28
|
+
<p>网上搜索一下,你会轻松找到好多答案。</p>
|
|
29
|
+
<blockquote>
|
|
30
|
+
<ul>
|
|
31
|
+
<li>与面向对象编程(Object-oriented programming)和过程式编程(Procedural programming)并列的编程范式。</li>
|
|
32
|
+
<li>最主要的特征是,函数是<a target="_blank" rel="noopener" href="https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/ch2.html">第一等公民</a>。</li>
|
|
33
|
+
<li>强调将计算过程分解成可复用的函数,典型例子就是<code>map</code>方法和<code>reduce</code>方法组合而成 <a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/MapReduce">MapReduce 算法</a>。</li>
|
|
34
|
+
<li>只有<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E7%BA%AF%E5%87%BD%E6%95%B0">纯的</a>、没有<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E5%87%BD%E6%95%B0%E5%89%AF%E4%BD%9C%E7%94%A8">副作用</a>的函数,才是合格的函数。</li>
|
|
35
|
+
</ul>
|
|
36
|
+
</blockquote>
|
|
37
|
+
<p>上面这些说法都对,但还不够,都没有回答下面这个更深层的问题。</p>
|
|
38
|
+
<p><img src="http://www.ruanyifeng.com/blogimg/asset/2017/bg2017022202.jpg" alt="xuehuayu.cn"></p>
|
|
39
|
+
<blockquote>
|
|
40
|
+
<p><strong>为什么要这样做?</strong></p>
|
|
41
|
+
</blockquote>
|
|
42
|
+
<p>这就是,本文要解答的问题。我会通过最简单的语言,帮你理解函数式编程,并且学会它那些基本写法。</p>
|
|
43
|
+
<p>需要声明的是,我不是专家,而是一个初学者,最近两年才真正开始学习函数式编程。一直苦于看不懂各种资料,立志要写一篇清晰易懂的教程。下面的内容肯定不够严密,甚至可能包含错误,但是我发现,像下面这样解释,初学者最容易懂。</p>
|
|
44
|
+
<p>另外,本文比较长,阅读时请保持耐心。</p>
|
|
45
|
+
<h2 id="一、范畴论"><a href="#一、范畴论" class="headerlink" title="一、范畴论"></a>一、范畴论</h2><p>函数式编程的起源,是一门叫做范畴论(Category Theory)的数学分支。</p>
|
|
46
|
+
<p><img src="http://www.ruanyifeng.com/blogimg/asset/2017/bg2017022205.jpg" alt="img"></p>
|
|
47
|
+
<p>理解函数式编程的关键,就是理解范畴论。它是一门很复杂的数学,认为世界上所有的概念体系,都可以抽象成一个个的”范畴”(category)。</p>
|
|
48
|
+
<h3 id="1-1-范畴的概念"><a href="#1-1-范畴的概念" class="headerlink" title="1.1 范畴的概念"></a>1.1 范畴的概念</h3><p>什么是范畴呢?</p>
|
|
49
|
+
<p><a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Category_(mathematics)">维基百科</a>的一句话定义如下。</p>
|
|
50
|
+
<blockquote>
|
|
51
|
+
<p>“范畴就是使用箭头连接的物体。”(In mathematics, a category is an algebraic structure that comprises “objects” that are linked by “arrows”. )</p>
|
|
52
|
+
</blockquote>
|
|
53
|
+
<p>也就是说,彼此之间存在某种关系的概念、事物、对象等等,都构成”范畴”。随便什么东西,只要能找出它们之间的关系,就能定义一个”范畴”。</p>
|
|
54
|
+
<p><img src="http://www.ruanyifeng.com/blogimg/asset/2017/bg2017022210.jpg" alt="xuehuayu.cn"></p>
|
|
55
|
+
<p>上图中,各个点与它们之间的箭头,就构成一个范畴。</p>
|
|
56
|
+
<p>箭头表示范畴成员之间的关系,正式的名称叫做”态射”(morphism)。范畴论认为,同一个范畴的所有成员,就是不同状态的”变形”(transformation)。通过”态射”,一个成员可以变形成另一个成员。</p>
|
|
57
|
+
<h3 id="1-2-数学模型"><a href="#1-2-数学模型" class="headerlink" title="1.2 数学模型"></a>1.2 数学模型</h3><p>既然”范畴”是满足某种变形关系的所有对象,就可以总结出它的数学模型。</p>
|
|
58
|
+
<blockquote>
|
|
59
|
+
<ul>
|
|
60
|
+
<li>所有成员是一个集合</li>
|
|
61
|
+
<li>变形关系是函数</li>
|
|
62
|
+
</ul>
|
|
63
|
+
</blockquote>
|
|
64
|
+
<p>也就是说,范畴论是集合论更上层的抽象,简单的理解就是”集合 + 函数”。</p>
|
|
65
|
+
<p>理论上通过函数,就可以从范畴的一个成员,算出其他所有成员。</p>
|
|
66
|
+
<h3 id="1-3-范畴与容器"><a href="#1-3-范畴与容器" class="headerlink" title="1.3 范畴与容器"></a>1.3 范畴与容器</h3><p>我们可以把”范畴”想象成是一个容器,里面包含两样东西。</p>
|
|
67
|
+
<blockquote>
|
|
68
|
+
<ul>
|
|
69
|
+
<li>值(value)</li>
|
|
70
|
+
<li>值的变形关系,也就是函数。</li>
|
|
71
|
+
</ul>
|
|
72
|
+
</blockquote>
|
|
73
|
+
<p>下面我们使用代码,定义一个简单的范畴。</p>
|
|
74
|
+
<blockquote>
|
|
75
|
+
<pre><code>class Category {
|
|
76
|
+
constructor(val) {
|
|
77
|
+
this.val = val;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
addOne(x) {
|
|
81
|
+
return x + 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
</code></pre>
|
|
85
|
+
</blockquote>
|
|
86
|
+
<p>上面代码中,<code>Category</code>是一个类,也是一个容器,里面包含一个值(<code>this.val</code>)和一种变形关系(<code>addOne</code>)。你可能已经看出来了,这里的范畴,就是所有彼此之间相差<code>1</code>的数字。</p>
|
|
87
|
+
<p>注意,本文后面的部分,凡是提到”容器”的地方,全部都是指”范畴”。</p>
|
|
88
|
+
<h3 id="1-4-范畴论与函数式编程的关系"><a href="#1-4-范畴论与函数式编程的关系" class="headerlink" title="1.4 范畴论与函数式编程的关系"></a>1.4 范畴论与函数式编程的关系</h3><p>范畴论使用函数,表达范畴之间的关系。</p>
|
|
89
|
+
<p>伴随着范畴论的发展,就发展出一整套函数的运算方法。这套方法起初只用于数学运算,后来有人将它在计算机上实现了,就变成了今天的”函数式编程”。</p>
|
|
90
|
+
<p><strong>本质上,函数式编程只是范畴论的运算方法,跟数理逻辑、微积分、行列式是同一类东西,都是数学方法,只是碰巧它能用来写程序。</strong></p>
|
|
91
|
+
<p>所以,你明白了吗,为什么函数式编程要求函数必须是纯的,不能有副作用?因为它是一种数学运算,原始目的就是求值,不做其他事情,否则就无法满足函数运算法则了。</p>
|
|
92
|
+
<p>总之,在函数式编程中,函数就是一个管道(pipe)。这头进去一个值,那头就会出来一个新的值,没有其他作用。</p>
|
|
93
|
+
<h2 id="二、函数的合成与柯里化"><a href="#二、函数的合成与柯里化" class="headerlink" title="二、函数的合成与柯里化"></a>二、函数的合成与柯里化</h2><p>函数式编程有两个最基本的运算:合成和柯里化。</p>
|
|
94
|
+
<h3 id="2-1-函数的合成"><a href="#2-1-函数的合成" class="headerlink" title="2.1 函数的合成"></a>2.1 函数的合成</h3><p>如果一个值要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做”函数的合成”(compose)。</p>
|
|
95
|
+
<p><img src="http://www.ruanyifeng.com/blogimg/asset/2017/bg2017022204.png" alt="xuehuayu.cn"></p>
|
|
96
|
+
<p>上图中,<code>X</code>和<code>Y</code>之间的变形关系是函数<code>f</code>,<code>Y</code>和<code>Z</code>之间的变形关系是函数<code>g</code>,那么<code>X</code>和<code>Z</code>之间的关系,就是<code>g</code>和<code>f</code>的合成函数<code>g·f</code>。</p>
|
|
97
|
+
<p>下面就是代码实现了,我使用的是 JavaScript 语言。注意,本文所有示例代码都是简化过的,完整的 Demo 请看《参考链接》部分。</p>
|
|
98
|
+
<p>合成两个函数的简单代码如下。</p>
|
|
99
|
+
<blockquote>
|
|
100
|
+
<pre><code>const compose = function (f, g) {
|
|
101
|
+
return function (x) {
|
|
102
|
+
return f(g(x));
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
</code></pre>
|
|
106
|
+
</blockquote>
|
|
107
|
+
<p>函数的合成还必须满足结合律。</p>
|
|
108
|
+
<p><img src="http://www.ruanyifeng.com/blogimg/asset/2017/bg2017022209.png" alt="img"></p>
|
|
109
|
+
<blockquote>
|
|
110
|
+
<pre><code>compose(f, compose(g, h))
|
|
111
|
+
// 等同于
|
|
112
|
+
compose(compose(f, g), h)
|
|
113
|
+
// 等同于
|
|
114
|
+
compose(f, g, h)
|
|
115
|
+
</code></pre>
|
|
116
|
+
</blockquote>
|
|
117
|
+
<p>合成也是函数必须是纯的一个原因。因为一个不纯的函数,怎么跟其他函数合成?怎么保证各种合成以后,它会达到预期的行为?</p>
|
|
118
|
+
<p>前面说过,函数就像数据的管道(pipe)。那么,函数合成就是将这些管道连了起来,让数据一口气从多个管道中穿过。</p>
|
|
119
|
+
<h3 id="2-2-柯里化"><a href="#2-2-柯里化" class="headerlink" title="2.2 柯里化"></a>2.2 柯里化</h3><p><code>f(x)</code>和<code>g(x)</code>合成为<code>f(g(x))</code>,有一个隐藏的前提,就是<code>f</code>和<code>g</code>都只能接受一个参数。如果可以接受多个参数,比如<code>f(x, y)</code>和<code>g(a, b, c)</code>,函数合成就非常麻烦。 </p>
|
|
120
|
+
<p>这时就需要函数柯里化了。所谓”柯里化”,就是把一个多参数的函数,转化为单参数函数。</p>
|
|
121
|
+
<blockquote>
|
|
122
|
+
<pre><code>// 柯里化之前
|
|
123
|
+
function add(x, y) {
|
|
124
|
+
return x + y;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
add(1, 2) // 3
|
|
128
|
+
|
|
129
|
+
// 柯里化之后
|
|
130
|
+
function addX(y) {
|
|
131
|
+
return function (x) {
|
|
132
|
+
return x + y;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
addX(2)(1) // 3
|
|
137
|
+
</code></pre>
|
|
138
|
+
</blockquote>
|
|
139
|
+
<p>有了柯里化以后,我们就能做到,所有函数只接受一个参数。后文的内容除非另有说明,都默认函数只有一个参数,就是所要处理的那个值。</p>
|
|
140
|
+
<h2 id="三、函子"><a href="#三、函子" class="headerlink" title="三、函子"></a>三、函子</h2><p>函数不仅可以用于同一个范畴之中值的转换,还可以用于将一个范畴转成另一个范畴。这就涉及到了函子(Functor)。</p>
|
|
141
|
+
<h3 id="3-1-函子的概念"><a href="#3-1-函子的概念" class="headerlink" title="3.1 函子的概念"></a>3.1 函子的概念</h3><p>函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。</p>
|
|
142
|
+
<p>它首先是一种范畴,也就是说,是一个容器,包含了值和变形关系。<strong>比较特殊的是,它的变形关系可以依次作用于每一个值,将当前容器变形成另一个容器。</strong></p>
|
|
143
|
+
<p><img src="http://www.ruanyifeng.com/blogimg/asset/2017/bg2017022203.png" alt="xuehuayu.cn"></p>
|
|
144
|
+
<p>上图中,左侧的圆圈就是一个函子,表示人名的范畴。外部传入函数<code>f</code>,会转成右边表示早餐的范畴。</p>
|
|
145
|
+
<p>下面是一张更一般的图。</p>
|
|
146
|
+
<p><img src="http://www.ruanyifeng.com/blogimg/asset/2017/bg2017022211.jpg" alt="xuehuayu.cn"></p>
|
|
147
|
+
<p>上图中,函数<code>f</code>完成值的转换(<code>a</code>到<code>b</code>),将它传入函子,就可以实现范畴的转换(<code>Fa</code>到<code>Fb</code>)。</p>
|
|
148
|
+
<h3 id="3-2-函子的代码实现"><a href="#3-2-函子的代码实现" class="headerlink" title="3.2 函子的代码实现"></a>3.2 函子的代码实现</h3><p>任何具有<code>map</code>方法的数据结构,都可以当作函子的实现。</p>
|
|
149
|
+
<blockquote>
|
|
150
|
+
<pre><code>class Functor {
|
|
151
|
+
constructor(val) {
|
|
152
|
+
this.val = val;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
map(f) {
|
|
156
|
+
return new Functor(f(this.val));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
</code></pre>
|
|
160
|
+
</blockquote>
|
|
161
|
+
<p>上面代码中,<code>Functor</code>是一个函子,它的<code>map</code>方法接受函数<code>f</code>作为参数,然后返回一个新的函子,里面包含的值是被<code>f</code>处理过的(<code>f(this.val)</code>)。</p>
|
|
162
|
+
<p><strong>一般约定,函子的标志就是容器具有<code>map</code>方法。该方法将容器里面的每一个值,映射到另一个容器。</strong></p>
|
|
163
|
+
<p>下面是一些用法的示例。</p>
|
|
164
|
+
<blockquote>
|
|
165
|
+
<pre><code>(new Functor(2)).map(function (two) {
|
|
166
|
+
return two + 2;
|
|
167
|
+
});
|
|
168
|
+
// Functor(4)
|
|
169
|
+
|
|
170
|
+
(new Functor('flamethrowers')).map(function(s) {
|
|
171
|
+
return s.toUpperCase();
|
|
172
|
+
});
|
|
173
|
+
// Functor('FLAMETHROWERS')
|
|
174
|
+
|
|
175
|
+
(new Functor('bombs')).map(_.concat(' away')).map(_.prop('length'));
|
|
176
|
+
// Functor(10)
|
|
177
|
+
</code></pre>
|
|
178
|
+
</blockquote>
|
|
179
|
+
<p>上面的例子说明,函数式编程里面的运算,都是通过函子完成,即运算不直接针对值,而是针对这个值的容器—-函子。函子本身具有对外接口(<code>map</code>方法),各种函数就是运算符,通过接口接入容器,引发容器里面的值的变形。</p>
|
|
180
|
+
<p>因此,<strong>学习函数式编程,实际上就是学习函子的各种运算。</strong>由于可以把运算方法封装在函子里面,所以又衍生出各种不同类型的函子,有多少种运算,就有多少种函子。函数式编程就变成了运用不同的函子,解决实际问题。</p>
|
|
181
|
+
<h2 id="四、of-方法"><a href="#四、of-方法" class="headerlink" title="四、of 方法"></a>四、of 方法</h2><p>你可能注意到了,上面生成新的函子的时候,用了<code>new</code>命令。这实在太不像函数式编程了,因为<code>new</code>命令是面向对象编程的标志。</p>
|
|
182
|
+
<p><strong>函数式编程一般约定,函子有一个<code>of</code>方法,用来生成新的容器。</strong></p>
|
|
183
|
+
<p>下面就用<code>of</code>方法替换掉<code>new</code>。</p>
|
|
184
|
+
<blockquote>
|
|
185
|
+
<pre><code>Functor.of = function(val) {
|
|
186
|
+
return new Functor(val);
|
|
187
|
+
};
|
|
188
|
+
</code></pre>
|
|
189
|
+
</blockquote>
|
|
190
|
+
<p>然后,前面的例子就可以改成下面这样。</p>
|
|
191
|
+
<blockquote>
|
|
192
|
+
<pre><code>Functor.of(2).map(function (two) {
|
|
193
|
+
return two + 2;
|
|
194
|
+
});
|
|
195
|
+
// Functor(4)
|
|
196
|
+
</code></pre>
|
|
197
|
+
</blockquote>
|
|
198
|
+
<p>这就更像函数式编程了。</p>
|
|
199
|
+
<h2 id="五、Maybe-函子"><a href="#五、Maybe-函子" class="headerlink" title="五、Maybe 函子"></a>五、Maybe 函子</h2><p>函子接受各种函数,处理容器内部的值。这里就有一个问题,容器内部的值可能是一个空值(比如<code>null</code>),而外部函数未必有处理空值的机制,如果传入空值,很可能就会出错。</p>
|
|
200
|
+
<blockquote>
|
|
201
|
+
<pre><code>Functor.of(null).map(function (s) {
|
|
202
|
+
return s.toUpperCase();
|
|
203
|
+
});
|
|
204
|
+
// TypeError
|
|
205
|
+
</code></pre>
|
|
206
|
+
</blockquote>
|
|
207
|
+
<p>上面代码中,函子里面的值是<code>null</code>,结果小写变成大写的时候就出错了。</p>
|
|
208
|
+
<p>Maybe 函子就是为了解决这一类问题而设计的。简单说,它的<code>map</code>方法里面设置了空值检查。</p>
|
|
209
|
+
<blockquote>
|
|
210
|
+
<pre><code>class Maybe extends Functor {
|
|
211
|
+
map(f) {
|
|
212
|
+
return this.val ? Maybe.of(f(this.val)) : Maybe.of(null);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
</code></pre>
|
|
216
|
+
</blockquote>
|
|
217
|
+
<p>有了 Maybe 函子,处理空值就不会出错了。</p>
|
|
218
|
+
<blockquote>
|
|
219
|
+
<pre><code>Maybe.of(null).map(function (s) {
|
|
220
|
+
return s.toUpperCase();
|
|
221
|
+
});
|
|
222
|
+
// Maybe(null)
|
|
223
|
+
</code></pre>
|
|
224
|
+
</blockquote>
|
|
225
|
+
<h2 id="六、Either-函子"><a href="#六、Either-函子" class="headerlink" title="六、Either 函子"></a>六、Either 函子</h2><p>条件运算<code>if...else</code>是最常见的运算之一,函数式编程里面,使用 Either 函子表达。</p>
|
|
226
|
+
<p>Either 函子内部有两个值:左值(<code>Left</code>)和右值(<code>Right</code>)。右值是正常情况下使用的值,左值是右值不存在时使用的默认值。</p>
|
|
227
|
+
<blockquote>
|
|
228
|
+
<pre><code>class Either extends Functor {
|
|
229
|
+
constructor(left, right) {
|
|
230
|
+
this.left = left;
|
|
231
|
+
this.right = right;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
map(f) {
|
|
235
|
+
return this.right ?
|
|
236
|
+
Either.of(this.left, f(this.right)) :
|
|
237
|
+
Either.of(f(this.left), this.right);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
Either.of = function (left, right) {
|
|
242
|
+
return new Either(left, right);
|
|
243
|
+
};
|
|
244
|
+
</code></pre>
|
|
245
|
+
</blockquote>
|
|
246
|
+
<p>下面是用法。</p>
|
|
247
|
+
<blockquote>
|
|
248
|
+
<pre><code>var addOne = function (x) {
|
|
249
|
+
return x + 1;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
Either.of(5, 6).map(addOne);
|
|
253
|
+
// Either(5, 7);
|
|
254
|
+
|
|
255
|
+
Either.of(1, null).map(addOne);
|
|
256
|
+
// Either(2, null);
|
|
257
|
+
</code></pre>
|
|
258
|
+
</blockquote>
|
|
259
|
+
<p>上面代码中,如果右值有值,就使用右值,否则使用左值。通过这种方式,Either 函子表达了条件运算。</p>
|
|
260
|
+
<p>Either 函子的常见用途是提供默认值。下面是一个例子。</p>
|
|
261
|
+
<blockquote>
|
|
262
|
+
<pre><code>Either
|
|
263
|
+
.of({address: 'xxx'}, currentUser.address)
|
|
264
|
+
.map(updateField);
|
|
265
|
+
</code></pre>
|
|
266
|
+
</blockquote>
|
|
267
|
+
<p>上面代码中,如果用户没有提供地址,Either 函子就会使用左值的默认地址。</p>
|
|
268
|
+
<p>Either 函子的另一个用途是代替<code>try...catch</code>,使用左值表示错误。</p>
|
|
269
|
+
<blockquote>
|
|
270
|
+
<pre><code>function parseJSON(json) {
|
|
271
|
+
try {
|
|
272
|
+
return Either.of(null, JSON.parse(json));
|
|
273
|
+
} catch (e: Error) {
|
|
274
|
+
return Either.of(e, null);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
</code></pre>
|
|
278
|
+
</blockquote>
|
|
279
|
+
<p>上面代码中,左值为空,就表示没有出错,否则左值会包含一个错误对象<code>e</code>。一般来说,所有可能出错的运算,都可以返回一个 Either 函子。</p>
|
|
280
|
+
<h2 id="七、ap-函子"><a href="#七、ap-函子" class="headerlink" title="七、ap 函子"></a>七、ap 函子</h2><p>函子里面包含的值,完全可能是函数。我们可以想象这样一种情况,一个函子的值是数值,另一个函子的值是函数。</p>
|
|
281
|
+
<blockquote>
|
|
282
|
+
<pre><code>function addTwo(x) {
|
|
283
|
+
return x + 2;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const A = Functor.of(2);
|
|
287
|
+
const B = Functor.of(addTwo)
|
|
288
|
+
</code></pre>
|
|
289
|
+
</blockquote>
|
|
290
|
+
<p>上面代码中,函子<code>A</code>内部的值是<code>2</code>,函子<code>B</code>内部的值是函数<code>addTwo</code>。</p>
|
|
291
|
+
<p>有时,我们想让函子<code>B</code>内部的函数,可以使用函子<code>A</code>内部的值进行运算。这时就需要用到 ap 函子。</p>
|
|
292
|
+
<p>ap 是 applicative(应用)的缩写。凡是部署了<code>ap</code>方法的函子,就是 ap 函子。</p>
|
|
293
|
+
<blockquote>
|
|
294
|
+
<pre><code>class Ap extends Functor {
|
|
295
|
+
ap(F) {
|
|
296
|
+
return Ap.of(this.val(F.val));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
</code></pre>
|
|
300
|
+
</blockquote>
|
|
301
|
+
<p>注意,<code>ap</code>方法的参数不是函数,而是另一个函子。</p>
|
|
302
|
+
<p>因此,前面例子可以写成下面的形式。</p>
|
|
303
|
+
<blockquote>
|
|
304
|
+
<pre><code>Ap.of(addTwo).ap(Functor.of(2))
|
|
305
|
+
// Ap(4)
|
|
306
|
+
</code></pre>
|
|
307
|
+
</blockquote>
|
|
308
|
+
<p>ap 函子的意义在于,对于那些多参数的函数,就可以从多个容器之中取值,实现函子的链式操作。</p>
|
|
309
|
+
<blockquote>
|
|
310
|
+
<pre><code>function add(x) {
|
|
311
|
+
return function (y) {
|
|
312
|
+
return x + y;
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
Ap.of(add).ap(Maybe.of(2)).ap(Maybe.of(3));
|
|
317
|
+
// Ap(5)
|
|
318
|
+
</code></pre>
|
|
319
|
+
</blockquote>
|
|
320
|
+
<p>上面代码中,函数<code>add</code>是柯里化以后的形式,一共需要两个参数。通过 ap 函子,我们就可以实现从两个容器之中取值。它还有另外一种写法。</p>
|
|
321
|
+
<blockquote>
|
|
322
|
+
<pre><code>Ap.of(add(2)).ap(Maybe.of(3));
|
|
323
|
+
</code></pre>
|
|
324
|
+
</blockquote>
|
|
325
|
+
<h2 id="八、Monad-函子"><a href="#八、Monad-函子" class="headerlink" title="八、Monad 函子"></a>八、Monad 函子</h2><p>函子是一个容器,可以包含任何值。函子之中再包含一个函子,也是完全合法的。但是,这样就会出现多层嵌套的函子。</p>
|
|
326
|
+
<blockquote>
|
|
327
|
+
<pre><code>Maybe.of(
|
|
328
|
+
Maybe.of(
|
|
329
|
+
Maybe.of({name: 'Mulburry', number: 8402})
|
|
330
|
+
)
|
|
331
|
+
)
|
|
332
|
+
</code></pre>
|
|
333
|
+
</blockquote>
|
|
334
|
+
<p>上面这个函子,一共有三个<code>Maybe</code>嵌套。如果要取出内部的值,就要连续取三次<code>this.val</code>。这当然很不方便,因此就出现了 Monad 函子。</p>
|
|
335
|
+
<p><strong>Monad 函子的作用是,总是返回一个单层的函子。</strong>它有一个<code>flatMap</code>方法,与<code>map</code>方法作用相同,唯一的区别是如果生成了一个嵌套函子,它会取出后者内部的值,保证返回的永远是一个单层的容器,不会出现嵌套的情况。</p>
|
|
336
|
+
<blockquote>
|
|
337
|
+
<pre><code>class Monad extends Functor {
|
|
338
|
+
join() {
|
|
339
|
+
return this.val;
|
|
340
|
+
}
|
|
341
|
+
flatMap(f) {
|
|
342
|
+
return this.map(f).join();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
</code></pre>
|
|
346
|
+
</blockquote>
|
|
347
|
+
<p>上面代码中,如果函数<code>f</code>返回的是一个函子,那么<code>this.map(f)</code>就会生成一个嵌套的函子。所以,<code>join</code>方法保证了<code>flatMap</code>方法总是返回一个单层的函子。这意味着嵌套的函子会被铺平(flatten)。</p>
|
|
348
|
+
<h2 id="九、IO-操作"><a href="#九、IO-操作" class="headerlink" title="九、IO 操作"></a>九、IO 操作</h2><p>Monad 函子的重要应用,就是实现 I/O (输入输出)操作。</p>
|
|
349
|
+
<p>I/O 是不纯的操作,普通的函数式编程没法做,这时就需要把 IO 操作写成<code>Monad</code>函子,通过它来完成。</p>
|
|
350
|
+
<blockquote>
|
|
351
|
+
<pre><code>var fs = require('fs');
|
|
352
|
+
|
|
353
|
+
var readFile = function(filename) {
|
|
354
|
+
return new IO(function() {
|
|
355
|
+
return fs.readFileSync(filename, 'utf-8');
|
|
356
|
+
});
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
var print = function(x) {
|
|
360
|
+
return new IO(function() {
|
|
361
|
+
console.log(x);
|
|
362
|
+
return x;
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
</code></pre>
|
|
366
|
+
</blockquote>
|
|
367
|
+
<p>上面代码中,读取文件和打印本身都是不纯的操作,但是<code>readFile</code>和<code>print</code>却是纯函数,因为它们总是返回 IO 函子。</p>
|
|
368
|
+
<p>如果 IO 函子是一个<code>Monad</code>,具有<code>flatMap</code>方法,那么我们就可以像下面这样调用这两个函数。</p>
|
|
369
|
+
<blockquote>
|
|
370
|
+
<pre><code>readFile('./user.txt')
|
|
371
|
+
.flatMap(print)
|
|
372
|
+
</code></pre>
|
|
373
|
+
</blockquote>
|
|
374
|
+
<p>这就是神奇的地方,上面的代码完成了不纯的操作,但是因为<code>flatMap</code>返回的还是一个 IO 函子,所以这个表达式是纯的。我们通过一个纯的表达式,完成带有副作用的操作,这就是 Monad 的作用。</p>
|
|
375
|
+
<p>由于返回还是 IO 函子,所以可以实现链式操作。因此,在大多数库里面,<code>flatMap</code>方法被改名成<code>chain</code>。</p>
|
|
376
|
+
<blockquote>
|
|
377
|
+
<pre><code>var tail = function(x) {
|
|
378
|
+
return new IO(function() {
|
|
379
|
+
return x[x.length - 1];
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
readFile('./user.txt')
|
|
384
|
+
.flatMap(tail)
|
|
385
|
+
.flatMap(print)
|
|
386
|
+
|
|
387
|
+
// 等同于
|
|
388
|
+
readFile('./user.txt')
|
|
389
|
+
.chain(tail)
|
|
390
|
+
.chain(print)
|
|
391
|
+
</code></pre>
|
|
392
|
+
</blockquote>
|
|
393
|
+
<p>上面代码读取了文件<code>user.txt</code>,然后选取最后一行输出。</p>
|
|
394
|
+
<h2 id="十、参考链接"><a href="#十、参考链接" class="headerlink" title="十、参考链接"></a>十、参考链接</h2><ul>
|
|
395
|
+
<li><a target="_blank" rel="noopener" href="https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/">JS 函数式编程指南</a></li>
|
|
396
|
+
<li><a target="_blank" rel="noopener" href="http://mattfield.github.io/javascript/2013/07/28/taking-things-out-of-context-functors-in-javascript/">Taking Things Out of Context: Functors in JavaScript</a></li>
|
|
397
|
+
<li><a target="_blank" rel="noopener" href="https://gist.github.com/buzzdecafe/5721205">Functor.js</a></li>
|
|
398
|
+
<li><a target="_blank" rel="noopener" href="https://gist.github.com/rjchatfield/54393388f80f55fcc81a">Maybe, Either & Try Functors in ES6</a></li>
|
|
399
|
+
<li><a target="_blank" rel="noopener" href="http://rs.io/why-category-theory-matters/">Why Category Theory Matters</a></li>
|
|
400
|
+
</ul>
|
|
401
|
+
<p>(正文完)</p>
|
|
402
|
+
</div><iframe src="/donate/?AliPayQR=https://store.xuehuayu.cn/store/public/alipay_qr.jpg&WeChatQR=https://store.xuehuayu.cn/store/public/wechat_qr.png&UnionPayQR=null&GitHub=null&BTCQR=null&BTCKEY=null&PayPal=https://www.paypal.com/paypalme/OrdinaryPerson" style="overflow-x:hidden; overflow-y:hidden; border:0xp none #fff; min-height:240px; width:100%;" frameborder="0" scrolling="no"></iframe><div><ul class="post-copyright"><li class="post-copyright-author"><strong>本文作者:</strong>雪花雨</li><li class="post-copyright-link"><strong>本文链接:</strong><a href="/article/14936.html">https://xuehuayu.cn/article/14936.html</a></li><li class="post-copyright-license"><strong>版权声明:</strong>① 标为原创的文章为博主原创,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接。② 转载文章来自网络,已标明出处,<a href="mailto:cainiaoblog@126.com">侵删</a>。</li></ul></div><br><div class="tags"><a href="/tags/%E8%BD%AC%E8%BD%BD/">转载</a></div><div class="post-nav"><a class="pre" href="/article/3621.html">如何在url中传一个对象?</a><a class="next" href="/article/45960.html">😇原生JS灵魂之问, 冲刺🚀进阶最后一公里</a></div><div id="vcomment"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@waline/client@v2/dist/waline.min.css"><script src="//cdn.jsdelivr.net/npm/@waline/client@v2/dist/waline.js"></script><script>const origin = window.location.origin
|
|
403
|
+
const serverURL = origin.includes('cainiaoblog') ? 'https://cmts.cainiaoblog.cn' : 'https://cmts.xuehuayu.cn'
|
|
404
|
+
const locale = {
|
|
405
|
+
placeholder: '请正确填写昵称和邮箱,方便接收回复通知~',
|
|
406
|
+
sofa: '沙发空缺中,还不快抢~',
|
|
407
|
+
};
|
|
408
|
+
Waline.init({
|
|
409
|
+
el: '#vcomment',
|
|
410
|
+
serverURL: serverURL,
|
|
411
|
+
pageSize: '20',
|
|
412
|
+
visitor: true, // 阅读量统计
|
|
413
|
+
requiredMeta: ['nick', 'mail'],
|
|
414
|
+
emoji: [
|
|
415
|
+
'https://cdn.jsdelivr.net/gh/walinejs/emojis@1.0.0/weibo',
|
|
416
|
+
'https://cdn.jsdelivr.net/gh/walinejs/emojis@1.0.0/qq',
|
|
417
|
+
'https://cdn.jsdelivr.net/gh/walinejs/emojis@1.0.0/tw-emoji',
|
|
418
|
+
],
|
|
419
|
+
locale,
|
|
420
|
+
pageview: true
|
|
421
|
+
})
|
|
422
|
+
</script></div></div></div><div class="pure-u-1 pure-u-md-1-4 fixed-search hidden_mid_and_down"><div id="sidebar"><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/search.min.js"></script><div class="widget"><div class="widget-search"><input class="search" type="radio" name="search" value="baidu" id="baidu" checked="checked"/><label class="label" for="baidu" title="百度全站搜索">百度</label><input class="search" type="radio" name="search" value="google" id="google"/><label class="label" for="google" title="谷歌全站搜索">谷歌</label><input class="search" type="radio" name="search" value="self" id="self"/><label class="label" for="self" title="使用站内搜索">站内</label></div><div class="widget" id="search"><form class="search-form" action="//www.baidu.com/baidu" method="get" accept-charset="utf-8" target="_blank"><input type="search" name="word" maxlength="30" placeholder="百度全站搜索"/><input type="hidden" name="si" value="xuehuayu.cn"/><input type="hidden" name="cl" value="3"/><input type="hidden" name="ct" value="2097152"/><input type="hidden" name="s" value="on"/><input class="search-submit" type="submit" value=""/></form></div></div><script>$('input[type=radio][name=search]').change(function() {
|
|
423
|
+
var val = $(this).val()
|
|
424
|
+
var self = '<div class="search-form"><input id="local-search-input" placeholder="站内搜索,首次慢" type="search" name="q" results="0"><input class="search-submit" type="submit" value=""/><div id="local-search-result"></div></div>'
|
|
425
|
+
var google = '<form class="search-form" action="//www.google.com/search" method="get" accept-charset="utf-8" target="_blank"><input type="search" name="q" maxlength="30" placeholder="谷歌全站搜索"><input type="hidden" name="sitesearch" value="xuehuayu.cn"><input class="search-submit" type="submit" value=""/></form>'
|
|
426
|
+
var baidu = '<form class="search-form" action="//www.baidu.com/baidu" method="get" accept-charset="utf-8" target="_blank"><input type="search" name="word" maxlength="30" placeholder="百度全站搜索"><input type="hidden" name="si" value="xuehuayu.cn"><input type="hidden" name="cl" value="3"><input type="hidden" name="ct" value="2097152"><input type="hidden" name="s" value="on"><input class="search-submit" type="submit" value=""/></form>'
|
|
427
|
+
|
|
428
|
+
if (val === 'self') {
|
|
429
|
+
$('#search').html(self)
|
|
430
|
+
var search_path = 'search.xml';
|
|
431
|
+
if (search_path.length == 0) {
|
|
432
|
+
search_path = 'search.xml';
|
|
433
|
+
}
|
|
434
|
+
var path = 'https://cdn.staticaly.com/gh/npljy/npljy.github.io/main/' + search_path;
|
|
435
|
+
searchFunc(path, 'local-search-input', 'local-search-result');
|
|
436
|
+
} else if (val === 'baidu') {
|
|
437
|
+
$('#search').html(baidu)
|
|
438
|
+
} else if (val === 'google') {
|
|
439
|
+
$('#search').html(google)
|
|
440
|
+
}
|
|
441
|
+
})</script><div class="widget widget-wxmp"><img alt="微信公众号" width="100%" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/mp-mini.jpg"/></div><div class="widget widget-categories"><div class="widget-title"><i class="fa fa-folder-o"> 分类</i></div><ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/CDN/">CDN</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/IDE/">IDE</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/git/">git</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/hexo/">hexo</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%85%AC%E5%85%B1%E8%8A%82%E6%97%A5/">公共节日</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%88%86%E4%BA%AB/">分享</a><span class="category-list-count">29</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%89%8D%E7%AB%AF/">前端</a><span class="category-list-count">191</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%89%8D%E7%AB%AF/%E6%B5%8F%E8%A7%88%E5%99%A8/">浏览器</a><span class="category-list-count">1</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%89%8D%E7%AB%AF/%E6%B5%8F%E8%A7%88%E5%99%A8/%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/">网络协议</a><span class="category-list-count">1</span></li></ul></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%8D%9A%E5%AE%A2/">博客</a><span class="category-list-count">3</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%B7%A5%E5%85%B7/">工具</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%B9%BF%E5%91%8A%E8%BF%87%E6%BB%A4/">广告过滤</a><span class="category-list-count">8</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/%E5%B9%BF%E5%91%8A%E8%BF%87%E6%BB%A4/%E8%A7%86%E9%A2%91%E7%94%B5%E8%A7%86%E5%B0%8F%E8%AF%B4%E6%BC%AB%E7%94%BB%E8%B5%84%E8%AE%AF%E9%9F%B3%E4%B9%90/">视频电视小说漫画资讯音乐</a><span class="category-list-count">2</span></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E6%AD%A3%E5%88%99/">正则</a><span class="category-list-count">5</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E6%B5%8F%E8%A7%88%E5%99%A8/">浏览器</a><span class="category-list-count">5</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E7%AE%97%E6%B3%95/">算法</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E8%B5%84%E8%AE%AF/">资讯</a><span class="category-list-count">16</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/%E8%BD%BB%E6%9D%BE%E4%B8%80%E5%88%BB/">轻松一刻</a><span class="category-list-count">1</span></li></ul></div><div class="widget widget-tags"><div class="widget-title"><i class="fa fa-star-o"> 标签</i></div><div class="tagcloud"><a href="/tags/%E5%8E%9F%E5%88%9B/" style="font-size: 15px;">原创</a> <a href="/tags/%E8%BD%AC%E8%BD%BD/" style="font-size: 15px;">转载</a> <a href="/tags/%E6%89%8B%E5%86%99/" style="font-size: 15px;">手写</a> <a href="/tags/%E7%96%AB%E6%83%85/" style="font-size: 15px;">疫情</a> <a href="/tags/%E5%A8%B1%E4%B9%90/" style="font-size: 15px;">娱乐</a></div></div><div class="widget widget-recent-posts"><div class="widget-title"><i class="fa fa-file-o"> 最近文章</i></div><ul class="post-list"><li class="post-list-item"><a class="post-list-link" href="/article/c5576490.html">面试准备</a></li><li class="post-list-item"><a class="post-list-link" href="/article/84c9ccd6.html">精读《SolidJS》</a></li><li class="post-list-item"><a class="post-list-link" href="/article/53803e6.html">git提交注释规范</a></li><li class="post-list-item"><a class="post-list-link" href="/article/a4145266.html">前端数组拍平flat一行代码</a></li><li class="post-list-item"><a class="post-list-link" href="/article/bedea419.html">一道前端this面试题</a></li><li class="post-list-item"><a class="post-list-link" href="/article/bf7e7421.html">git多账号配置</a></li></ul></div><div class="widget widget-links"><div class="widget-title"><i class="fa fa-external-link"> 友情链接</i></div><ul></ul><a href="https://github.com/npljy?utm_source=xuehuayu.cn" title="GitHub" target="_blank">GitHub</a><ul></ul><a href="https://laonongmin.online?utm_source=xuehuayu.cn" title="看看" target="_blank">看看</a><ul></ul><a href="https://cainiaoblog.cn?utm_source=xuehuayu.cn" title="菜鸟博客" target="_blank">菜鸟博客</a><ul></ul><a href="mailto:cainiaoblog@126.com" title="友链联系" target="_blank">友链联系</a></div></div></div><div class="pure-u-1 pure-u-md-3-4"><div id="footer"><div class="flex-block justify-center align-center flex-wrap"><a class="gxba-link" id="gxba" rel="nofollow" target="_blank" href="http://beian.miit.gov.cn/">京ICP备20007647号-2</a><a class="gaba-link" id="gaba-link" rel="nofollow" target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11010802031264"><img class="nofancybox" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/gaba.png" alt=""/><span id="gaba">京公网安备 11010802031264号</span><span style="padding-right: 10px;"></span></a><span>Copyright © 2023 </span><a href="/." rel="nofollow">前端壹菜鸟. </a><script>(function(){
|
|
442
|
+
var cnb =window.location.origin.indexOf('cainiaoblog') !==-1
|
|
443
|
+
if (cnb) {
|
|
444
|
+
var gxba =document.getElementById('gxba')
|
|
445
|
+
var gaba =document.getElementById('gaba')
|
|
446
|
+
var gabaLink =document.getElementById('gaba-link')
|
|
447
|
+
gxba.innerText ='京ICP备20007647号-1'
|
|
448
|
+
gaba.innerText ='京公网安备 11010802031254号'
|
|
449
|
+
gabaLink.setAttribute('href','http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11010802031254')
|
|
450
|
+
}
|
|
451
|
+
})()</script></div><div class="flex-block justify-center align-center flex-wrap"><a rel="nofollow" target="_blank" href="https://v6.51.la/s/Guz3lwHCsVme9Cu"><div class="busuanzi_container" id="busuanzi_container"><span class="busuanzi_container_site_pv" id="busuanzi_container_site_pv">访问量:<span id="busuanzi_value_site_pv"><i class="fa fa-spinner"></i></span></span><span class="busuanzi_container_site_uv" id="busuanzi_container_site_uv">访客数:<span id="busuanzi_value_site_uv"><i class="fa fa-spinner"></i></span></span><span style="padding-right: 10px;"></span></div></a><a rel="nofollow" target="_blank" href="https://www.upyun.com/?utm_source=lianmeng&utm_medium=referral"><span style="font-weight:bold;letter-spacing: 1px;">本网站由 </span><img class="nofancybox" height="32" style="vertical-align: middle" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/img/upy_logo.min.svg" alt=""/><span style="font-weight:bold;letter-spacing: 1px;">提供CDN加速/云存储服务</span></a><span style="padding-right: 10px;"></span><a rel="nofollow" style="font-size:18px;font-weight:bold;vertical-align: middle" target="_blank" title="注册就送代金券可直接使用" href="https://console.upyun.com/register/?invite=HyDsjZHIL">注册</a></div></div></div></div><a class="show" id="rocket" href="#top"></a><div class="darkmode-toggle">🌓</div><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/totop.min.js" async></script><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/dark.min.js" async></script><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/codeblock-resizer.min.js"></script><script type="text/javascript" src="//cdn.staticaly.com/gh/npljy/npljy.github.io/main/js/smartresize.min.js"></script></div></body></html>
|