在济南网站建设中使用函数式编程

济南网站建设函数式编程是编程范式的胡子时髦。 本来退居计算机科学学术界的史册,函数式编程已经有了近复兴的主要是因为其在分布式系统实用工具(可能也因为“纯粹”的功能性语言,如Haskell中是很难把握,这给他们带来了一定的声望)。

严格的函数式编程语言通常用于当一个系统的性能和完整性都是重要的 – 即你的程序需要做的正是你期望每一次,需要在它的任务可以在数百联网计算机或数千间共享的环境中运行。 Clojure的 ,例如,权力Akamai的 ,海量内容分发网络通过如Facebook公司使用,而Twitter的著名采用 斯卡拉 ,其大多数性能密集型的零部件,并哈斯克尔 是其网络安全系统中使用由AT&T。

这些语言对最前端的Web开发人员一个陡峭的学习曲线; 然而,许多更平易近人的语言把函数式编程,最值得注意的是Python的,无论是在其核心库,与像功能特点mapreduce (我们将在一个有点谈),并与诸如图书馆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响应我们的可视化库所需的结构过程中的步骤。 在一个基本的层面上,我们将执行我们的数据执行以下操作:

  • 添加在列表中的每一个数字,
  • 计算平均值,
  • 从对象列表中检索单个属性。

我们将编写一个函数为每个这三个基本动作,然后从这些功能组成我们的节目。 函数式编程可以是一个有点混乱在第一,你可能会受到诱惑而陷入旧势在必行习惯。 为了避免这种情况,这里有一些简单的基本规则,以确保您遵循以下最佳实践:

  1. 所有的函数都必须接受至少一个参数。
  2. 所有的函数必须返回的数据或其他功能。
  3. 没有循环!

好吧,让我们添加在列表中的每个数字。 记住的规则,让我们确保我们的函数接受一个参数(数字数组添加),并返回一些数据。

 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索引的第二阵列中,依此类推1n (其中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 falsenull""等)值使用另一个得心应手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 } ); }

全部放在一起,这里的setGraphDatamakeArray在上下文中的功能,包括使用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分钟。 一旦你的数据已经被正确地格式化,你会弄花更多的时间在有趣的部分:制作可视化看起来真棒!

Comments are closed.