函數式編程--Ramda 函數庫學習

一、Ramda的特點

1)Ramda 的數據一律放在最后一個參數,理念是"function first,data last"。

2)除了數據放在最后一個參數,Ramda 還有一個特點:所有方法都支持柯里化。也就是說,所有多參數的函數,默認都可以單參數使用。

// 寫法一
R.map(square, [4, 8])

// 寫法二
R.map(square)([4, 8])
// 或者
var mapSquare = R.map(square);
mapSquare([4, 8]);

上面代碼中,寫法一是多參數版本,寫法二是柯里化以后的單參數版本。Ramda 都支持,并且推薦使用第二種寫法。

由于這兩個特點,使得 Ramda 成為 JavaScript 函數式編程最理想的工具庫。今天,我先介紹它的 API,下一次再介紹這些方法如何用于實戰。我保證,一旦你理解了它的運算模型,就一定會認同這才是正確的計算方法。

二、Ramda常用的API

1.比較運算

gt:判斷第一個參數是否大于第二個參數。

R.gt(2)(1) // true
R.gt('a')('z') // false

gte:判斷第一個參數是否大于等于第二個參數。

R.gte(2)(2) // true
R.gte('a')('z') // false

lt:判斷第一個參數是否小于第二個參數。

R.lt(2)(1) // false
R.lt('a')('z') // true

lte:判斷第一個參數是否小于等于第二個參數。

R.lte(2)(2) // true
R.lte('a')('z') // true

equals:比較兩個值是否相等(支持對象的比較)。

R.equals(1)(1) // true
R.equals(1)('1') // false
R.equals([1, 2, 3])([1, 2, 3]) // true

var a = {}; 
a.v = a;
var b = {}; 
b.v = b;
R.equals(a)(b)
// true

eqBy:比較兩個值傳入指定函數的運算結果是否相等。

R.eqBy(Math.abs)(5)(-5)   //true

2.數學運算

add:返回兩個值的和。

R.add(7)(10) // 17

subtract:返回第一個參數減第二個參數的差。

R.subtract(10)(8) // 2

multiply:返回兩個值的積。

R.multiply(2)(5)  // 10

divide:返回第一個參數除以第二個參數的商。

R.divide(71)(100) // 0.71

3.邏輯運算

either:接受兩個函數作為參數,只要有一個返回true,就返回true,否則返回false。相當于||運算。

var gt10 = x => x > 10;
var even = x => x % 2 === 0;

var f = R.either(gt10, even);
f(101) // true
f(8) // true

both:接受兩個函數作為參數,只有它們都返回true,才返回true,否則返回false,相當于&&運算。

var gt10 = x => x > 10;
var even = x => x % 2 === 0;

var f = R.both(gt10, even);
f(15) // false
f(30) // true

allPass:接受一個函數數組作為參數,只有它們都返回true,才返回true,否則返回false。

var gt10 = x => x > 10;
var even = x => x % 2 === 0;

var isEvenAndGt10 = R.allPass([gt10, even]);
isEvenAndGt10(15) // false
isEvenAndGt10(30) // true

4.字符串

split:按照指定分隔符將字符串拆成一個數組。

R.split('.')('a.b.c.xyz.d')
// ['a', 'b', 'c', 'xyz', 'd']

test:判斷一個字符串是否匹配給定的正則表達式。

R.test(/^x/)('xyz')
// true

R.test(/^y/)('xyz')
// false

match:返回一個字符串的匹配結果。

R.match(/([a-z]a)/g)('bananas')
// ['ba', 'na', 'na']

R.match(/a/)('b')
// []

R.match(/a/)(null)
// TypeError: null does not have a method named "match"

5.函數

1??函數的合成
compose:將多個函數合并成一個函數,從右到左執行。

R.compose(Math.abs, R.add(1), R.multiply(2))(-4) // 7

pipe:將多個函數合并成一個函數,從左到右執行。

R.pipe(Math.abs, R.add(1), R.multiply(2))(-4) // 10

converge:接受兩個參數,第一個參數是函數,第二個參數是函數數組。傳入的值先使用第二個參數包含的函數分別處理以后,再用第一個參數處理前一步生成的結果。

var sumOfArr = arr => {
  var sum = 0;
  arr.forEach(i => sum += i);
  return sum;
};
var lengthOfArr = arr => arr.length;

var average = R.converge(R.divide, [sumOfArr, lengthOfArr])
average([1, 2, 3, 4, 5, 6, 7])
// 4
// 相當于 28 除以 7

2??柯里化
curry:將多參數的函數,轉換成單參數的形式。

var addFourNumbers = (a, b, c, d) => a + b + c + d;

var curriedAddFourNumbers = R.curry(addFourNumbers);
var f = curriedAddFourNumbers(1, 2);
var g = f(3);
g(4) // 10

partial:允許多參數的函數接受一個數組,指定最左邊的部分參數。

var greet = (salutation, title, firstName, lastName) =>
  salutation + ', ' + title + ' ' + firstName + ' ' + lastName + '!';

