日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

合理的使用纯函数式编程

發(fā)布時(shí)間:2025/3/19 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 合理的使用纯函数式编程 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文是篇譯文,原文鏈接An Introduction to Reasonably Pure Functional Programming,不當(dāng)之處還請(qǐng)指正。

一個(gè)好的程序員應(yīng)該有能力掌控你寫的代碼,能夠以最簡(jiǎn)單的方法使你的代碼正確并且可讀。作為一名優(yōu)秀的程序員,你會(huì)編寫盡量短小的函數(shù),使代碼更好的被復(fù)用;你會(huì)編寫測(cè)試代碼,使自己有足夠的信心相信代碼會(huì)按原本的意圖正確運(yùn)行。沒有人喜歡解bug,所以一名優(yōu)秀的程序員也要會(huì)避免一些錯(cuò)誤,這些要靠經(jīng)驗(yàn)獲得,也可以遵循一些最佳實(shí)踐,比如Douglas Crockford 最著名的JavaScript:The good parts

函數(shù)式編程能夠降低程序的復(fù)雜程度:函數(shù)看起來就像是一個(gè)數(shù)學(xué)公式。學(xué)習(xí)函數(shù)編程能夠幫助你編寫簡(jiǎn)單并且更少bug的代碼。

純函數(shù)

純函數(shù)可以理解為一種 相同的輸入必定有相同的輸出的函數(shù),沒有任何可以觀察到副作用

//pure function add(a + b) {return a + b; }

上面是一個(gè)純函數(shù),它不依賴也不改變?nèi)魏魏瘮?shù)以外的變量狀態(tài),對(duì)于相同的輸入總能返回相同的輸出。

//impure var minimum = 21; var checkAge = function(age) {return age >= minimum; // 如果minimum改變,函數(shù)結(jié)果也會(huì)改變 }

這個(gè)函數(shù)不是純函數(shù),因?yàn)樗蕾囃獠靠勺兊臓顟B(tài)

如果我們將變量移到函數(shù)內(nèi)部,那么它就變成了純函數(shù),這樣我們就能夠保證函數(shù)每次都能正確的比較年齡。

var checkAge = function(age) {var minimum = 21;return age >= minimum; };

純函數(shù)沒有副作用,一些你要記住的是,它不會(huì):

  • 訪問函數(shù)以外的系統(tǒng)狀態(tài)

  • 修改以參數(shù)形式傳遞過來的對(duì)象

  • 發(fā)起http請(qǐng)求

  • 保留用戶輸入

  • 查詢DOM

控制增變(controlled mutation)

你需要留意一些會(huì)改變數(shù)組和對(duì)象的增變方法,舉例來說你要知道splice和slice之間的差異。

//impure, splice 改變了原數(shù)組 var firstThree = function(arr) {return arr.splice(0,3); }//pure, slice 返回了一個(gè)新數(shù)組 var firstThree = function(arr) {return arr.slice(0,3); }

如果我們避免使用傳入函數(shù)的對(duì)象的增變方法,我們的程序?qū)⒏菀桌斫?#xff0c;我們也有理由期望我們的函數(shù)不會(huì)改變?nèi)魏魏瘮?shù)之外的東西。

let items = ['a', 'b', 'c']; let newItems = pure(items); //對(duì)于純函數(shù)items始終應(yīng)該是['a', 'b', 'c']

純函數(shù)的優(yōu)點(diǎn)

相比于不純的函數(shù),純函數(shù)有如下優(yōu)點(diǎn):

  • 更加容易被測(cè)試,因?yàn)樗鼈兾ㄒ坏穆氊?zé)就是根據(jù)輸入計(jì)算輸出

  • 結(jié)果可以被緩存,因?yàn)橄嗤妮斎肟倳?huì)獲得相同的輸出

  • 自我文檔化,因?yàn)楹瘮?shù)的依賴關(guān)系很清晰

  • 更容易被調(diào)用,因?yàn)槟悴挥脫?dān)心函數(shù)會(huì)有什么副作用

因?yàn)榧兒瘮?shù)的結(jié)果可以被緩存,我們可以記住他們,這樣以來復(fù)雜昂貴的操作只需要在被調(diào)用時(shí)執(zhí)行一次。例如,緩存一個(gè)大的查詢索引的結(jié)果可以極大的改善程序的性能。

不合理的純函數(shù)編程

