如何使用 Ramda 實作 wrap() ?

栏目: 编程语言 · 发布时间: 6年前

内容简介:在 Elixir 的部落格發現一個很帥的VS Code 1.32.3Quokka 1.0.200

在 Elixir 的部落格發現一個很帥的 wrap() Operator,除了可以將 Array 攤平,還可以讓 null 優雅的消失,特別將其 Porting 到 Ramda。

Version

VS Code 1.32.3

Quokka 1.0.200

Ramda 0.26.1

wrap()

import { pipe, filter, identity, unnest, of } from 'ramda';

// wrap :: a -> [a]
const wrap = pipe(
  of,
  unnest,
  filter(identity)
);

console.log(wrap(1));
console.log(wrap(null));
console.log(wrap([2, 3]));

wrap() 的功能如下:

null

of() 先將 value 變成 array,這使得 wrap(1) 成為 [1]

wrap([2, 3]) 則會變成 [[2, 3]] ,顯然多了一層 array,所以還要配合 unnest() 攤平一層,但這對 wrap(1) 沒有影響,因為只有一層的 array 對 unnest() 無效。

filter(identity) 則是針對 wrap(null) ,因為 null !== null ,因此會被 filter() 濾掉,也就是 null 不會新增進 array,但為其他 primitive value 與 array 則毫無影響。

wrap() 有什麼威力呢 ? 讓我們繼續看下去。

如何使用 Ramda 實作 wrap() ?

Imperative

const data = [
  { teacher: { name: 'John', age: 40 } },
  { assistant: null },
  { students: [
    { name: 'Amber', age: 20 },
    { name: 'William', age: 22 }
  ] }
];

// getResult :: [a] -> [b]
const getResult = data => {
  const result = [];

  for (let obj of data) {
    for (let obj2 of Object.values(obj)) {
      if (obj2 !== null && !Array.isArray(obj2)) {
        result.push(obj2);
      }

      if (Array.isArray(obj2)) {
        for (let obj3 of Object.values(obj2)) {
          if (obj3 !== null) {
            result.push(obj3);
          }
        }
      }
    }
  }

  return result;
};

console.dir(getResult(data));

data 為 array,每個 object 的 key 並不相同,各為 teacherassistantstudents 。且自個 value 亦為 object、null 與 array,除了 key 不同,value 格式也不同。

[ { name: 'John', age: 40 }, 
  { name: 'Amber', age: 20 }, 
  { name: 'William', age: 22 } ]

若我們希望結果為一層 array,且僅包含 object,並不包含 null

for (let obj of data) {
}

若使用 Imperative,由於 data 為 array,所以要先使用 for loop 展開。

for (let obj2 of Object.values(obj)) {
  if (obj2 !== null && !Array.isArray(obj2)) {
    result.push(obj2);
  }
}

由於需求不顯示 null ,因此要使用 if 先過濾 null

array 也必須排除,因為 array 稍後要特別處理。

if (Array.isArray(obj2)) {
  for (let obj3 of Object.values(obj2)) {
    if (obj3 !== null) {
      result.push(obj3);
    }
  }
}

由於 students 為 array,因此又要使用 for loop 加以展開。

可以發現 Imperative 寫法,需動用三層 for loop ,且還必須用 if 判斷 null 與 array 另外處理, 可讀性很差。

如何使用 Ramda 實作 wrap() ?

chain()

import { pipe, filter, identity, unnest, of, values, chain } from 'ramda';

const data = [
  { teacher: { name: 'John', age: 40 } },
  { assistant: null },
  { students: [
    { name: 'Amber', age: 20 },
    { name: 'William', age: 22 }
  ] }
];

// wrap :: a -> [a]
const wrap = pipe(
  of,
  unnest,
  filter(identity)
);

// getResult :: [a] -> [b]
const getResult = pipe(
  chain(x => wrap(values(x))),
  unnest
);
console.dir(getResult(data));