var sayHello = R.partial(greet, ['Hello']);
var sayHelloToMs = R.partial(sayHello, ['Ms.']);
sayHelloToMs('Jane', 'Jones'); //=> 'Hello, Ms. Jane Jones!'

partialRight:與partial類似,但數組指定的參數為最右邊的參數。

var greet = (salutation, title, firstName, lastName) =>
  salutation + ', ' + title + ' ' + firstName + ' ' + lastName + '!';
var greetJones = R.partialRight(greet, ['Jones']);
var greetJane = R.partialRight(greetJones,['Jane']);
var toMs = R.partialRight(greetJane,['Ms.']);
console.log(toMs('Hello') )// 'Hello, Ms. Jane Jones!'

useWith:接受一個函數fn和一個函數數組fnList作為參數,返回fn的柯里化版本。該新函數的參數,先分別經過對應的fnList成員處理,再傳入fn執行。

var decreaseOne = x => x - 1;
var increaseOne = x => x + 1;

R.useWith(Math.pow, [decreaseOne, increaseOne])(3, 4) // 32
R.useWith(Math.pow, [decreaseOne, increaseOne])(3)(4) // 32

complement:返回一個新函數,如果原函數返回true,該函數返回false;如果原函數返回false,該函數返回true。

var gt10 = x => x > 10;
var lte10 = R.complement(gt10);
gt10(7) // false
lte10(7) // true

3??函數的執行
zipWith:將兩個數組對應位置的值,一起作為參數傳入某個函數。

var f = (x, y) => {
  // ...
};
R.zipWith(f, [1, 2, 3])(['a', 'b', 'c'])
// [f(1, 'a'), f(2, 'b'), f(3, 'c')]

apply:將數組轉成參數序列,傳入指定函數。

var nums = [1, 2, 3, -99, 42, 6, 7];
R.apply(Math.max)(nums) // 42

applySpec:返回一個模板函數,該函數會將參數傳入模板內的函數執行,然后將執行結果填充到模板。

var getMetrics = R.applySpec({
  sum: R.add,
  nested: { mul: R.multiply }
});

getMetrics(2, 4) // { sum: 6, nested: { mul: 8 } }

ascend:返回一個升序排列的比較函數,主要用于排序。

var people = [
    {
        name:'zhangsan',
        age:23
    },
    {
        name:'lisi',
        age:20
    },
    {
        name:'xiaowu',
        age:123
    }
]
var byAge = R.ascend(R.prop('age'));
R.sort(byAge)(people)

descend:返回一個降序排列的比較函數,主要用于排序。

 var byAge = R.descend(R.prop('age'));
 R.sort(byAge)(people);

6.數組

1??數組的特征判斷

contains:如果包含某個成員,返回true。

R.contains(3)([1, 2, 3]) // true
R.contains(4)([1, 2, 3]) // false
R.contains({ name: 'Fred' })([{ name: 'Fred' }]) // true
R.contains([42])([[42]]) // true

all:所有成員都滿足指定函數時,返回true,否則返回false

var equals3 = R.equals(3);
R.all(equals3)([3, 3, 3, 3]) // true
R.all(equals3)([3, 3, 1, 3]) // false

any:只要有一個成員滿足條件,就返回true。

var equals3 = R.equals(3);
R.any(equals3)([3, 3, 3, 3]) // true
R.any(equals3)([3, 3, 1, 3]) // true

none:沒有成員滿足條件時,返回true。

var isEven = n => n % 2 === 0;

R.none(isEven)([1, 3, 5, 7, 9, 11]) // true
R.none(isEven)([1, 3, 5, 7, 8, 11]) // false

2??數組的截取和添加

head:返回數組的第一個成員。

R.head(['fi', 'fo', 'fum']) // 'fi'
R.head([])  // undefined
R.head('abc') // 'a'
R.head('') // ''

last:返回數組的最后一個成員。

R.last(['fi', 'fo', 'fum']) // 'fum'
R.last([]) // undefined
R.last('abc') // 'c'
R.last('') // ''

tail:返回第一個成員以外的所有成員組成的新數組。

R.tail([1, 2, 3])  // [2, 3]
R.tail([1, 2])     // [2]
R.tail([1])        // []
R.tail([])         // []

R.tail('abc')  // 'bc'
R.tail('ab')   // 'b'
R.tail('a')    // ''
R.tail('')     // ''

init:返回最后一個成員以外的所有成員組成的新數組。

R.init([1, 2, 3])  // [1, 2]
R.init([1, 2])     // [1]
R.init([1])        // []
R.init([])         // []

R.init('abc')  // 'ab'
R.init('ab')   // 'a'
R.init('a')    // ''
R.init('')     // ''

nth:取出指定位置的成員。

var list = ['foo', 'bar', 'baz', 'quux'];
R.nth(1)(list) // 'bar'
R.nth(-1)(list) // 'quux'
R.nth(-99)(list) // undefined