使用純函數(shù)能夠極大的降低程序的復(fù)雜度。但是,如果我們使用過多的函數(shù)式編程的抽象概念,我們的函數(shù)式編程也會(huì)非常難以理解。

import _ from 'ramda'; import $ from 'jquery';var Impure = {getJSON: _.curry(function(callback, url) {$.getJSON(url, callback);}),setHtml: _.curry(function(sel, html) {$(sel).html(html);}) };var img = function (url) {return $('<img />', { src: url }); };var url = function (t) {return 'http://api.flickr.com/services/feeds/photos_public.gne?tags=' +t + '&format=json&jsoncallback=?'; };var mediaUrl = _.compose(_.prop('m'), _.prop('media')); var mediaToImg = _.compose(img, mediaUrl); var images = _.compose(_.map(mediaToImg), _.prop('items')); var renderImages = _.compose(Impure.setHtml("body"), images); var app = _.compose(Impure.getJSON(renderImages), url); app("cats");

花一分鐘理解上面的代碼。

除非你接觸過函數(shù)式編程的這些概念(柯里化,組合和prop),否則很難理解上述代碼。相比于純函數(shù)式的方法,下面的代碼則更加容易理解和修改,它更加清晰的描述程序并且更少的代碼。

  • app函數(shù)的參數(shù)是一個(gè)標(biāo)簽字符串

  • 從Flickr獲取JSON數(shù)據(jù)

  • 從返回的數(shù)據(jù)里抽出urls

  • 創(chuàng)建<img>節(jié)點(diǎn)數(shù)組

  • 將他們插入文檔

var app = (tags) => {let url = `http://api.flickr.com/services/feeds/photos_public.gne?tags=${tags}&format=json&jsoncallback=?`;$.getJSON(url, (data) => {let urls = data.items.map((item) => item.media.m)let images = urls.map(url) => $('<img />', {src:url}) );$(document.body).html(images);}) } app("cats");

或者可以使用fetchPromise來更好的進(jìn)行異步操作。

let flickr = (tags)=> {let url = `http://api.flickr.com/services/feeds/photos_public.gne?tags=${tags}&format=json&jsoncallback=?`return fetch(url).then((resp)=> resp.json()).then((data)=> {let urls = data.items.map((item)=> item.media.m )let images = urls.map((url)=> $('<img />', { src: url }) )return images}) } flickr("cats").then((images)=> {$(document.body).html(images) })

Ajax請(qǐng)求和DOM操作都不是純的,但是我們可以將余下的操作組成純函數(shù),將返回的JSON數(shù)據(jù)轉(zhuǎn)換成圖片節(jié)點(diǎn)數(shù)組。

let responseToImages = (resp) => {let urls = resp.items.map((item) => item.media.m)let images = urls.map((url) => $('<img />', {src:url}))return images }

我們的函數(shù)做了2件事情:

  • 將返回的數(shù)據(jù)轉(zhuǎn)換成urls

  • 將urls轉(zhuǎn)換成圖片節(jié)點(diǎn)

函數(shù)式的方法是將上述2個(gè)任務(wù)拆開,然后使用compose將一個(gè)函數(shù)的結(jié)果作為參數(shù)傳給另一個(gè)參數(shù)。

let urls = (data) => {return data.items.map((item) => item.media.m) } let images = (urls) => {return urls.map((url) => $('<img />', {src: url})) } let responseToImages = _.compose(images, urls)

compose 返回一系列函數(shù)的組合,每個(gè)函數(shù)都會(huì)將后一個(gè)函數(shù)的結(jié)果作為自己的入?yún)?/p>

這里compose做的事情,就是將urls的結(jié)果傳入images函數(shù)

let responseToImages = (data) => {return images(urls(data)) }

通過將代碼變成純函數(shù),讓我們?cè)谝院笥袡C(jī)會(huì)復(fù)用他們,他們更加容易被測(cè)試和自文檔化。不好的是當(dāng)我們過度的使用這些函數(shù)抽象(像第一個(gè)例子那樣), 就會(huì)使事情變得復(fù)雜,這不是我們想要的。當(dāng)我們重構(gòu)代碼的時(shí)候最重要的是要問一下自己:

這是否讓代碼更加容易閱讀和理解?

基本功能函數(shù)

我并不是要詆毀函數(shù)式編程。每個(gè)程序員都應(yīng)該齊心協(xié)力去學(xué)習(xí)基礎(chǔ)函數(shù),這些函數(shù)讓你在編程過程中使用一些抽象出的一般模式,寫出更加簡(jiǎn)潔明了的代碼,或者像Marijn Haverbeke說的