若使用了 wrap() ,則 null 不會進 array,會優雅的消失,因此不用特別判斷。

chain() 雖然號稱是 flatMap() ,但實際上是 pipe(map, unnest) ,只能使 array 攤平一層,這對 teacher 是有用的,但 students 還有一層,因此必須再使用 pipe(chian, unnest) 繼續攤平。

可以發現使用了 wrap() 之後,除了看不到 for loop 外,連 null 與 array 都不用判斷,非常優雅

如何使用 Ramda 實作 wrap() ?

Point-free

import { pipe, filter, identity, unnest, of, values, chain, compose } from 'ramda';

const data = [
  { teacher: { name: 'John', age: 40 } },
  { assistant: null },
  { students: [
    { name: 'Amber', age: 20 },
    { name: 'William', age: 22 }
  ] }
];

// wrap :: a -> [a]
const wrap = pipe(
  of,
  unnest,
  filter(identity)
);

// getResult :: [a] -> [b]
const getResult = pipe(
  chain(compose(wrap, values)),
  unnest
);
console.dir(getResult(data));

其實之前的寫法,可讀性已經很高,若想將 chian() 的 callback 進一步 Point-free,可使用 compose()wrap()values() 加以組合。

實務上對架構會使用 pipe() 顯示流程,callback 會使用 compose() 表示組合

如何使用 Ramda 實作 wrap() ?

Composition

import { pipe, filter, identity, unnest, of, values, chain } from 'ramda';

const data = [
  { teacher: { name: 'John', age: 40 } },
  { assistant: null },
  { students: [
    { name: 'Amber', age: 20 },
    { name: 'William', age: 22 }
  ] }
];

// wrap :: a -> [a]
const wrap = pipe(
  of,
  unnest,
  filter(identity)
);

// getResult :: [a] -> [b]
const getResult = pipe(
  chain(values),
  chain(wrap),
  unnest
);
console.dir(getResult(data));

chain() 支援的 data 為 Chain 型別,而 Chain 型別一定也是 Functor 型別。

如何使用 Ramda 實作 wrap() ?

其實 array 既是 Functor 也是 Chain ,而 Functor 的基本特性就是支援數學的 identity 與 composition。

Composition 就是 chain(x => f(g(x))) 等於 pipe(chain(x => g(x)), chain(x => f(x)) ,因此可將 compose() 拆開,改成兩次 chain()

要不要將 compose() 拆開改成 pipe() 則見仁見智,最少這是 Functor 的基本特性:composition,可以等效替換,可讓整個流程只使用 pipe() ,讓 chain() 只有一層

如何使用 Ramda 實作 wrap() ?

Conclusion

  • Elixir 的 wrap() 理念很好,除了讓 array 自動減少一層外,還可讓 null 優雅的消失,他山之石可以攻錯,我們也可以在 Ramda 實現 wrap()
  • chain() 雖然號稱是 flatMap() ,其實是 compose(unnest, map) ,只能攤平一層 array
  • chain() 雖然支援的是 Chain ,而 Chain 就是 Functor ,且 array 既是 Functor 也是 Chain ,因此也支援 Functor 的基本特性:Composition,因此可將 compose 解開改用 pipe

Reference

Taiansu , List 操作小技巧

Ramda , of()

Ramda , unnest()

Ramda , identity()

Ramda , filter()

Ramda , chain()

Ramda , pipe()

Ramda , compose()

Ramda , values()

Fantasy Land Specification , Functor


以上所述就是小编给大家介绍的《如何使用 Ramda 實作 wrap() ?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Java Message Service API Tutorial and Reference

Java Message Service API Tutorial and Reference

Hapner, Mark; Burridge, Rich; Sharma, Rahul / 2002-2 / $ 56.49

Java Message Service (JMS) represents a powerful solution for communicating between Java enterprise applications, software components, and legacy systems. In this authoritative tutorial and comprehens......一起来看看 《Java Message Service API Tutorial and Reference》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具