R.nth(2)('abc') // 'c'
R.nth(3)('abc') // ''

take:取出前 n 個成員。

R.take(1)(['foo', 'bar', 'baz']) // ['foo']
R.take(2)(['foo', 'bar', 'baz']) // ['foo', 'bar']
R.take(3)(['foo', 'bar', 'baz']) // ['foo', 'bar', 'baz']
R.take(4)(['foo', 'bar', 'baz']) // ['foo', 'bar', 'baz']
R.take(3)('ramda')               // 'ram'

takeLast:取出后 n 個成員。

R.takeLast(1)(['foo', 'bar', 'baz']) // ['baz']
R.takeLast(2)(['foo', 'bar', 'baz']) // ['bar', 'baz']
R.takeLast(3)(['foo', 'bar', 'baz']) // ['foo', 'bar', 'baz']
R.takeLast(4)(['foo', 'bar', 'baz']) // ['foo', 'bar', 'baz']
R.takeLast(3)('ramda')               // 'mda'

slice:從起始位置(包括)開始,到結束位置(不包括)為止,從原數組截取出一個新數組。

R.slice(1, 3)(['a', 'b', 'c', 'd']) // ['b', 'c']
R.slice(1, Infinity)(['a', 'b', 'c', 'd']) // ['b', 'c', 'd']
R.slice(0, -1)(['a', 'b', 'c', 'd']) // ['a', 'b', 'c']
R.slice(-3, -1)(['a', 'b', 'c', 'd']) // ['b', 'c']
R.slice(0, 3)('ramda') // 'ram'

remove:移除開始位置后的n個成員。

R.remove(2, 3)([1,2,3,4,5,6,7,8]) // [1,2,6,7,8]

insert:在指定位置插入給定值。

R.insert(2, 'x')([1,2,3,4]) // [1,2,'x',3,4]

insertAll:在指定位置,插入另一個數組的所有成員。

R.insertAll(2,['x','y','z'])([1,2,3,4]) // [1,2,'x','y','z',3,4]

prepend:在數組頭部插入一個成員

R.prepend('fee')(['fi', 'fo', 'fum'])
// ['fee', 'fi', 'fo', 'fum']

append:在數組尾部追加新的成員。

R.append('tests')(['write', 'more']) // ['write', 'more', 'tests']
R.append('tests')([]) // ['tests']
R.append(['tests'])(['write', 'more']) // ['write', 'more', ['tests']]

intersperse:在數組成員之間插入表示分隔的成員。

R.intersperse('n')(['ba', 'a', 'a'])
// ['ba', 'n', 'a', 'n', 'a']

join:將數組合并成一個字符串,并在成員之間插入分隔符。

R.join('|')([1, 2, 3]) // '1|2|3'

3??數組的過濾

filter:過濾出符合條件的成員。

var isEven = n => n % 2 === 0;
R.filter(isEven)([1, 2, 3, 4]) // [2, 4]

reject:過濾出所有不滿足條件的成員。

var isOdd = (n) => n % 2 === 1;
R.reject(isOdd)([1, 2, 3, 4]) // [2, 4]

takeWhile: 一旦不滿足條件,后面的成員都會被過濾。

var isNotFour = x => x !== 4;
R.takeWhile(isNotFour)([1, 2, 3, 4, 3, 2, 1]) // [1, 2, 3]

dropWhile:一旦不滿足條件,取出剩余的所有成員。

var lteTwo = x => x <= 2;
R.dropWhile(lteTwo)([1, 2, 3, 4, 3, 2, 1])
// [3, 4, 3, 2, 1]

without:返回指定值以外的成員。

R.without([1, 2])([1, 2, 1, 3, 4])
// [3, 4]

4??單數組運算

countBy:對每個成員執行指定函數以后,返回一個對象,表示各種執行結果分別包含多少成員。

var numbers = [1.0, 1.1, 1.2, 2.0, 3.0, 2.2];
R.countBy(Math.floor)(numbers)  // {'1': 3, '2': 2, '3': 1}

var letters = ['a', 'b', 'A', 'a', 'B', 'c'];
R.countBy(R.toLower)(letters)  // {'a': 3, 'b': 2, 'c': 1}

splitAt:在給定位置,將原數組分成兩個部分。

R.splitAt(1)([1, 2, 3]) // [[1], [2, 3]]
R.splitAt(5)('hello world') // ['hello', ' world']
R.splitAt(-1)('foobar') // ['fooba', 'r']

splitEvery:按照指定的個數,將原數組分成多個部分。

R.splitEvery(3)([1, 2, 3, 4, 5, 6, 7,8,9,10,11])
// [[1, 2, 3], [4, 5, 6], [7,8,9],[10.11]]
R.splitEvery(3)('foobarbaz')
// ['foo', 'bar', 'baz']

splitWhen:以第一個滿足指定函數的成員為界,將數組分成兩個部分。

