国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

首頁(yè) php教程 PHP開(kāi)發(fā) 合理的使用純函數(shù)式編程

合理的使用純函數(shù)式編程

Nov 22, 2016 pm 12:25 PM

函數(shù)式程式設(shè)計(jì)能夠降低程式的複雜程度:函數(shù)看起來(lái)就像是一個(gè)數(shù)學(xué)公式。學(xué)習(xí)函數(shù)程式設(shè)計(jì)能夠幫助你寫(xiě)簡(jiǎn)單且更少bug的程式碼。

純函數(shù)

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

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

上面是一個(gè)純函數(shù),它不依賴也不改變?nèi)魏魏瘮?shù)以外的變數(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ù)移到函數(shù)內(nèi)部,那麼它就變成了純函數(shù),這樣我們就能夠保證函數(shù)每次都能正確的比較年齡。

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

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

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

修改以參數(shù)形式傳遞過(guò)來(lái)的對(duì)象

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

保留用戶輸入

查詢DOM

控制增變(controlled mutation)

你需要留意一些會(huì)改變數(shù)組和對(duì)象的增變方法,舉例來(lái)說(shuō)你要知道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ù)的物件的增變方法,我們的程式將更容易理解,我們也有理由期望我們的函數(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é)果可以被緩存,我們可以記住他們,這樣以來(lái)複雜昂貴的操作只需要在被調(diào)用時(shí)執(zhí)行一次。例如,快取一個(gè)大的查詢索引的結(jié)果可以極大的改善程序的效能。

不合理的純函數(shù)程式設(shè)計(jì)

使用純函數(shù)能夠極大的降低程式的複雜度。但是,如果我們使用過(guò)多的函數(shù)式程式設(shè)計(jì)的抽象概念,我們的函數(shù)式程式設(shè)計(jì)也會(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 $(&#39;<img />&#39;, { src: url });
};

var url = function (t) {
  return &#39;http://api.flickr.com/services/feeds/photos_public.gne?tags=&#39; +
    t + &#39;&format=json&jsoncallback=?&#39;;
};

var mediaUrl = _.compose(_.prop(&#39;m&#39;), _.prop(&#39;media&#39;));
var mediaToImg = _.compose(img, mediaUrl);
var images = _.compose(_.map(mediaToImg), _.prop(&#39;items&#39;));
var renderImages = _.compose(Impure.setHtml("body"), images);
var app = _.compose(Impure.getJSON(renderImages), url);
app("cats");

花一分鐘理解上面的程式碼。

除非你接觸過(guò)函數(shù)式程式設(shè)計(jì)的這些概念(柯里化,組合和prop),否則很難理解上述程式碼。相較於純函數(shù)式的方法,下面的程式碼則更容易理解和修改,它更清晰的描述程式並且更少的程式碼。

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

從Flickr獲取JSON資料

從返回的資料中抽出urls

創(chuàng)建合理的使用純函數(shù)式編程節(jié)點(diǎn)數(shù)組

將他們插入文檔來(lái)更好的進(jìn)行非同步操作。

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) => $(&#39;<img />&#39;, {src:url}) );
    
    $(document.body).html(images);
  })
}
app("cats");

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

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)=> $(&#39;<img />&#39;, { src: url }) )

      return images
  })
}
flickr("cats").then((images)=> {
  $(document.body).html(images)
})

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

將傳回的資料轉(zhuǎn)換成urls

??將urls轉(zhuǎn)換成圖片節(jié)點(diǎn)????函數(shù)式的方法是將上述2個(gè)任務(wù)拆開(kāi),然後使用compose將一個(gè)函數(shù)的結(jié)果作為參數(shù)傳給另一個(gè)參數(shù)。 ??
let responseToImages = (resp) => {
  let urls = resp.items.map((item) => item.media.m)
  let images = urls.map((url) => $(&#39;<img />&#39;, {src:url}))
  
  return images
}
??compose 傳回一系列函數(shù)的組合,每個(gè)函數(shù)都會(huì)將後一個(gè)函數(shù)的結(jié)果作為自己的入?yún)????這裡compose做的事情,就是將urls的結(jié)果傳入images函數(shù)??
let urls = (data) => {
  return data.items.map((item) => item.media.m)
}
let images = (urls) => {
  return urls.map((url) => $(&#39;<img />&#39;, {src: url}))
}
let responseToImages = _.compose(images, urls)
??透過(guò)將程式碼變成純函數(shù),讓我們?cè)谝葬嵊袡C(jī)會(huì)重複使用他們,他們更容易被測(cè)試和自我文件化。不好的是當(dāng)我們過(guò)度的使用這些函數(shù)抽象(像第一個(gè)例子), 就會(huì)使事情變得複雜,這不是我們想要的。當(dāng)我們重構(gòu)程式碼的時(shí)候最重要的是要問(wèn)自己:????這是否讓程式碼更容易閱讀和理解? ??

基本功能函數(shù)

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

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

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

Functions
-debounce
-compose
-partial
-curry

Less is More

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

let items = [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;];
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)來(lái)簡(jiǎn)化函數(shù)

這看起來(lái)很明顯且微不足道,但是我還是讓函數(shù)訪問(wèn)和修改了外部的狀態(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;
}

使用更加可讀的語(yǔ)言抽象forEach來(lái)迭代

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ù)編寫(xiě)函數(shù),語(yǔ)言本身就提供了足夠的抽象來(lái)完成功能