一個(gè)程序員能夠用常規(guī)的基礎(chǔ)函數(shù)武裝自己,更重要的是知道如何使用它們,要比那些苦思冥想的人高效的多。-- Eloquent JavaScript, Marijn Haverbeke

這里列出了一些JavaScript開發(fā)者應(yīng)該掌握的基礎(chǔ)函數(shù)
Arrays
-forEach
-map
-filter
-reduce

Functions
-debounce
-compose
-partial
-curry

Less is More

讓我們來通過實(shí)踐看一下函數(shù)式編程能如何改善下面的代碼

let items = ['a', 'b', 'c']; let upperCaseItems = () => {let arr = [];for (let i=0, ii= items.length; i<ii; i++) {let item = items[i];arr.push(item.toUpperCase());}items = arr; }

共享狀態(tài)來簡(jiǎn)化函數(shù)

這看起來很明顯且微不足道,但是我還是讓函數(shù)訪問和修改了外部的狀態(tài),這讓函數(shù)難以測(cè)試且容易出錯(cuò)。

//pure let upperCaseItems = (items) => {let arr = [];for (let i =0, ii= items.length; i< ii; i++) {let item = items[i];arr.push(item.toUpperCase());}return arr; }

使用更加可讀的語言抽象forEach來迭代

let upperCaseItems = (items) => {let arr = [];items.forEach((item) => {arr.push(item.toUpperCase());})return arr; }

使用map進(jìn)一步簡(jiǎn)化代碼

let upperCaseItems = (items) => {return items.map((item) => item.toUpperCase()) }

進(jìn)一步簡(jiǎn)化代碼

let upperCase = (item) => item.toUpperCase() let upperCaseItems = (item) => items.map(upperCase)

刪除代碼直到它不能工作

我們不需要為這種簡(jiǎn)單的任務(wù)編寫函數(shù),語言本身就提供了足夠的抽象來完成功能

let items = ['a', 'b', 'c'] let upperCaseItems = item.map((item) => item.toUpperCase())

測(cè)試

純函數(shù)的一個(gè)關(guān)鍵優(yōu)點(diǎn)是易于測(cè)試,所以在這一節(jié)我會(huì)為我們之前的Flicker模塊編寫測(cè)試。

我們會(huì)使用Mocha來運(yùn)行測(cè)試,使用Babel來編譯ES6代碼。

mkdir test-harness cd test-harness npm init -y npm install mocha babel-register babel-preset-es2015 --save-dev echo '{ "presets": ["es2015"] }' > .babelrc mkdir test touch test/example.js

Mocha提供了一些好用的函數(shù)如describeit來拆分測(cè)試和鉤子(例如before和after這種用來組裝和拆分任務(wù)的鉤子)。assert是用來進(jìn)行相等測(cè)試的斷言庫(kù),assertassert.deepEqual是很有用且值得注意的函數(shù)。

讓我們來編寫第一個(gè)測(cè)試test/example.js

import assert from 'assert';describe('Math', () => {describe('.floor', () => {it('rounds down to the nearest whole number', () => {let value = Math.floor(4.24)assert(value === 4)})}) })

打開package.json文件,將"test"腳本修改如下

mocha --compilers js:babel-register --recursive

然后你就可以在命令行運(yùn)行npm test

Math.floor? rounds down to the nearest whole number 1 passing (32ms)

Note:如果你想讓mocha監(jiān)視改變,并且自動(dòng)運(yùn)行測(cè)試,可以在上述命令后面加上-w選項(xiàng)。

mocha --compilers js:babel-register --recursive -w

測(cè)試我們的Flicker模塊

我們的模塊文件是lib/flickr.js

import $ from 'jquery'; import { compose } from 'underscore';let urls = (data) => {return data.items.map((item) => item.media.m) }let images = (urls) => {return urls.map((url) => $('<img />', {src: url})[0] ) }let responseToImages = compose(images, urls)let flickr = (tags) => {let url = `http://api.flickr.com/services/feeds/photos_public.gne?tags=${tags}&format=json&jsoncallback=?`return fetch(url).then((response) => reponse.json()).then(responseToImages) }export default {_responseToImages: responseToImages,flickr: flickr }