R.splitWhen(R.equals(2))([1, 2, 3, 1, 2, 3])
// [[1], [2, 3, 1, 2, 3]]

aperture:每個成員與其后給定數量的成員分成一組,這些組構成一個新的數組。

R.aperture(3)([1, 2, 3, 4, 5, 6, 7])
// [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7]]

partition:根據是否滿足指定函數,將成員分區。

R.partition(R.contains('s'))(['sss', 'ttt', 'foo', 'bars'])
// => [ [ 'sss', 'bars' ],  [ 'ttt', 'foo' ] ]

indexOf:某個值在數組中第一次出現的位置。

R.indexOf(3)([1,2,3,4]) // 2
R.indexOf(10)([1,2,3,4]) // -1

lastIndexOf:某個值在數組中最后一次出現的位置。

R.lastIndexOf(3)([-1,3,3,0,1,2,3,4]) // 6
R.lastIndexOf(10)([1,2,3,4]) // -1

map:數組的每個成員依次執行某個函數。

var double = x => x * 2;
R.map(double)([1, 2, 3]) // [2, 4, 6]

addIndex:與map類似,區別是遍歷函數可以額外獲得兩個參數:索引位置和原數組。

var mapIndexed = R.addIndex(R.map);
mapIndexed(
  (val, idx) => idx + '-' + val, ['f', 'o', 'o', 'b', 'a', 'r']
)
// ['0-f', '1-o', '2-o', '3-b', '4-a', '5-r']

forEach:數組的每個成員依次執行某個函數,總是返回原數組。

var printXPlusFive = x => console.log(x + 5);
R.forEach(printXPlusFive, [1, 2, 3]) // [1, 2, 3]
// logs 6
// logs 7
// logs 8

reduce:數組成員依次執行指定函數,每一次的運算結果都會進入一個累積變量。

var mySubtract = function (a, b) {
  return a - b;
};
R.reduce(mySubtract, 0)([1, 2, 3, 4]) // -10

reduceRight:與reduce類似,區別是數組成員從右到左執行。

R.reduceRight(R.subtract, 0)([1, 2, 3, 4]) // -2

reduceWhile:與reduce類似,區別是有一個判斷函數,一旦數組成員不符合條件,就停止累積。

var isOdd = (acc, x) => x % 2 === 1;
var xs = [1, 3, 5, 60, 777, 800];
R.reduceWhile(isOdd, R.add, 0)(xs) // 9

var ys = [2, 4, 6];
R.reduceWhile(isOdd, R.add, 111)(ys) // 111

sort:按照給定函數,對數組進行排序。

var diff = function(a, b) { return a - b; };
R.sort(diff)([4,2,7,5])
// [2, 4, 5, 7]

sortWith:按照給定的一組函數,進行多重排序。

var alice = {
  name: 'alice',
  age: 40
};
var bob = {
  name: 'bob',
  age: 30
};
var clara = {
  name: 'clara',
  age: 40
};
var people = [clara, bob, alice];
var ageNameSort = R.sortWith([
  R.descend(R.prop('age')),
  R.ascend(R.prop('name'))
]);
ageNameSort(people); //=> [alice, clara, bob]

adjust:對指定位置的成員執行給定的函數。

R.adjust(R.add(10), 1)([1, 2, 3]) // [1, 12, 3]

ap:數組成員分別執行一組函數,將結果合成為一個新數組。

R.ap([R.multiply(2), R.add(3)])([1,2,3])
// [2, 4, 6, 4, 5, 6]

R.ap([R.concat('tasty '), R.toUpper])(['pizza', 'salad'])
// ["tasty pizza", "tasty salad", "PIZZA", "SALAD"]

flatten:將嵌套數組鋪平。

R.flatten([1, 2, [3, 4], 5, [6, [7, 8, [9, [10, 11], 12]]]])
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

groupBy:將數組成員依次按照指定條件兩兩比較,并按照結果將所有成員放入子數組。

R.groupWith(R.equals)([0, 1, 1, 2, 3, 5, 8, 13, 21])
// [[0], [1, 1], [2], [3], [5], [8], [13], [21]]

R.groupWith((a, b) => a % 2 === b % 2)([0, 1, 1, 2, 3, 5, 8, 13, 21])
// [[0], [1, 1], [2], [3, 5], [8], [13, 21]]

5??雙數組運算

concat:將兩個數組合并成一個數組。

R.concat('ABC')('DEF') // 'ABCDEF'
R.concat([4, 5, 6])([1, 2, 3]) // [4, 5, 6, 1, 2, 3]
R.concat([])([]) // []

zip:將兩個數組指定位置的成員放在一起,生成一個新數組。

R.zip([1, 2, 3])(['a', 'b', 'c'])
// [[1, 'a'], [2, 'b'], [3, 'c']]

zipObj:將兩個數組指定位置的成員分別作為鍵名和鍵值,生成一個新對象。

R.zipObj(['a', 'b', 'c'])([1, 2, 3])
// {a: 1, b: 2, c: 3}

