濟南網站建設函數式編程是編程范式的胡子時髦。 本來退居計算機科學學術界的史冊,函數式編程已經有了近復興的主要是因為其在分布式系統實用工具(可能也因為“純粹”的功能性語言,如Haskell中是很難把握,這給他們帶來了一定的聲望)。
嚴格的函數式編程語言通常用于當一個系統的性能和完整性都是重要的 – 即你的程序需要做的正是你期望每一次,需要在它的任務可以在數百聯網計算機或數千間共享的環境中運行。 Clojure的 ,例如,權力Akamai的 ,海量內容分發網絡通過如Facebook公司使用,而Twitter的著名采用 斯卡拉 ,其大多數性能密集型的零部件,并哈斯克爾 是其網絡安全系統中使用由AT&T。
這些語言對最前端的Web開發人員一個陡峭的學習曲線; 然而,許多更平易近人的語言把函數式編程,最值得注意的是Python的,無論是在其核心庫,與像功能特點map
和reduce
(我們將在一個有點談),并與諸如圖書館Fn.py ,隨著JavaScript中,使用再次收集方法,還與諸如圖書館Underscore.js 和Bacon.js 。
函數式編程可以是艱巨的,但請記住,它不僅是博士,數據科學家和宇航員的架構。 對于大多數人來說,采用功能性風格的真正好處是,我們的程序可以被分解成更小的,更簡單的片段,都更可靠,更容易理解。 如果你是一個前端開發人員使用數據,特別是如果你使用的是D3,拉斐爾等的格式化的數據進行可視化,那么函數式編程將在您的阿森納必備武器。
查找函數式編程的一個一致的定義是艱難的,而大多數文獻都依賴像有些預感報表“函數作為第一類對象”,并以防萬一“消除副作用。”不彎曲你的大腦進入海里,在更多的理論水平,函數式編程中經常來解釋lambda演算 (有些實際上認為 是函數式編程的基本數學) -但你可以放松。 從更實際的角度來看,初學者需要了解,以將其用于日常應用(無需積分?。┲挥袃蓚€概念。
首先,在功能程序的數據應該是一成不變的 ,這聽起來嚴重,但只是意味著它應該永遠不會改變。 起初,這可能看上去很奇怪(畢竟,誰需要一個程序,它永遠不會改變什么嗎?),但在實踐中,你會簡單地創建,而不是修改那些已經存在的新的數據結構。 例如,如果你需要處理陣列中的一些數據,那么你會成為一個新的數組與更新的值,而不是修改原始數組。 簡單!
其次,功能的程序應該是無狀態的 ,這基本上意味著他們應該執行每一項任務,仿佛是第一次,沒有知識,什么可能會或可能不會有先前在程序的執行發生(你可能會說,一個無狀態的程序是無知的在過去的 )。 結合不變性,這可以幫助我們把每個功能就好像它是在真空中運行,幸福無知的任何東西,除了在其他功能的應用程序。 更具體而言,這意味著你的功能將在作為參數傳遞的數據只能操作,絕不會依靠外部值來執行他們的計算。
不變性和無國籍的核心函數式編程,而且重要的是理解的,但如果他們不很有道理但不要擔心。 你會很熟悉這些原則由文章的最后,我答應函數式編程之美,精度和功耗會變成你的應用程序變成鮮艷,有光澤,數據極其糾結的彩虹。 現在,先從簡單的函數返回數據(或其他功能),然后再結合這些基本構建塊來執行更復雜的任務。
例如,假設我們有一個API響應:
var data = [ { name: "Jamestown", population: 2047, temperatures: [-34, 67, 101, 87] }, { name: "Awesome Town", population: 3568, temperatures: [-3, 4, 9, 12] } { name: "Funky Town", population: 1000000, temperatures: [75, 75, 75, 75, 75] } ];
如果我們想用一個圖表或圖形庫的平均溫度比較的人口規模,我們就需要編寫一些JavaScript代碼,做了一些更改數據之前,它的正確格式化為我們的可視化。 我們的圖形庫希望x和y坐標的數組,如下所示:
[ [x, y], [x, y] …etc ]
這里, x
是平均溫度,并y
是種群規模。
如果沒有函數式編程(使用或不使用所謂的一個“勢在必行”的風格),我們的程序可能看起來像這樣:
var coords = [], totalTemperature = 0, averageTemperature = 0; for (var i=0; i < data.length; i++) { totalTemperature = 0; for (var j=0; j < data[i].temperatures.length; j++) { totalTemperature += data[i].temperatures[j]; } averageTemperature = totalTemperature / data[i].temperatures.length; coords.push([averageTemperature, data[i].population]); }
即使是在一個人為的例子,這已經成為難以遵循。 讓我們來看看我們可以做的更好。
當在函數式風格的編程,你一直在尋找可以抽象到一個功能簡單的,可重復的動作。 更多關于在第二個 – 然后,我們可以通過調用序列這些函數(也被稱為“創作”功能)構建更復雜的功能。 與此同時,讓我們來看看,我們會采取在改造初期的API響應我們的可視化庫所需的結構過程中的步驟。 在一個基本的層面上,我們將執行我們的數據執行以下操作:
- 添加在列表中的每一個數字,
- 計算平均值,
- 從對象列表中檢索單個屬性。
我們將編寫一個函數為每個這三個基本動作,然后從這些功能組成我們的節目。 函數式編程可以是一個有點混亂在第一,你可能會受到誘惑而陷入舊勢在必行習慣。 為了避免這種情況,這里有一些簡單的基本規則,以確保您遵循以下最佳實踐:
- 所有的函數都必須接受至少一個參數。
- 所有的函數必須返回的數據或其他功能。
- 沒有循環!
好吧,讓我們添加在列表中的每個數字。 記住的規則,讓我們確保我們的函數接受一個參數(數字數組添加),并返回一些數據。
function totalForArray(arr) { // add everything return total; }
到目前為止,一切都很好。 但我們如何來訪問每個項目在列表中,如果我們不這樣做循環過來的嗎? 問好你的新朋友,遞歸! 這是一個有點棘手,但基本上,當你使用遞歸,您將創建一個函數調用自身,除非特定的條件已經得到滿足 – 在這種情況下,返回一個值。 只是在看一個例子可能是最簡單的:
// Notice we're accepting two values, the list and the current total function totalForArray(currentTotal, arr) { currentTotal += arr[0]; // Note to experienced JavaScript programmers, I'm not using Array.shift on // purpose because we're treating arrays as if they are immutable. var remainingList = arr.slice(1); // This function calls itself with the remainder of the list, and the // current value of the currentTotal variable if(remainingList.length > 0) { return totalForArray(currentTotal, remainingList); } // Unless of course the list is empty, in which case we can just return // the currentTotal value. else { return currentTotal; } }
要注意的是:遞歸會讓你的程序更易讀,而且是必不可少的一個功能編程風格。 然而,在一些語言(包括JavaScript),你會碰到問題時,你的程序使得大量的單次操作的遞歸調用(在撰寫本文時,“大”是關于10000鉻,50,000在Firefox中調用而在Node.js的11,000 )。 的細節超出了本文的范圍,但其要旨是,至少直到6 ECMAScript的釋放 ,JavaScript不支持所謂的“ 尾遞歸 “,這是遞歸的一種更有效的形式。 這是一個高級的話題,不會拿出很多時候,但它是值得了解的。
有了這樣的方式,請記住,我們需要來計算溫度的數組,以便然后計算平均的總溫度。 現在,而不是在遍歷每個項目temperatures
陣,我們可以簡單地這樣寫:
var totalTemp = totalForArray(0, temperatures);
如果你是純粹的,你可能會說,我們的totalForArray
功能可以進一步細分。 例如,將兩個數相加的任務可能就會出現在您的應用程序的其他部分,其后應該真正將自己的功能。
function addNumbers(a, b) { return a + b; }
現在,我們的totalForArray
函數看起來像這樣:
function totalForArray(currentTotal, arr) { currentTotal = addNumbers(currentTotal, arr[0]); var remainingArr = arr.slice(1); if(remainingArr.length > 0) { return totalForArray(currentTotal, remainingArr); } else { return currentTotal; } }
優秀的! 從數組中返回一個值是相當常見于函數式編程,以至于它有一個特別的名字,“減量化”,這你會更常聽到的一個動詞,就像當你“減少數組的單個值“JavaScript有一個特殊的方法只是為了執行這一共同任務。 Mozilla開發者網絡提供了一個完整的解釋 ,但我們的目的很簡單,只要這樣:
// The reduce method takes a function as its first argument, and that function // accepts both the current item in the list and the current total result from // whatever calculation you're performing. var totalTemp = temperatures.reduce(function(previousValue, currentValue){ // After this calculation is returned, the next currentValue will be // previousValue + currentValue, and the next previousValue will be the // next item in the array. return previousValue + currentValue; });
但是,嘿,既然我們已經定義了一個addNumber
功能,我們就可以使用它。
var totalTemp = temperatures.reduce(addNumbers);
事實上,由于總額高達數組是太酷了,讓我們把它列入自己的功能,使我們可以再次使用它,而不必記住所有的關于減少和遞歸的混亂的東西。
function totalForArray(arr) { return arr.reduce(addNumbers); } var totalTemp = totalForArray(temperatures);
啊,現在就是一些可讀的代碼! 只要你知道,方法,例如reduce
是常見的大多數函數式編程語言。 執行上,以代替循環數組操作這些輔助方法通常被稱為“高階函數”。
繼續向前,我們列出的第二項任務是計算平均值。 這是很容易的。
function average(total, count) { return total / count; }
怎么可能,我們去獲得平均為整個數組?
function averageForArray(arr) { return average(totalForArray(arr), arr.length); } var averageTemp = averageForArray(temperatures);
希望你已經開始看到如何結合函數來執行更復雜的任務。 這是可能的,因為我們下面載于本文章開頭的規則 – 即,我們的職能必須始終接受參數并返回數據。 相當真棒。
最后,我們想從對象的數組中檢索單個屬性。 而不是顯示你遞歸的更多示例中,我將切入正題和線索,你在另一個內置的JavaScript方法: 圖 。 這種方法是當你有一個結構的數組,并需要將其映射到另一個結構,像這樣的:
// The map method takes a single argument, the current item in the list. Check // out the link above for more complete examples. var allTemperatures = data.map(function(item) { return item.temperatures; });
這很酷,但是從對象的集合拉一個單一的財產是你會做所有的時間,所以讓我們只為一個函數。
// Pass in the name of the property that you'd like to retrieve function getItem(propertyName) { // Return a function that retrieves that item, but don't execute the function. // We'll leave that up to the method that is taking action on items in our // array. return function(item) { return item[propertyName]; } }
檢查出來:我們已經做了一個函數,返回一個函數! 現在,我們可以把它傳遞給map
這樣的方法:
var temperatures = data.map(getItem('temperature'));
如果你喜歡的細節,我們可以這樣做的原因是因為,在JavaScript中,函數是“第一類對象”,這基本上意味著你可以通過周圍就像任何其他的價值功能。 而這是許多編程語言中的功能,它是可以在功能樣式可以使用任何語言的要求。 順便說一句,這也是你可以做的東西像的原因$('#my-element').on('click', function(e) … )
在第二個參數on
方法是一個function
,而當你將函數作為參數,你使用它們,就像你在命令式語言中使用的值。 整齊漂亮。
最后,讓我們換調用map
在它自己的功能,使事情變得更加易讀。
function pluck(arr, propertyName) { return arr.map(getItem(propertyName)); } var allTemperatures = pluck(data, 'temperatures');
好了,現在我們有,我們可以在我們的應用程序在任何地方使用的通用功能的工具包,甚至在其他的項目。 我們可以從中總結的項目在一個數組,得到一個數組的平均值,并通過從對象列表采摘性質作出新的陣列。 最后但并非最不重要的,讓我們回到我們最初的問題:
var data = [ { name: "Jamestown", population: 2047, temperatures: [-34, 67, 101, 87] }, … ];
我們需要改變像上面一成數組對象的數組x, y
對,是這樣的:
[ [75, 1000000], … ];
這里, x
是平均溫度,并y
為總人口。 首先,讓我們找出我們所需要的數據。
var populations = pluck(data, 'population'); var allTemperatures = pluck(data, 'temperatures');
現在,讓我們把平均的數組。 請記住,我們傳遞給函數map
將被要求在陣列中的每個項目; 所以,該傳遞函數的返回值將被添加到一個新的數組,而新的數組最終將被分配給了averageTemps
變量。
var averageTemps = allTemperatures.map(averageForArray);
到目前為止,一切都很好。 但現在我們有兩個數組:
// populations [2047, 3568, 1000000] // averageTemps [55.25, 5.5, 75]
顯然,我們只想要一個陣列,所以讓我們寫一個函數來組合它們。 我們的函數應該確保在索引中的項目0
的第一個陣列中的被配對的項在索引0
索引的第二陣列中,依此類推1
至n
(其中n
是項目的陣列中的總數)。
function combineArrays(arr1, arr2, finalArr) { // Just so we don't have to remember to pass an empty array as the third // argument when calling this function, we'll set a default. finalArr = finalArr || []; // Push the current element in each array into what we'll eventually return finalArr.push([arr1[0], arr2[0]]); var remainingArr1 = arr1.slice(1), remainingArr2 = arr2.slice(1); // If both arrays are empty, then we're done if(remainingArr1.length === 0 && remainingArr2.length === 0) { return finalArr; } else { // Recursion! return combineArrays(remainingArr1, remainingArr2, finalArr); } }; var processed = combineArrays(averageTemps, populations);
或者,因為單行的樂趣:
var processed = combineArrays(pluck(data, 'temperatures').map(averageForArray), pluck(data, 'population')); // [ // [ 55.25, 2047 ], // [ 5.5, 3568 ], // [ 75, 1000000 ] // ]
讓我們真正的
最后但并非最不重要的,讓我們再看一個真實世界的例子,這一次增加了我們與功能toolbelt Underscore.js ,它提供了許多偉大的函數式編程助手一個JavaScript庫。 我們將從沖突和災難的信息,我一直在努力名為在一個平臺上提取數據CrisisNET ,我們將用夢幻般的D3 庫以可視化的數據。
我們的目標是讓人們來CrisisNET的主頁的信息系統類型的快照。 為了證明這一點,我們可以從分配給一個特定的類別,如“身體暴力”或“武裝沖突”。這樣的API計算文件的數量,用戶可以看到他們找到了話題多的信息是如何使用最有趣的。
氣泡圖可能是一個不錯的選擇,因為它們經常被用來代表一大群人的相對大小。 幸運的是,D3有一個內置的可視化命名的pack
僅此目的。 所以,讓我們創建一個圖形pack
,其中顯示一個給定的類的名稱將出現在從CrisisNET的API響應的次數。
在我們繼續之前,請注意,D3是一個復雜的庫,保證其自身教程(或很多教程,對于這個問題)。 因為本文的重點是函數式編程,我們不會花很多時間在D3是如何工作的。 但不要擔心-如果你不熟悉圖書館,你應該能夠復制并粘貼代碼片段具體到D3和深入細節另一個時間。 斯科特·穆雷的D3教程 是一個很好的資源,如果你“再有興趣學習更多。
順動,讓我們先來確保我們有一個DOM元素,讓D3有一些地方把圖表它將與我們的數據產生。
<div id="bubble-graph"></div>
現在,讓我們創建我們的圖表,并將其添加到DOM。
// width of chart var diameter = 960, format = d3.format(",d"), // creates an ordinal scale with 20 colors. See D3 docs for hex values color = d3.scale.category20c(), // chart object to which we'll be adding data var bubble = d3.layout.pack() .sort(null) .size([diameter, diameter]) .padding(1.5); // Add an SVG to the DOM that our pack object will use to draw the // visualization. var svg = d3.select("#bubble-graph").append("svg") .attr("width", diameter) .attr("height", diameter) .attr("class", "bubble");
該pack
對象采用對象的這種格式的數組:
{ children: [ { className: , package: "cluster", value: } ] }
CrisisNET的數據API將返回此格式的信息:
{ data: [ { summary: "Example summary", content: "Example content", … tags: [ { name: "physical-violence", confidence: 1 } ] } ] }
我們看到,每個文檔都有一個tags
屬性,該屬性包含的項目的數組。 每個標簽項都有一個name
屬性,這就是我們所追求的。 我們需要找到在CrisisNET的API響應每一個獨特的標記名稱,數一數該標簽的名字出現的次數。 讓我們先通過隔離,我們需要使用的信息pluck
我們前面創建的函數。
var tagArrays = pluck(data, 'tags');
這給了我們一個數組的數組,如下所示:
[ [ { name: "physical-violence", confidence: 1 } ], [ { name: "conflict", confidence: 1 } ] ]
但是,我們真正想要的是一個陣列,在它的每一個標簽。 所以,讓我們使用來自Underscore.js一個方便的函數叫做拉平 。 這將需要的值從任何嵌套數組,給我們一個數組,它是一層。
var tags = _.flatten(tagArrays);
現在,我們的數組是一個比較容易處理:
[ { name: "physical-violence", confidence: 1 }, { name: "conflict", confidence: 1 } ]
我們可以用pluck
再次得到我們真正想要的東西,這是唯一的標記名稱的簡單列表。
var tagNames = pluck(tags, 'name'); [ "physical-violence", "conflict" ]
啊,那最好不過了。
所以現在,我們來計算的次數相對簡單的任務,每一個標簽的名字出現在我們的名單,然后將這些名單到由D3所需要的結構pack
,我們在前面創建的布局。 正如你可能已經注意到,數組是一個非常流行的數據結構,函數式編程 – 大多數工具都設計時考慮到陣列。 作為第一步,接下來,我們將創建一個這樣的數組:
[ [ "physical-violence", 10 ], [ "conflict", 27 ] ]
在這里,數組中的每個項目都有索引標簽名0
和索引標簽的總計數1
。 我們只需要為每個唯一的標記名稱的數組,所以讓我們開始創建,其中每個標記名只出現一次的數組。 幸運的是,Underscore.js方法的存在只是為了這個目的。
var tagNamesUnique = _.uniq(tagNames);
讓我們也擺脫任何false-y
false
, null
, ""
等)值使用另一個得心應手Underscore.js功能。
tagNamesUnique = _.compact(tagNamesUnique);
從這里,我們可以寫一個函數,使用另一個內建的JavaScript收集方法,命名為我們生成數組過濾器 ,該過濾器根據條件的數組。
function makeArrayCount(keys, arr) { // for each of the unique tagNames return keys.map(function(key) { return [ key, // Find all the elements in the full list of tag names that match this key // and count the size of the returned array. arr.filter(function(item) { return item === key; }).length ] }); }
現在,我們可以很容易地創建一個數據結構pack
通過映射我們的陣列列表需要。
var packData = makeArrayCount(tagNamesUnique, tagNames).map(function(tagArray) { return { className: tagArray[0], package: "cluster", value: tagArray[1] } });
最后,我們可以在我們的數據傳遞到D3和產生在我們的SVG DOM節點,一個圓圈為每一個獨特的標記名稱,大小相對于總次數該標簽的名字出現在CrisisNET的API響應。
function setGraphData(data) { var node = svg.selectAll(".node") // Here's where we pass our data to the pack object. .data(bubble.nodes(data) .filter(function(d) { return !d.children; })) .enter().append("g") .attr("class", "node") .attr("transform", function(d) { return "translate(" + dx + "," + dy + ")"; }); // Append a circle for each tag name. node.append("circle") .attr("r", function(d) { return dr; }) .style("fill", function(d) { return color(d.className); }); // Add a label to each circle, using the tag name as the label's text node.append("text") .attr("dy", ".3em") .style("text-anchor", "middle") .style("font-size", "10px") .text(function(d) { return d.className } ); }
全部放在一起,這里的setGraphData
和makeArray
在上下文中的功能,包括使用jQuery調用CrisisNET的API(你需要獲得一個API密鑰 )。 我也貼了GitHub上充分合作的例子 。
function processData(dataResponse) { var tagNames = pluck(_.flatten(pluck(dataResponse.data, 'tags')), 'name'); var tagNamesUnique = _.uniq(tagNames); var packData = makeArrayCount(tagNamesUnique, tagNames).map(function(tagArray) { return { className: tagArray[0], package: "cluster", value: tagArray[1] } }); return packData; } function updateGraph(dataResponse) { setGraphData(processData(dataResponse)); } var apikey = // Get an API key here: http://api.crisis.net var dataRequest = $.get('http://api.crisis.net/item?limit=100&apikey=' + apikey); dataRequest.done( updateGraph );
這是一個漂亮的深潛,所以與它堅持的祝賀! 正如我所提到的,這些概念可以在第一是具有挑戰性的,但抵制誘惑,敲定for
循環為你的后半生。
在幾周內,使用函數式編程技術,你會很快建立了一套簡單,可重復使用的功能,這將大大提高你的應用程序的可讀性。 此外,您將能夠顯著更快地處理數據結構,淘汰曾經被認為是令人沮喪的調試中的幾行代碼30分鐘。 一旦你的數據已經被正確地格式化,你會弄花更多的時間在有趣的部分:制作可視化看起來真棒!