let items = [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;]
let upperCaseItems = item.map((item) => item.toUpperCase())

測(cè)試

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

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

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

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

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

import assert from &#39;assert&#39;;

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

打開(kāi)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 &#39;jquery&#39;;
import { compose } from &#39;underscore&#39;;

let urls = (data) => {
  return data.items.map((item) => item.media.m)
}

let images = (urls) => {
  return urls.map((url) => $(&#39;<img />&#39;, {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ù)fetch和Promise。為了測(cè)試他們,我們使用jsdom來(lái)模擬DOM對(duì)象window和document,使用sinon包來(lái)測(cè)試fetch api。

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

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

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

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

import assert from &#39;assert&#39;;
import Flickr from &#39;../lib/flickr&#39;;
import sinon from &#39;sinon&#39;;
import { Promise } from &#39;es6-promise&#39;;
import { Response } from &#39;whatwg-fetch&#39;;

let sampleResponse = {
  items: [{
    media: { m: &#39;lolcat.jpg&#39; }
  }, {
    media: {m: &#39;dancing_pug.gif&#39;}
  }]
}

//實(shí)際項(xiàng)目中我們會(huì)將這個(gè)test helper移到一個(gè)模塊里
let jsonResponse = (obj) => {
  let json = JSON.stringify(obj);
  var response = new Response(json, {
    status: 200,
    headers: {&#39;Content-type&#39;: &#39;application/json&#39;}
  });
  return Promise.resolve(response);
}


describe(&#39;Flickr&#39;, () => {
  describe(&#39;._responseToImages&#39;, () => {
    it("maps response JSON to a NodeList of <img>", () => {
      let images = Flickr._responseToImages(sampleResponse);
      
      assert(images.length === 2);
      assert(images[0].nodeName === &#39;IMG&#39;);
      assert(images[0].src === &#39;lolcat.jpg&#39;);
    })
  })
  
  describe(&#39;.flickr&#39;, () => {
    //截?cái)鄁etch 請(qǐng)求,返回一個(gè)Promise對(duì)象
    before(() => {
      sinon.stub(global, &#39;fetch&#39;, (url) => {
        return jsonResponse(sampleResponse)
      })
    })
    
    after(() => {
      global.fetch.restore();
    })
    
    it("returns a Promise that resolve with a NodeList of <img>", (done) => {
      Flickr.flickr(&#39;cats&#39;).then((images) => {
        assert(images.length === 2);
        assert(images[1].nodeName === &#39;IMG&#39;);
        assert(images[1].src === &#39;dancing_pug.gif&#39;);
        done();
      })
    })
  })  
  
})

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

Math
  .floor
    ? rounds down to the nearest whole number

Flickr
  ._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)代碼的目的。

本網(wǎng)站聲明
本文內(nèi)容由網(wǎng)友自願(yuàn)投稿,版權(quán)歸原作者所有。本站不承擔(dān)相應(yīng)的法律責(zé)任。如發(fā)現(xiàn)涉嫌抄襲或侵權(quán)的內(nèi)容,請(qǐng)聯(lián)絡(luò)admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費(fèi)脫衣圖片

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅(qū)動(dòng)的應(yīng)用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費(fèi)的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費(fèi)的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強(qiáng)大的PHP整合開(kāi)發(fā)環(huán)境

Dreamweaver CS6

Dreamweaver CS6

視覺(jué)化網(wǎng)頁(yè)開(kāi)發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級(jí)程式碼編輯軟體(SublimeText3)