xprod:將兩個數組的成員兩兩混合,生成一個新數組。

R.xprod([1, 2])(['a', 'b'])
// [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']]

intersection:返回兩個數組相同的成員組成的新數組。

R.intersection([1,2,3,4], [7,6,5,4,3]) // [4, 3]

difference:返回第一個數組不包含在第二個數組里面的成員。

R.difference([1,2,3,4])([7,6,5,4,3]) // [1,2]
R.difference([7,6,5,4,3])([1,2,3,4]) // [7,6,5]
R.difference([{a: 1}, {b: 2}])([{a: 1}, {c: 3}]) // [{b: 2}]

differenceWith:返回執行指定函數后,第一個數組里面不符合條件的所有成員。

var cmp = (x, y) => x.a === y.a;
var l1 = [{a: 1}, {a: 2}, {a: 3}];
var l2 = [{a: 3}, {a: 4}];
R.differenceWith(cmp, l1)(l2) // [{a: 1}, {a: 2}]

symmetricDifference:返回兩個數組的非共有成員所組成的一個新數組。

R.symmetricDifference([1,2,3,4])([7,6,5,4,3]) // [1,2,7,6,5]
R.symmetricDifference([7,6,5,4,3])([1,2,3,4]) // [7,6,5,1,2]

symmetricDifferenceWith:根據指定條件,返回兩個數組所有運算結果不相等的成員所組成的新數組。

var eqA = R.eqBy(R.prop('a'));
var l1 = [{a: 1}, {a: 2}, {a: 3}, {a: 4}];
var l2 = [{a: 3}, {a: 4}, {a: 5}, {a: 6}];
R.symmetricDifferenceWith(eqA, l1, l2) // [{a: 1}, {a: 2}, {a: 5}, {a: 6}]

6??復合數組

find:返回符合指定條件的成員。

var xs = [{a: 1}, {a: 2}, {a: 3}];
R.find(R.propEq('a', 2))(xs) // {a: 2}
R.find(R.propEq('a', 4))(xs) // undefined

findIndex:返回符合指定條件的成員的位置。

var xs = [{a: 1}, {a: 2}, {a: 3}];
R.findIndex(R.propEq('a', 2))(xs) // 1
R.findIndex(R.propEq('a', 4))(xs) // -1

findLast:返回最后一個符合指定條件的成員。

var xs = [{a: 1, b: 0}, {a:1, b: 1}];
R.findLast(R.propEq('a', 1))(xs) // {a: 1, b: 1}
R.findLast(R.propEq('a', 4))(xs) // undefined

findLastIndex:返回最后一個符合指定條件的成員的位置。

var xs = [{a: 1, b: 0}, {a:1, b: 1}];
R.findLastIndex(R.propEq('a', 1))(xs) // 1
R.findLastIndex(R.propEq('a', 4))(xs) // -1

pluck:取出數組成員的某個屬性,組成一個新數組。

R.pluck('a')([{a: 1}, {a: 2}]) // [1, 2]
R.pluck(0)([[1, 2], [3, 4]])   // [1, 3]

project:取出數組成員的多個屬性,組成一個新數組。

var abby = {name: 'Abby', age: 7, hair: 'blond', grade: 2};
var fred = {name: 'Fred', age: 12, hair: 'brown', grade: 7};
var kids = [abby, fred];
R.project(['name', 'grade'])(kids)
// [{name: 'Abby', grade: 2}, {name: 'Fred', grade: 7}]

transpose:將每個成員相同位置的值,組成一個新數組。

R.transpose([[1, 'a'], [2, 'b'], [3, 'c']])
// [[1, 2, 3], ['a', 'b', 'c']]

R.transpose([[1, 2, 3], ['a', 'b', 'c']])
// [[1, 'a'], [2, 'b'], [3, 'c']]

R.transpose([[10, 11], [20], [], [30, 31, 32]])
// [[10, 20, 30], [11, 31], [32]]

mergeAll:將數組的成員合并成一個對象

R.mergeAll([{foo:1},{bar:2},{baz:3}])
// {foo:1,bar:2,baz:3}

R.mergeAll([{foo:1},{foo:2},{bar:2}])
// {foo:2, bar:2}

fromPairs:將嵌套數組轉為一個對象。

R.fromPairs([['a', 1], ['b', 2], ['c', 3]])
// {a: 1, b: 2, c: 3}

groupBy:將數組成員按照指定條件分組。

var byGrade = R.groupBy(function(student) {
  var score = student.score;
  return score < 65 ? 'F' :
         score < 70 ? 'D' :
         score < 80 ? 'C' :
         score < 90 ? 'B' : 'A';
});
var students = [{name: 'Abby', score: 84},
                {name: 'Eddy', score: 58},
                // ...
                {name: 'Jack', score: 69}];
byGrade(students);
// {
//   'A': [{name: 'Dianne', score: 99}],
//   'B': [{name: 'Abby', score: 84}]
//   // ...,
//   'F': [{name: 'Eddy', score: 58}]
// }

