Javascript - programowanie funkcyjne i lambdy Tuesday, December 16, 2014 Wyobraźmy sobie, że w JavaScript mamy zrobić dwie proste rzeczy: sprawdzić czy wszystkie elementy w tablicy mają konretną wartość przefiltrowac tablicę Standardowe podejście 1) var tab = [ 1, 1, 1]; var allTheSame = true; for(var i in tab) { if (tab[i] !== 1) { allTheSame = false; break; } }; console.log(allTheSame); // true 2) var tab = [ 1, 1, 2]; var filtered = []; for(var i in tab) { if (tab[i] > 1) { filtered.push(tab[i]); } }; console.log(filtered); // [2] Niby dobrze. Ale co można poprawić? Ulepszenie 1 - wbudowane metody W ECMAScript 5 doszły nowe funkcje na tablicach takie jak every, some, forEach, map, filter, reduce Wspieraję je wszystkie nowe przeglądarki (IE>=9) http://kangax.github.io/compat-table/es5/ Teraz nasze przykłady wyglądają następująco: 1) var tab = [ 1, 1, 1]; var allTheSame = tab.every(function(el) { return el === 1; }); console.log(allTheSame); 2) filtrowanie var filtered = tab.filter(function(el) { return el > 1; }); console.log(filtered); // [2] Ulepszenie 2 - różne biblioteki Jeśli chcemy wspierać starsze przeglądarki lub chcemy mieć wiekśze możliwości możemy wykorzystać darmowe biblioteki. a) jQuery - ma podstawowe funkcje .grep (filter), .each, .map, .is. Niektóre z nich są ulepszene względem tych z ECMAScript 5 np. z .each można wyjść za pomoća return false a w .map można też filtrować. Zapis $.grep(tab, function(el, i) {} ) lub $(tab).each(function(i, el) { }) b) Underscore - http://underscorejs.org/ Znana biblioteka. Ogromne możliwości. Przykładowo metody z króych ja korzystałem: findWhere (szukanie po obietach), groupBy, sortBy, contains, isEmpty, pluck. Można robić łańcuchy np. _.chain(stooges) .sortBy(function(stooge){ return stooge.age; }) .map(function(stooge){ return stooge.name + ' is ' + stooge.age; }) .first() .value(); c) Lo-Dash – nieco ulepszony klon underscore https://lodash.com/docs#pluck d) inne functional.js http://functionaljs.com/ np. fjs.every(function (item) { return item % 2 === 0; })([1,2,3]); Rambda http://ramda.github.io/ramdocs/docs/R.html#gt np. var double = function(x) { return x * 2; }; R.map(double, [1, 2, 3]); //=> [2, 4, 6] linq.js http://linqjs.codeplex.com/ - praktycznie 1:1 z Linq z .net np. Enumerable.Range(1, 10) .Where(function(i) { return i % 3 == 0; }) .Select(function(i) { return i * 10; }) Ulepszenie 3 - funkcje pomocnicze Żeby kod był jeszcze krótszy i czytelniejszy możemy dodać funkcje pomocnicze do porównywania wartości np. var eq = function(valueToCompare) { return function(valueInList) { return valueInList === valueToCompare; }; }; i teraz zamiast var allTheSame = tab.every(function(el) { return el === 1; }); możemy napisać var allTheSame = tab.every(eq(1)); Oczywiście można dodać dużo więcej takich metod jak gt, notEmpty, and, or itd Można też skorzystać z biblioteki np. f_underscore.js http://krisjordan.github.io/f_underscore/# np. _.map(stooges, f_.toUpperCase(f_.get('name'))); Ulepszenie 4 – lambdy wspierają je bilbioteki: a) functional.js http://functionaljs.com/basics/lambda/ np. var doubleMap = fjs.map("n => n * 2"); b) linq.js np. var queryResult2 = Enumerable.From(jsonArray) .Where("$.user.id < 200") .OrderBy("$.user.screen_name") .Select("$.user.screen_name + ':' + $.text") .ToArray(); c) lambda.js - http://www.javascriptoo.com/lambda-js/ Dodaje metodę lamba, która przyjmuje string a zwraca funkcje dzieku temu można tak naprawde użyć jej wszędzie np. var allTheSame = tab.every(lambda("== 1")); można sobie oczywiście dać coś krótszego zamiast lambda np. L tab.every(L("== 1"); Można nawet rozserzyć istniejące metody, żęby akceptowały lambdy ["filter", "some", "every", "map"].forEach(function(fnName) { var oldF = Array.prototype[fnName]; Array.prototype[fnName] = function(fun) { var args = Array.prototype.slice.call(arguments); if (typeof fun == "string") { args[0] = lambda(fun); } return oldF.apply(this, args); }; }); Jeśli pierwszy argument jest stringiem to zmieniamy go na funkcje za pomocą biblioteki lambda.js i przekazujemy dalej. i teraz możemy robić tak [1,2,3].filter(">1").map("x->{key: x + 1}") //[{key: 3}, {key: 4}] Prościej to już chyba się nie da J