我們的模塊暴露了2個(gè)方法:一個(gè)公有flickr和一個(gè)私有函數(shù)_responseToImages,這樣就可以獨(dú)立的測(cè)試他們。

我們使用了一組依賴:jquery,underscore和polyfill函數(shù)fetchPromise。為了測(cè)試他們,我們使用jsdom來模擬DOM對(duì)象windowdocument,使用sinon包來測(cè)試fetch api。

npm install jquery underscore whatwg-fetch es6-promise jsdom sinon --save-dev touch test/_setup.js

打開test/_setup.js,使用全局對(duì)象來配置jsdom

global.document = require('jsdom').jsdom('<html></html>'); global.window = document.defaultView; global.$ = require('jquery')(window); global.fetch = require('whatwg-fetch').fetch;

我們的測(cè)試代碼在test/flickr.js,我們將為函數(shù)的輸出設(shè)置斷言。我們"stub"或者覆蓋全局的fetch方法,來阻斷和模擬HTTP請(qǐng)求,這樣我們就可以在不直接訪問Flickr api的情況下運(yùn)行我們的測(cè)試。

import assert from 'assert'; import Flickr from '../lib/flickr'; import sinon from 'sinon'; import { Promise } from 'es6-promise'; import { Response } from 'whatwg-fetch';let sampleResponse = {items: [{media: { m: 'lolcat.jpg' }}, {media: {m: 'dancing_pug.gif'}}] }//實(shí)際項(xiàng)目中我們會(huì)將這個(gè)test helper移到一個(gè)模塊里 let jsonResponse = (obj) => {let json = JSON.stringify(obj);var response = new Response(json, {status: 200,headers: {'Content-type': 'application/json'}});return Promise.resolve(response); }describe('Flickr', () => {describe('._responseToImages', () => {it("maps response JSON to a NodeList of <img>", () => {let images = Flickr._responseToImages(sampleResponse);assert(images.length === 2);assert(images[0].nodeName === 'IMG');assert(images[0].src === 'lolcat.jpg');})})describe('.flickr', () => {//截?cái)鄁etch 請(qǐng)求,返回一個(gè)Promise對(duì)象before(() => {sinon.stub(global, 'fetch', (url) => {return jsonResponse(sampleResponse)})})after(() => {global.fetch.restore();})it("returns a Promise that resolve with a NodeList of <img>", (done) => {Flickr.flickr('cats').then((images) => {assert(images.length === 2);assert(images[1].nodeName === 'IMG');assert(images[1].src === 'dancing_pug.gif');done();})})}) })

運(yùn)行npm test,會(huì)得到如下結(jié)果:

Math.floor? rounds down to the nearest whole numberFlickr._responseToImages? maps response JSON to a NodeList of <img>.flickr? returns a Promise that resolves with a NodeList of <img>3 passing (67ms)

到這里,我們已經(jīng)成功的測(cè)試了我們的模塊以及組成它的函數(shù),學(xué)習(xí)到了純函數(shù)以及如何使用函數(shù)組合。我們知道了純函數(shù)與不純函數(shù)的區(qū)別,知道純函數(shù)更可讀,由小函數(shù)組成,更容易測(cè)試。相比于不太合理的純函數(shù)式編程,我們的代碼更加可讀、理解和修改,這也是我們重構(gòu)代碼的目的。

Links

  • Professor Frisby’s Mostly Adequate Guide to Functional Programming – @drboolean-這是一本很優(yōu)秀的介紹函數(shù)式編程的書,本文的很多內(nèi)容和例子出自這本書

  • Eloquent Javascript – Functional Programming @marijnjh-介紹編程的好書,同樣有一章介紹函數(shù)式編程的內(nèi)容很棒

  • Underscore-深入的挖掘像Underscore,lodash,Ramda這樣的工具庫(kù)是成為成熟開發(fā)者的重要一步。理解如何使用這些函數(shù)將極大降低你代碼的長(zhǎng)度,讓你的程序更加聲明式的。

以上就是本文的全部!非常感謝閱讀,我希望這篇文章很好的向你介紹了函數(shù)式編程,重構(gòu)以及測(cè)試你的JavaScript。由于目前特別火熱的庫(kù)如React,Redux,Elm,Cycle和ReactiveX都在鼓勵(lì)和使用這種模式,所以這個(gè)時(shí)候?qū)戇@樣一篇有趣的范例也算是推波助流吧。

總結(jié)

以上是生活随笔為你收集整理的合理的使用纯函数式编程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。