sortBy:根據成員的某個屬性排序。

var sortByFirstItem = R.sortBy(R.prop(0));
sortByFirstItem([[-1, 1], [-2, 2], [-3, 3]])
// [[-3, 3], [-2, 2], [-1, 1]]

var sortByNameCaseInsensitive = R.sortBy(
  R.compose(R.toLower, R.prop('name'))
);
var alice = {name: 'ALICE', age: 101};
var bob = {name: 'Bob', age: -10};
var clara = {name: 'clara', age: 314.159};
var people = [clara, bob, alice];
sortByNameCaseInsensitive(people)
// [alice, bob, clara]

7.對象

1?? 對象的特征判斷

has: 返回一個布爾值,表示對象自身是否具有該屬性。

var hasName = R.has('name')
hasName({name: 'alice'})   //=> true
hasName({name: 'bob'})     //=> true
hasName({})                //=> false

var point = {x: 0, y: 0};
var pointHas = R.has(R.__, point);
pointHas('x')  // true
pointHas('y')  // true
pointHas('z')  // false

hasIn:返回一個布爾值,表示對象自身或原型鏈上是否具有某個屬性。

function Rectangle(width, height) {
  this.width = width;
  this.height = height;
}
Rectangle.prototype.area = function() {
  return this.width * this.height;
};

var square = new Rectangle(2, 2);
R.hasIn('width')(square)  // true
R.hasIn('area')(square)  // true

propEq:如果屬性等于給定值,返回true。

var abby = {name: 'Abby', age: 7, hair: 'blond'};
var fred = {name: 'Fred', age: 12, hair: 'brown'};
var rusty = {name: 'Rusty', age: 10, hair: 'brown'};
var alois = {name: 'Alois', age: 15, disposition: 'surly'};
var kids = [abby, fred, rusty, alois];
var hasBrownHair = R.propEq('hair', 'brown');
R.filter(hasBrownHair)(kids) // [fred, rusty]

whereEq:如果屬性等于給定值,返回true。

var pred = R.whereEq({a: 1, b: 2});

pred({a: 1})              // false
pred({a: 1, b: 2})        // true
pred({a: 1, b: 2, c: 3})  // true
pred({a: 1, b: 1})        // false

where:如果各個屬性都符合指定條件,返回true。

var pred = R.where({
    a: R.equals('foo'),
    b: R.complement(R.equals('bar')),
    x: R.gt(R.__, 10),
    y: R.lt(R.__, 20)
});
pred({a: 'foo', b: 'xxx', x: 11, y: 19}) // true
pred({a: 'xxx', b: 'xxx', x: 11, y: 19}) // false
pred({a: 'foo', b: 'bar', x: 11, y: 19}) // false
pred({a: 'foo', b: 'xxx', x: 10, y: 19}) // false
pred({a: 'foo', b: 'xxx', x: 11, y: 20}) // false

2??對象的過濾

omit:過濾指定屬性。

R.omit(['a', 'd'])({a: 1, b: 2, c: 3, d: 4})
// {b: 2, c: 3}

filter:返回所有滿足條件的屬性

var isEven = n => n % 2 === 0;
R.filter(isEven)({a: 1, b: 2, c: 3, d: 4}) // {b: 2, d: 4}

reject:返回所有不滿足條件的屬性

var isOdd = (n) => n % 2 === 1;
R.reject(isOdd)({a: 1, b: 2, c: 3, d: 4})
// {b: 2, d: 4}

3??對象的截取

dissoc:過濾指定屬性。

R.dissoc('b')({a: 1, b: 2, c: 3})
// {a: 1, c: 3}

assoc:添加或改寫某個屬性。

R.assoc('c', 3)({a: 1, b: 2})
// {a: 1, b: 2, c: 3}

partition:根據屬性值是否滿足給定條件,將屬性分區。

R.partition(R.contains('s'))({ a: 'sss', b: 'ttt', foo: 'bars' })
// [ { a: 'sss', foo: 'bars' }, { b: 'ttt' }  ]

pick:返回指定屬性組成的新對象

R.pick(['a', 'd'])({a: 1, b: 2, c: 3, d: 4})
// {a: 1, d: 4}

R.pick(['a', 'e', 'f'])({a: 1, b: 2, c: 3, d: 4})
// {a: 1}

pickAll:與pick類似,但會包括不存在的屬性。

R.pickAll(['a', 'd'])({a: 1, b: 2, c: 3, d: 4})
// {a: 1, d: 4}

R.pickAll(['a', 'e', 'f'])({a: 1, b: 2, c: 3, d: 4})
// {a: 1, e: undefined, f: undefined}

pickBy:返回符合條件的屬性

var isUpperCase = (val, key) => key.toUpperCase() === key;
R.pickBy(isUpperCase)({a: 1, b: 2, A: 3, B: 4})
// {A: 3, B: 4}

keys:返回對象自身屬性的屬性名組成的新數組。

R.keys({a: 1, b: 2, c: 3}) // ['a', 'b', 'c']

keysIn:返回對象自身的和繼承的屬性的屬性名組成的新數組。

var F = function() { this.x = 'X'; };
F.prototype.y = 'Y';
var f = new F();
R.keysIn(f) // ['x', 'y']

values:返回對象自身的屬性的屬性值組成的數組。

R.values({a: 1, b: 2, c: 3}); //=> [1, 2, 3]

valuesIn:返回對象自身的和繼承的屬性的屬性值組成的數組。

var F = function() { this.x = 'X'; };
F.prototype.y = 'Y';
var f = new F();
R.valuesIn(f) // ['X', 'Y']

invertObj:將屬性值和屬性名互換。如果多個屬性的屬性值相同,只返回最后一個屬性。

var raceResultsByFirstName = {
  first: 'alice',
  second: 'jake',
  third: 'alice',
};
R.invertObj(raceResultsByFirstName)
// {"alice": "third", "jake": "second"}

invert:將屬性值和屬性名互換,每個屬性值對應一個數組。

var raceResultsByFirstName = {
  first: 'alice',
  second: 'jake',
  third: 'alice',
};
R.invert(raceResultsByFirstName)
// { 'alice': ['first', 'third'], 'jake':['second'] }

4??對象的運算

prop:返回對象的指定屬性

R.prop('x')({x: 100})
// 100

R.prop('x')({})
// undefined

map:對象的所有屬性依次執行某個函數。

var double = x => x * 2;
R.map(double)({x: 1, y: 2, z: 3})
// {x: 2, y: 4, z: 6}

mapObjIndexed:與map類似,但是會額外傳入屬性名和整個對象。

var values = { x: 1, y: 2, z: 3 };
var prependKeyAndDouble = (num, key, obj) => key + (num * 2);

R.mapObjIndexed(prependKeyAndDouble)(values)
// { x: 'x2', y: 'y4', z: 'z6' }

forEachObjIndexed:每個屬性依次執行給定函數,給定函數的參數分別是屬性值和屬性名,返回原對象。

var printKeyConcatValue = (value, key) => console.log(key + ':' + value);
R.forEachObjIndexed(printKeyConcatValue)({x: 1, y: 2}) // {x: 1, y: 2}
// logs x:1
// logs y:2

merge:合并兩個對象,如果有同名屬性,后面的值會覆蓋掉前面的值。

R.merge({ 'name': 'fred', 'age': 10 })({ 'age': 40 })
// { 'name': 'fred', 'age': 40 }

var resetToDefault = R.merge(R.__, {x: 0});
resetToDefault({x: 5, y: 2}) // {x: 0, y: 2}

mergeWith:合并兩個對象,如果有同名屬性,會使用指定的函數處理。

R.mergeWith(
  R.concat,
  { a: true, values: [10, 20] },
  { b: true, values: [15, 35] }
);
// { a: true, b: true, values: [10, 20, 15, 35] }

eqProps:比較兩個對象的指定屬性是否相等。

var o1 = { a: 1, b: 2, c: 3, d: 4 };
var o2 = { a: 10, b: 20, c: 3, d: 40 };
R.eqProps('a', o1)(o2) // false
R.eqProps('c', o1)(o2) // true

R.evolve:對象的屬性分別經過一組函數的處理,返回一個新對象。

var tomato  = {
  firstName: '  Tomato ',
  data: {elapsed: 100, remaining: 1400},
  id: 123
};
var transformations = {
  firstName: R.trim,
  lastName: R.trim, // 不會被調用
  data: {elapsed: R.add(1), remaining: R.add(-1)}
};
R.evolve(transformations)(tomato)
// {
//   firstName: 'Tomato',
//   data: {elapsed: 101, remaining: 1399},
//   id: 123
// }

5??復合對象

path:取出數組中指定路徑的值。

R.path(['a', 'b'], {a: {b: 2}}) // 2
R.path(['a', 'b'], {c: {b: 2}}) // undefined

pathEq:返回指定路徑的值符合條件的成員

var user1 = { address: { zipCode: 90210 } };
var user2 = { address: { zipCode: 55555 } };
var user3 = { name: 'Bob' };
var users = [ user1, user2, user3 ];
var isFamous = R.pathEq(['address', 'zipCode'], 90210);
R.filter(isFamous)(users) // [ user1 ]

assocPath:添加或改寫指定路徑的屬性的值。

R.assocPath(['a', 'b', 'c'], 42)({a: {b: {c: 0}}})
// {a: {b: {c: 42}}}

R.assocPath(['a', 'b', 'c'], 42)({a: 5})
// {a: {b: {c: 42}}}

三、函數式編程有什么用

學習了以上API之后,接下來要回答一個很重要的問題:函數式編程有什么用?

1.程序的本質

image.png

上圖是一個編程任務,左側是數據輸入(input),中間是一系列的運算步驟,對數據進行加工,右側是最后的數據輸出(output)。一個或多個這樣的任務,就組成了程序。
其中復雜的運算fn可以拆分成多個 f1..f2..f3....fn..

2.Pointfree 的概念

我們完全可以把數據處理的過程,定義成一種與參數無關的合成運算。不需要用到代表數據的那個參數,只要把一些簡單的運算步驟合成在一起即可。

這就叫做 Pointfree:不使用所要處理的值,只合成運算過程。中文可以譯作"無值"風格。

3.Pointfree 示例

1??下面是一個字符串,請問其中最長的單詞有多少個字符?

var str = 'Lorem ipsum dolor sit amet consectetur adipiscing elit';
var strToAry = R.split(' ')(str);
var lengthArr = R.map(R.length)(strToAry);
var longestLength = R.reduce(R.max,0)(lengthArr)
//合成后的寫法
var getLongestWordLength = R.pipe(
    R.split(' '),
    R.map(R.length),
    R.reduce(R.max,0)
)
getLongestWordLength(str)

2??下面是一段服務器返回的 JSON 數據。

var data = {
    result: "SUCCESS",
    interfaceVersion: "1.0.3",
    requested: "10/17/2013 15:31:20",
    lastUpdated: "10/16/2013 10:52:39",
    tasks: [
        {id: 104, complete: false,            priority: "high",
                  dueDate: "2013-11-29",      username: "Scott",
                  title: "Do something",      created: "9/22/2013"},
        {id: 105, complete: false,            priority: "medium",
                  dueDate: "2013-11-22",      username: "Lena",
                  title: "Do something else", created: "9/22/2013"},
        {id: 107, complete: true,             priority: "high",
                  dueDate: "2013-11-22",      username: "Mike",
                  title: "Fix the foo",       created: "9/22/2013"},
        {id: 108, complete: false,            priority: "low",
                  dueDate: "2013-11-15",      username: "Punam",
                  title: "Adjust the bar",    created: "9/25/2013"},
        {id: 110, complete: false,            priority: "medium",
                  dueDate: "2013-11-15",      username: "Scott",
                  title: "Rename everything", created: "10/2/2013"},
        {id: 112, complete: true,             priority: "high",
                  dueDate: "2013-11-27",      username: "Lena",
                  title: "Alter all quuxes",  created: "10/5/2013"}
        // , ...
    ]
};

現在要求是,找到用戶 Scott 的所有未完成任務,并按到期日期升序排列。

過程式編程代碼如下:

var fetchData = function () {
  return Promise.resolve(data);
};

var getIncompleteTaskSummaries = function(membername) {
    return fetchData()
        .then(function(data) {
            return data.tasks;
        })
        .then(function(tasks) {
            var results = [];
            for (var i = 0, len = tasks.length; i < len; i++) {
                if (tasks[i].username == membername) {
                    results.push(tasks[i]);
                }
            }
            return results;
        })
        .then(function(tasks) {
            var results = [];
            for (var i = 0, len = tasks.length; i < len; i++) {
                if (!tasks[i].complete) {
                    results.push(tasks[i]);
                }
            }
            return results;
        })
        .then(function(tasks) {
            var results = [], task;
            for (var i = 0, len = tasks.length; i < len; i++) {
                task = tasks[i];
                results.push({
                    id: task.id,
                    dueDate: task.dueDate,
                    title: task.title,
                    priority: task.priority
                })
            }
            return results;
        })
        .then(function(tasks) {
            tasks.sort(function(first, second) {
                var a = first.dueDate, b = second.dueDate;
                return a < b ? -1 : a > b ? 1 : 0;
            });
            return tasks;
        });
};

getIncompleteTaskSummaries('Scott').then(r => console.log(r))

用 Pointfree 風格改寫如下

var getIncompleteTaskSummaries = function(membername) {
  return fetchData()
    .then(R.prop('tasks'))
    .then(R.filter(R.propEq('username', membername)))
    .then(R.reject(R.propEq('complete', true)))
    .then(R.map(R.pick(['id', 'dueDate', 'title', 'priority'])))
    .then(R.sortBy(R.prop('dueDate')));
};

另一種寫法是,把各個then里面的函數合成起來

// 提取 tasks 屬性
var SelectTasks = R.prop('tasks');

// 過濾出指定的用戶
var filterMember = member => R.filter(
  R.propEq('username', member)
);

// 排除已經完成的任務
var excludeCompletedTasks = R.reject(R.propEq('complete', true));

// 選取指定屬性
var selectFields = R.map(
  R.pick(['id', 'dueDate', 'title', 'priority'])
);

// 按照到期日期排序
var sortByDueDate = R.sortBy(R.prop('dueDate'));
var getIncompleteTaskSummaries = function(membername) {
  return fetchData().then(
    R.pipe(
      SelectTasks,
      filterMember(membername),
      excludeCompletedTasks,
      selectFields,
      sortByDueDate,
    )
  );
};

顯然,我們從以上可以知道,通過函數式編程來處理數據非常清晰方便

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容