본문으둜 κ±΄λ„ˆλ›°κΈ°

🐀 Chapter 9: λžŒλ‹€ 라이브러리

  • μžμ„Έν•œ λ‚΄μš©μ€ 책을 μ°Έκ³ ν•©μ‹œλ‹Ή (P.199 ~ P.258)

πŸ¦„ λžŒλ‹€ κΈ°λ³Έ μ‚¬μš©λ²•β€‹

πŸ“š R.range ν•¨μˆ˜β€‹

import * as R from 'ramda';

console.log(R.range(1, 9 + 1)); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

πŸ“š R.tap λ””λ²„κΉ…μš© ν•¨μˆ˜β€‹

  • R.tap ν•¨μˆ˜λŠ” 2μ°¨ κ³ μ°¨ ν•¨μˆ˜ ν˜•νƒœλ‘œ ν˜„μž¬ 값을 νŒŒμ•…ν•  수 있게 ν•΄μ€€λ‹€.
import * as R from 'ramda';

const numbers: number[] = R.range(1, 9 + 1);

R.tap(n => console.log(n))(numbers); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

πŸ“š R.pipe ν•¨μˆ˜β€‹

import * as R from 'ramda';

const array: number[] = R.range(1, 10);

R.pipe(R.tap(n => console.log(n)))(array); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

πŸ“š ν¬μΈνŠΈκ°€ μ—†λŠ” ν•¨μˆ˜β€‹

import * as R from 'ramda';

const dump = <T>(array: T[]): T[] => R.pipe(
R.tap(n => console.log(n))
)(array) as T[]; // νƒ€μž… 단언 μ‚¬μš©

dump(R.range(1, 10)); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

πŸ“š μžλ™ 컀리 μ΄ν•΄ν•˜κΈ°β€‹

import * as R from 'ramda';

console.log(
R.add(1, 2), // 3
R.add(1)(2), // 3
);

πŸ“š R.curryN ν•¨μˆ˜β€‹

  • N개의 λ§€κ°œλ³€μˆ˜λ₯Ό 가진 1μ°¨ ν•¨μˆ˜(first function)λ₯Ό N개의 컀리 λ§€κ°œλ³€μˆ˜λ₯Ό κ°€μ§€λŠ” Nμ°¨ κ³ μ°¨ ν•¨μˆ˜λ‘œ λ§Œλ“€μ–΄ μ€€λ‹€.
import * as R from 'ramda';

const sum = (...numbers: number[]): number =>
numbers.reduce((result: number, sum: number) => result + sum, 0);

const curriedSum = R.curryN(4, sum);

console.log(
curriedSum(), // [Function (anonymous)]
curriedSum(1), // [Function (anonymous)]
curriedSum(1)(2), // [Function (anonymous)]
curriedSum(1)(2)(3), // [Function (anonymous)]
curriedSum(1)(2)(3)(4), // 10
);

πŸ“š 순수 ν•¨μˆ˜β€‹

  • λžŒλ‹€ λΌμ΄λΈŒλŸ¬λ¦¬κ°€ μ œκ³΅ν•˜λŠ” ν•¨μˆ˜λ“€μ€ 항상 μž…λ ₯ λ³€μˆ˜μ˜ μƒνƒœλ₯Ό λ³€ν™”μ‹œν‚€μ§€ μ•Šκ³  μƒˆλ‘œμš΄ 값을 λ°˜ν™˜ν•œλ‹€.

πŸ¦„ 배열에 λ‹΄κΈ΄ 수 닀루기​

πŸ“š μ„ μ–Έν˜• ν”„λ‘œκ·Έλž˜λ°β€‹

  • μ„ μ–Έν˜• ν”„λ‘œκ·Έλž˜λ°μ—μ„œ λͺ¨λ“  μž…λ ₯ λ°μ΄ν„°λŠ” λ‹€μŒμ²˜λŸΌ λ‹¨μˆœ 데이터보닀배열 ν˜•νƒœλ₯Ό 주둜 μ‚¬μš©ν•œλ‹€.
const value = 1;

const newArray = R.pipe(
R.map(R.inc)
)([value]) // [2]
  • R.pipe μ•ˆμ—μ„œλŠ” console.log()문을 직접 μ‚¬μš©ν•  수 μ—†μœΌλ―€λ‘œ λ°˜λ“œμ‹œ R.tap ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.

πŸ“š 사칙 μ—°μ‚° ν•¨μˆ˜β€‹

R.add(a: number)(b: number); // a + b
R.subtract(a: number)(b: number); // a - b
R.multiply(a: number)(b: number); // a * b
R.divide(a: number)(b: number); // a / b

πŸ“š R.addIndex ν•¨μˆ˜β€‹

  • Array.map은 두 번째 λ§€κ°œλ³€μˆ˜λ‘œ indexλ₯Ό μ œκ³΅ν•˜μ§€λ§Œ, R.map은 Array.mapκ³Ό λ‹€λ₯΄κ²Œ index λ§€κ°œλ³€μˆ˜λ₯Ό 기본적으둜 μ œκ³΅ν•˜μ§€ μ•ŠλŠ”λ‹€. λ”°λΌμ„œ R.addIndex ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•΄ R.map이 indexλ₯Ό μ œκ³΅ν•˜λŠ” μƒˆλ‘œμš΄ ν•¨μˆ˜λ₯Ό λ§Œλ“€μ–΄μ•Ό ν•œλ‹€.
import * as R from 'ramda';

const addIndex = R.pipe(
R.addIndex(R.map)(R.add),
// R.addIndex(R.map)((value: number, index: number) => R.add(value)(index)),
R.tap(a => console.log(a)) // [1, 3, 5, 7, 9, 11, 13, 15, 17]
);

const newNumbers = addIndex(R.range(1, 9 + 1));

πŸ“š R.flip ν•¨μˆ˜β€‹

  • λžŒλ‹€λŠ” R.flipμ΄λΌλŠ” ν•¨μˆ˜λ₯Ό μ œκ³΅ν•˜λŠ”λ° R.flip은 R.subtract와 같은 2μ°¨ κ³ μ°¨ ν•¨μˆ˜μ˜ 맀개 λ³€μˆ˜ μˆœμ„œλ₯Ό λ°”κΏ”μ€€λ‹€.
import * as R from 'ramda';

const reverseSubtract = R.flip(R.subtract);

const newArray = R.pipe(
R.map(reverseSubtract(10)), // value - 10
R.tap(a => console.log(a)), // [ -9, -8, -7, -6, -5, -4, -3, -2, -1 ]
)(R.range(1, 9 + 1));

πŸ“š 사칙 μ—°μ‚° ν•¨μˆ˜λ“€μ˜ 쑰합​

f(x) = ax2 + bx + c

import * as R from 'ramda';

type NumberToNumberFunc = (number) => number;

// λžŒλ‹€λ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠμŒ
//const f = (a: number, b: number, c: number): NumberToNumberFunc =>
// (x: number): number => a * x ** 2 + b * x + c;

// λžŒλ‹€ ν•¨μˆ˜ μ‚¬μš©
const exp = (N: number) => (x: number) => x ** N;
const square = exp(2);

export const f = (a: number, b: number, c: number): NumberToNumberFunc =>
(x: number): number => R.add(
R.add(
R.multiply(a)(square(x))
)(R.multiply(b)(x)),
c
);

πŸ¦„ μ„œμˆ μžμ™€ 쑰건 연산​

πŸ“š 수의 크기λ₯Ό νŒλ‹¨ν•˜λŠ” μ„œμˆ μžβ€‹

  • 수λ₯Ό 비ꡐ해 trueλ‚˜ falseλ₯Ό λ°˜ν™˜ν•˜λŠ” λ‹€μŒμ˜ μ„œμˆ μžλ“€μ„ μ œκ³΅ν•œλ‹€.
R.lt(a)(b): boolean // a < b 이면 true, aκ°€ b보닀 μž‘μŒ
R.lte(a)(b): boolean // a <= b 이면 true, aκ°€ b보닀 μž‘κ±°λ‚˜ κ°™μŒ
R.gt(a)(b): boolean // a > b 이면 true, aκ°€ b보닀 큼
R.gte(a)(b): boolean // a >= b 이면 true, aκ°€ b보닀 ν¬κ±°λ‚˜ κ°™μŒ
  • λ°°μ—΄μ˜ μ•„μ΄ν…œ 쀑 3보닀 ν¬κ±°λ‚˜ 같은 수만 선택
import * as R from 'ramda';

R.pipe(
R.filter(R.lte(3)),
R.tap(n => console.log(n)) // [3, 4, 5, 6, 7, 8, 9, 10]
)(R.range(1, 10 + 1));

πŸ“š R.allPass 둜직 ν•¨μˆ˜β€‹

  • R.lt, R.gt처럼 boolean νƒ€μž…μ˜ 값을 λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜λ“€μ€ R.allPass와 R.anyPassλΌλŠ” 둜직 ν•¨μˆ˜λ₯Ό 톡해 κ²°ν•©ν•  수 μžˆλ‹€.
R.allPass(μ„œμˆ μžλ°°μ—΄) // λ°°μ—΄μ˜ 쑰건을 λͺ¨λ‘ λ§Œμ‘±ν•˜λ©΄ true
R.anyPass(μ„œμˆ μžλ°°μ—΄) // λ°°μ—΄μ˜ 쑰건을 ν•˜λ‚˜λΌλ„ λ§Œμ‘±ν•˜λ©΄ true
  • λ‹€μŒ μ˜ˆλŠ” xκ°€ min <= x < max 쑰건을 λ§Œμ‘±ν•˜λŠ”μ§€ R.allPass ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•΄ ν™•μΈν•œλ‹€.
import * as R from 'ramda';

type NumberToBooleanFunc = (n: number) => boolean;

export const selectRange = (min: number, max: number): NumberToBooleanFunc =>
R.allPass([
R.lte(min),
R.gt(max),
]);

πŸ“š R.not ν•¨μˆ˜β€‹

  • μž…λ ₯값이 true이면 falseλ₯Ό λ°˜ν™˜ν•˜κ³  false이면 trueλ₯Ό λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜μ΄λ‹€.
  • 이전에 κ΅¬ν˜„ν•œ selectRange와 λ°˜λŒ€λ‘œ μž‘μš©ν•˜λŠ” notRangeλ₯Ό κ΅¬ν˜„ν•  수 μžˆλ‹€.
import * as R from 'ramda';
import { selectRange } from './selectRange';

export const notRange = (min: number, max: number) =>
R.pipe(selectRange(min, max), R.not);

πŸ“š R.ifElse ν•¨μˆ˜β€‹

  • R.ifElse ν•¨μˆ˜λŠ” μ„Έ 가지 λ§€κ°œλ³€μˆ˜λ₯Ό ν¬ν•¨ν•˜λŠ”λ°, 첫 λ²ˆμ§ΈλŠ” true/falseλ₯Ό λ°˜ν™˜ν•˜λŠ” μ„œμˆ μžλ₯Ό, 두 λ²ˆμ§ΈλŠ” μ„ νƒμžκ°€ trueλ₯Ό λ°˜ν™˜ν•  λ•Œ μ‹€ν–‰ν•  ν•¨μˆ˜λ₯Ό μ„Έ λ²ˆμ§ΈλŠ” μ„ νƒμžκ°€ falseλ₯Ό λ°˜ν™˜ν•  λ•Œ μ‹€ν–‰ν•  ν•¨μˆ˜μ΄λ‹€.
  • λ‹€μŒ μ½”λ“œλŠ” 1λΆ€ν„° 10κΉŒμ§€ μˆ˜μ—μ„œ 쀑간값 6보닀 μž‘μ€ μˆ˜λŠ” 1μ”© κ°μ†Œμ‹œν‚€κ³ , κ°™κ±°λ‚˜ 큰 μˆ˜λŠ” 1μ”© μ¦κ°€μ‹œν‚€λŠ” 것을 κ΅¬ν˜„ν•œ μ˜ˆμ΄λ‹€.
import * as R from 'ramda';

const input: number[] = R.range(1, 10 + 1);
const halfVale = input[input.length / 2]; // 6

const subtractOrAdd = R.pipe(
R.map(R.ifElse(
R.lte(halfVale), // 쑰건 μ„œμˆ μž: x => half <= x,
R.inc, // true 일 λ•Œ μ‹€ν–‰ν•  ν•¨μˆ˜
R.dec, // false 일 λ•Œ μ‹€ν–‰ν•  ν•¨μˆ˜
)),
R.tap(a => console.log(a)), // [0, 1, 2, 3, 4, 7, 8, 9, 10, 11]
);

const result = subtractOrAdd(input);

πŸ¦„ λ¬Έμžμ—΄ 닀루기​

  • λ¬Έμžμ—΄ μ•žλ’€μ˜ 백색 문자 자λ₯΄κΈ°
import * as R from 'ramda';

R.trim('\t hello \n'); // hello
  • λŒ€μ†Œλ¬Έμž μ „ν™˜
import * as R from 'ramda';

R.toUpper('Hello'); // HELLO
R.toLower('HELLO'); // hello
  • λ¬Έμžμ—΄μ„ λ°°μ—΄λ‘œ λ³€ν™˜κ³Ό 배열을 λ¬Έμžμ—΄λ‘œ λ³€ν™˜
import * as R from 'ramda';

const words: string[] = R.split(' ')(`Hello world!, I'm Peter.`);
// ['Hello', 'world!,', "I'm", 'Peter.']

R.join(' ')(words);
// "Hello world!, I'm Peter."

πŸ¦„ chance νŒ¨ν‚€μ§€λ‘œ 객체 λ§Œλ“€κΈ°β€‹

  • 예제λ₯Ό 따라 ν•΄λ΄„. (P.225 ~ P.232)

src/model directory μ°Έκ³ 

πŸ¦„ 렌즈λ₯Ό ν™œμš©ν•œ 객체의 속성 닀루기​

πŸ“š λ Œμ¦ˆλž€?​

  • λ Œμ¦ˆλŠ” ν•˜μŠ€μΌˆ μ–Έμ–΄μ˜ Control.Lens 라이브러리 λ‚΄μš© 쀑 μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œ λ™μž‘ν•  수 μžˆλŠ” κ²Œν„°μ™€ μ„Έν„° κΈ°λŠ₯λ§Œμ„ λžŒλ‹€ ν•¨μˆ˜λ‘œ κ΅¬ν˜„ν•œ 것이닀. λžŒλ‹€μ˜ 렌즈 κΈ°λŠ₯을 ν™œμš©ν•˜λ©΄ 객체의 속성값을 μ–»κ±°λ‚˜ μ„€μ •ν•˜λŠ” λ“±μ˜ μž‘μ—…μ„ μ‰½κ²Œ ν•  수 μžˆλ‹€.
  1. R.lens ν•¨μˆ˜λ‘œ 객체의 νŠΉμ • 속성에 λŒ€ν•œ 렌즈λ₯Ό λ§Œλ“ λ‹€.
  2. 렌즈λ₯Ό R.view ν•¨μˆ˜μ— μ μš©ν•΄ 속성값을 μ–»λŠ”λ‹€.
  3. 렌즈λ₯Ό R.set ν•¨μˆ˜μ— μ μš©ν•΄ 속성값이 바뀐 μƒˆλ‘œμš΄ 객체λ₯Ό μ–»λŠ”λ‹€.
  4. λ Œμ¦ˆμ™€ 속성값을 λ°”κΎΈλŠ” ν•¨μˆ˜λ₯Ό R.over ν•¨μˆ˜μ— μ μš©ν•΄ 값이 바뀐 μƒˆλ‘œμš΄ 객체λ₯Ό μ–»λŠ”λ‹€.

πŸ“š R.propκ³Ό R.assoc ν•¨μˆ˜β€‹

  • R.propλŠ” 객체의 νŠΉμ • 속성값을 κ°€μ Έμ˜€λŠ” ν•¨μˆ˜λ‘œμ„œ, 이런 λ™μž‘μ„ ν•˜λŠ” ν•¨μˆ˜λ₯Ό κ²Œν„°λΌκ³  ν•œλ‹€.
import * as R from 'ramda';

import { IPerson, makeRandomIPerson } from "./model/person";

const person: IPerson = makeRandomIPerson();

const name = R.pipe(
R.prop('name'),
R.tap(name => console.log(name)), // 랜덀 μƒμ„±λœ 이름
)(person);
  • 객체의 νŠΉμ • 속성값을 λ³€κ²½ν•˜λ €λ©΄ R.assoc ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜λŠ”λ°, 이런 λͺ©μ μœΌλ‘œ μ‚¬μš©ν•˜λŠ” ν•¨μˆ˜λ₯Ό 세터라고 ν•œλ‹€.
import * as R from 'ramda';

import { IPerson, makeRandomIPerson } from './model/person';

const getName = R.pipe(R.prop('name'), R.tap(name => console.log(name)));

const person: IPerson = makeRandomIPerson();
const originalName = getName(person); // 랜덀 μƒμ„±λœ 이름

const modifiedPerson = R.assoc('name', 'Seungmin')(person);
const modifiedName = getName(modifiedPerson); // Seungmin

πŸ“š R.lens ν•¨μˆ˜β€‹

  • λ Œμ¦ˆλŠ” λ‹€μŒμ²˜λŸΌ R.lens, R.prop, R.assoc의 μ‘°ν•©μœΌλ‘œ λ§Œλ“€ 수 μžˆλ‹€.
export const makeLens = (propName: string) => R.lens(R.prop(propName), R.assoc(propName));

πŸ“š R.view, R.set, R.over ν•¨μˆ˜β€‹

  • R.view, R.set, R.over ν•¨μˆ˜μ— 렌즈λ₯Ό μ μš©ν•΄μ„œ λ‹€μŒκ³Ό 같은 κ²Œν„°μ™€ μ„Έν„° 그리고 setterUsingFuncκ³Ό 같은 ν•¨μˆ˜λ₯Ό λ§Œλ“€ 수 μžˆλ‹€.
import * as R from 'ramda';

export const makeLens = (propName: string) =>
R.lens(R.prop(propName), R.assoc(propName));

export const getter = (lens) => R.view(lens);
export const setter = (lens) => <T>(newValue: T) => R.set(lens, newValue);
export const setterUsingFunc = (lens) => <T, R>(func: (T) => R) => R.over(lens, func);
  • μœ„ μ½”λ“œλ₯Ό μ‚¬μš©ν•œ lens.ts의 ν•¨μˆ˜λ“€μ„ ν…ŒμŠ€νŠΈλŠ” lens-test.tsλ₯Ό μ°Έκ³ 

πŸ“š R.lensPath ν•¨μˆ˜β€‹

  • λžŒλ‹€ λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œλŠ” 객체의 쀑첩 속성을 경둜(path)라고 ν•œλ‹€. longitude처럼 κΈ΄ 경둜의 속성을 렌즈둜 λ§Œλ“€λ €λ©΄ R.lensPath ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•œλ‹€.
렌즈 = R.lensPath(['location', 'coordinates', 'longitude']);
  • μ΄λ ‡κ²Œ 렌즈λ₯Ό λ§Œλ“€μ—ˆμœΌλ©΄ μ•žμ—μ„œ κ΅¬ν˜„ν•œ lens.ts의 κ²Œν„°μ™€ μ„Έν„° 그리고 setterUsingFunc ν•¨μˆ˜μ— λ°”λ‘œ μ μš©ν•  수 μžˆλ‹€.
import * as R from 'ramda';

import { getter, setter, setterUsingFunc } from './lens';
import { IPerson, makeRandomIPerson } from './model/person';

const longitudeLens = R.lensPath(['location', 'coordinates', 'longitude']);
const getLongitude = getter(longitudeLens);
const setLongitude = setter(longitudeLens);
const setLongitudeUsingFunc = setterUsingFunc(longitudeLens);

const person: IPerson = makeRandomIPerson();

const longitude = getLongitude(person);
const newPerson = setLongitude(0.1234567)(person);
const anotherPerson = setLongitudeUsingFunc(R.add(0.1234567))(person);

console.log(
longitude, getLongitude(newPerson), getLongitude(anotherPerson),
);
// 91.00362 0.1234567 91.1270767

πŸ¦„ 객체 닀루기​

πŸ“š R.toPairs와 R.fromPairs ν•¨μˆ˜β€‹

  • R.toPairs ν•¨μˆ˜λŠ” 객체의 속성듀을 λΆ„ν•΄ν•΄ λ°°μ—΄λ‘œ λ§Œλ“€μ–΄μ€€λ‹€. μ΄λ•Œ λ°°μ—΄μ˜ 각 μ•„μ΄ν…œμ€ [string, any] νƒ€μž…μ˜ νŠœν”Œμ΄λ‹€.
import * as R from 'ramda';

import { IPerson, makeRandomIPerson } from './model/person';

const person: IPerson = makeRandomIPerson();
const pairs: [string, any][] = R.toPairs(person);

console.log('pairs', pairs);
  • R.fromPairs ν•¨μˆ˜λŠ” [ν‚€:κ°’] ν˜•νƒœμ˜ μ•„μ΄ν…œμ„ 가진 배열을 λ‹€μ‹œ 객체둜 λ§Œλ“€μ–΄ μ€€λ‹€.
import * as R from 'ramda';

import { IPerson, makeRandomIPerson } from './model/person';

const pairs: [string, any][] = R.toPairs(makeRandomIPerson());
const person: IPerson = R.fromPairs(pairs) as IPerson;

console.log('person: ', person);

πŸ“š R.keys와 R.values ν•¨μˆ˜β€‹

  • R.keys ν•¨μˆ˜λŠ” 객체의 속성 μ΄λ¦„λ§Œ μΆ”λ €μ„œ string[] νƒ€μž… λ°°μ—΄λ‘œ λ°˜ν™˜ν•œλ‹€.
import * as R from 'ramda';

import { makeRandomIPerson } from './model/person';

const keys: string[] = R.keys(makeRandomIPerson());

console.log('keys: ', keys); // keys: ['name', 'age', 'title', 'location']
  • R.values ν•¨μˆ˜λŠ” 객체의 μ†μ„±κ°’λ§Œ μΆ”λ €μ„œ any[] νƒ€μž… λ°°μ—΄λ‘œ λ°˜ν™˜ν•œλ‹€.
import * as R from 'ramda';

import { makeRandomIPerson } from './model/person';

const values: any[] = R.values(makeRandomIPerson());

console.log('values: ', values);

πŸ“š R.zipObj ν•¨μˆ˜β€‹

  • R.zipObj ν•¨μˆ˜λŠ” ν‚€ λ°°μ—΄κ³Ό κ°’ λ°°μ—΄μ΄λΌλŠ” 두 가지 λ§€κ°œλ³€μˆ˜λ₯Ό κ²°ν•©ν•΄ 객체둜 λ§Œλ“€μ–΄ μ€€λ‹€.
import * as R from 'ramda';

import { IPerson, makeRandomIPerson } from './model/person';

const originalPerson: IPerson = makeRandomIPerson();
const keys: string[] = R.keys(originalPerson);
const values: any[] = R.values(originalPerson);
const zippedPerson: IPerson = R.zipObj(keys, values) as IPerson;

console.log(
'originalPerson: ', originalPerson,
'zippedPerson: ', zippedPerson,
);

πŸ“š R.mergeLeft와 R.mergeRight ν•¨μˆ˜β€‹

  • R.mergeLeft와 R.mergeRight ν•¨μˆ˜λŠ” 두 개의 객체λ₯Ό μž…λ ₯λ°›μ•„ 두 객체의 속성듀을 κ²°ν•©ν•΄ μƒˆλ‘œμš΄ 객체λ₯Ό μƒμ„±ν•œλ‹€.
μƒˆλ‘œμš΄κ°μ²΄ = R.mergeLeft(객체1)(객체2); // 속성값이 λ‹€λ₯Ό λ•Œ μ™Όμͺ½ 객체의 μš°μ„ μˆœμœ„κ°€ λ†’μŒ
μƒˆλ‘œμš΄κ°μ²΄ = R.mergeRight(객체1)(객체2); // 속성값이 λ‹€λ₯Ό λ•Œ 였λ₯Έμͺ½ 객체의 μš°μ„ μˆœμœ„κ°€ λ†’μŒ
  • λ‹€μŒ κ²°κ³ΌλŠ” left μͺ½μ˜ name 속성값이 μ„€μ •λœλ‹€.
import * as R from 'ramda';

const left = { name: 'Jack' };
const right = { name: 'Jane', age: 32 };

const person = R.mergeLeft(left, right);
console.log(person); // { name: 'Jack', age: 32 }

πŸ“š R.mergeDeepLeft와 R.mergeDeepRight ν•¨μˆ˜β€‹

  • R.mergeLeft와 R.mergeRight ν•¨μˆ˜λŠ” 객체의 속성에 λ‹΄κΈ΄ 객체λ₯Ό λ°”κΎΈμ§€λŠ” λͺ»ν•œλ‹€.
  • λ°˜λ©΄μ— 이 두 ν•¨μˆ˜λ“€μ€ μ•„λž˜ μ˜ˆμ œμ™€ 같이 location, coordinates와 같은 경둜의 속성값듀도 λ°”κΏ€ 수 μžˆλ‹€.
import * as R from 'ramda';

import { IPerson, makeRandomIPerson } from './model/person';
import { ILocation, makeRandomILocation } from './model/location';
import { ICoordinates, makeRandomICoordinates } from './model/coordinates';

const person: IPerson = makeRandomIPerson();
const location: ILocation = makeRandomILocation();
const coordinates: ICoordinates = makeRandomICoordinates();

const newLocation = R.mergeDeepRight(location, { coordinates });
const newPerson = R.mergeDeepRight(person, { location: newLocation });

console.log('person: ', person);
console.log('newPerson: ', newPerson);

πŸ¦„ λ°°μ—΄ 닀루기​

πŸ“š R.prepend와 R.append ν•¨μˆ˜β€‹

  • R.prepend와 R.appendλŠ” κΈ°μ‘΄ λ°°μ—΄μ˜ μ•žλ’€μ— μƒˆ μ•„μ΄ν…œμ„ μ‚½μž…ν•œ μƒˆ 배열을 λ§Œλ“€μ–΄ μ€€λ‹€.
import * as R from 'ramda';

const array: number[] = [3, 4];
const newPrependArray = R.prepend(1)(array);

console.log(newPrependArray); // [1, 3, 4]

const newAppendArray = R.append(1)(array);

console.log(newAppendArray); // [3, 4, 1]

πŸ“š R.flatten ν•¨μˆ˜β€‹

  • λ°°μ—΄μ˜ ꡬ쑰가 λ‹€μŒμ²˜λŸΌ λ³΅μž‘ν•˜κ²Œ κ΅¬μ„±λ˜μ–΄ 있으면, 이 배열을 λŒ€μƒμœΌλ‘œ λžŒλ‹€ 라이브러리의 κΈ°λŠ₯을 μ μš©ν•˜λŠ” 것은 μ–΄λ ΅λ‹€.
[[[1, 1], [1, 2]], [[2, 1], [2, 2]]]
  • R.flatten ν•¨μˆ˜λŠ” μœ„μ²˜λŸΌ λ³΅μž‘ν•œ 1μ°¨μ›μ˜ ν‰ν‰ν•œ λ°°μ—΄λ‘œ λ°”κΏ”μ€€λ‹€.
import * as R from 'ramda';

const array = [[[1, 1], [1, 2]], [[2, 1], [2, 2]]];

const flattendArray = R.flatten(array);
console.log(flattendArray); // [1, 1, 1, 2, 2, 1, 2, 2]

πŸ“š R.unnest ν•¨μˆ˜β€‹

  • R.unnest ν•¨μˆ˜λŠ” R.flatten보닀 쒀더 μ •κ΅ν•˜κ²Œ 배열을 가곡해쀀닀.
const array = [[[1, 1], [1, 2]], [[2, 1], [2, 2]]];

const unnestedArray = R.unnest(array);
console.log(unnestedArray); //[[1, 1], [1, 2], [2, 1], [2, 2]]

const twoUnnestedArray = R.pipe(R.unnest, R.unnest)(array);
console.log(twoUnnestedArray); // [1, 1, 1, 2, 2, 1, 2, 2]

πŸ“š R.sort ν•¨μˆ˜β€‹

  • λ°°μ—΄μ˜ νƒ€μž…μ΄ number[]라면 R.sort ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•΄ 배열을 λ‚΄λ¦Όμ°¨μˆœμ΄λ‚˜ μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬ν•  수 μžˆλ‹€.
import * as R from 'ramda';

const array: number[] = [5, 6, 2, 1, 7, 9, 8, 3, 4];
const sortedArray = R.sort((a: number, b: number): number => a - b)(array);

console.log(sortedArray); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

πŸ“š R.sortBy ν•¨μˆ˜β€‹

  • 배열에 λ‹΄κΈ΄ μ•„μ΄ν…œμ΄ 객체라면 νŠΉμ • 속성값에 따라 μ •λ ¬ν•΄μ•Ό ν•˜λŠ”λ°, μ΄λ•Œ R.sortBy ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•œλ‹€.
import * as R from 'ramda';

import { IPerson, makeRandomIPerson } from './model/person';

const persons: IPerson[] = R.range(1, 4 + 1).map(makeRandomIPerson);
const nameSortedPersons = R.sortBy(R.prop('name'))(persons);
const ageSortedPersons = R.sortBy(R.prop('age'))(persons);

πŸ“š R.sortWith ν•¨μˆ˜β€‹

  • R.sortBy ν•¨μˆ˜λŠ” μ˜€λ¦„μ°¨μˆœ λ‚΄λ¦Όμ°¨μˆœ 정렬을 ν•˜μ§€ λͺ»ν•˜κ³  항상 μ˜€λ¦„μ°¨μˆœμœΌλ‘œλ§Œ μ •λ ¬ν•œλ‹€.
  • R.sortWith ν•¨μˆ˜λŠ” R.ascend, R.descend ν•¨μˆ˜μ™€ ν•¨κ»˜ μ‚¬μš©λ˜μ–΄ μ˜€λ¦„μ°¨μˆœ, λ‚΄λ¦Όμ°¨μˆœ 정렬을 ν•  수 μžˆλ‹€.
import * as R from 'ramda';

import { IPerson, makeRandomIPerson } from './model/person';

const persons: IPerson[] = R.range(1, 4 + 1).map(makeRandomIPerson);
const nameSortedPersons = R.sortWith([
R.descend(R.prop('name'))
])(persons);

πŸ¦„ μ‘°ν•© 논리 μ΄ν•΄ν•˜κΈ°β€‹

  • λžŒλ‹€ μˆ˜ν•™μ˜ λͺ¨λ“  이둠을 컴퓨터 ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄λ‘œ ν‘œν˜„ν•  수 μ—†μœΌλ―€λ‘œ μ–΄λ–€ μ œν•œλœ λ²”μœ„μ—μ„œ λžŒλ‹€ μˆ˜ν•™μ„ κ΅¬ν˜„ν•˜κΈ° μœ„ν•΄ μ‘°ν•© 논리학이 생겨났닀.

πŸ“š μ‘°ν•©μžλž€?​

  • μ‘°ν•© 논리학은 μ‘°ν•©μž(combinator)λΌλŠ” νŠΉλ³„ν•œ ν˜•νƒœμ˜ κ³ μ°¨ ν•¨μˆ˜λ“€μ„ κ²°ν•©ν•΄ μƒˆλ‘œμš΄ μ‘°ν•©μžλ₯Ό λ§Œλ“€μ–΄ λ‚΄λŠ” 것이닀. μ΄λŠ” ν•¨μˆ˜ν˜• μ–Έμ–΄μ˜ 컴파일러λ₯Ό λ§Œλ“œλŠ” 데 ν•„μš”ν•œ 이둠을 κ²€μ¦ν•˜κ³  κ°œλ°œν•  λ•Œ 주둜 μ‚¬μš©λœλ‹€.
  • λžŒλ‹€ λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œ λͺ‡ 가지 유λͺ…ν•œ μ‘°ν•©μžλ₯Ό μ œκ³΅ν•œλ‹€.
μ‘°ν•©μž μ΄λ¦„μ˜λ―ΈλžŒλ‹€ ν•¨μˆ˜ 이름
IidentityR.identity
KconstantR.always
TthrushR.applyTo
WduplicationR.unnest
CflipR.flip
SsubstitutionR.ap

πŸ“š R.chain ν•¨μˆ˜ 탐ꡬ​

  • λžŒλ‹€ λΌμ΄λΈŒλŸ¬λ¦¬λŠ” R.chainμ΄λΌλŠ” ν•¨μˆ˜λ₯Ό μ œκ³΅ν•œλ‹€. 이 ν•¨μˆ˜λŠ” ν•¨μˆ˜λ₯Ό λ§€κ°œλ³€μˆ˜λ‘œ λ°›μ•„ λ™μž‘ν•˜λŠ” ν•¨μˆ˜λ‘œμ„œ, λ§€κ°œλ³€μˆ˜κ°€ ν•œ 개일 λ•Œμ™€ 두 개일 λ•Œμ˜ λ™μž‘μ΄ 쑰금 λ‹€λ₯΄λ‹€.
import * as R from 'ramda';

const array = [1, 2, 3];

R.pipe(
R.chain(n => [n, n]),
R.tap(n => console.log(n)), // [1, 1, 2, 2, 3, 3]
)(array);

R.pipe(
R.chain(R.append, R.head),
R.tap(n => console.log(n)), // [1, 2, 3, 1]
)(array);
  • R.chain ν•¨μˆ˜λŠ” λ§€κ°œλ³€μˆ˜κ°€ ν•œ 개일 λ•ŒλŠ” λ‹€μŒ flatMap ν•¨μˆ˜μ²˜λŸΌ λ™μž‘ν•œλ‹€.
import * as R from 'ramda';

const array = [1, 2, 3];

const flatMap = (f) => R.pipe(
R.map(f),
R.flatten,
);

R.pipe(
flatMap(n => [n, n]),
R.tap(n => console.log(n)), // [1, 1, 2, 2, 3, 3]
)(array);
  • λ§€κ°œλ³€μˆ˜κ°€ 두 개일 λ•ŒλŠ” λ‹€μŒ μ½”λ“œμ˜ chainTwoFunc ν•¨μˆ˜μ²˜λŸΌ λ™μž‘ν•œλ‹€.
import * as R from 'ramda';

const chainTwoFunc = (firstFn, secondFn) => (x) => firstFn(secondFn(x), x);

const array = [1, 2, 3];

R.pipe(
chainTwoFunc(R.append, R.head), // array => R.append(R.head(array))(array)
R.tap(n => console.log(n)), // [1, 2, 3, 1]
)(array);

πŸ“š R.flip μ‘°ν•©μžβ€‹

  • R.flipν•¨μˆ˜λŠ” 2μ°¨ κ³ μ°¨ ν•¨μˆ˜μ˜ λ§€κ°œλ³€μˆ˜ μˆœμ„œλ₯Ό μ„œλ‘œ λ°”κΏ”μ£ΌλŠ” 역할을 ν•œλ‹€.
// flip ν•¨μˆ˜μ²˜λŸΌ κ΅¬ν˜„λ˜μ–΄ μžˆλ‹€.
const flip = cb => a => b => cb(b)(a);

πŸ“š R.identity μ‘°ν•©μžβ€‹

  • R.identityλŠ” λ‹€μŒμ²˜λŸΌ κ΅¬ν˜„λœ κ°€μž₯ λ‹¨μˆœν•œ μ‘°ν•©μžμ΄μ§€λ§Œ, μ‘°ν•©μžμ˜ ꡬ쑰상 λ°˜λ“œμ‹œ ν•¨μˆ˜κ°€ μžˆμ–΄μ•Ό ν•˜λŠ” 곳에 μœ„μΉ˜ν•  λ•Œ κ·Έ μœ„λ ₯을 λ°œνœ˜ν•œλ‹€.
const identity = x => x;
  • μ•žμ„œ κ΅¬ν˜„ν•œ flatMap ν•¨μˆ˜λŠ” 콜백 ν•¨μˆ˜κ°€ ν•œ 개 ν•„μš”ν•˜λ‹€. λ‹€μŒ μ½”λ“œλŠ” flatMap ν•¨μˆ˜κ°€ μš”κ΅¬ν•˜λŠ” 콜백 ν•¨μˆ˜μ— R.identity μ‘°ν•©μžλ₯Ό μ‚¬μš©ν•œ μ˜ˆμ΄λ‹€. unnest ν•¨μˆ˜λŠ” R.unnest ν•¨μˆ˜μ™€ λ˜‘κ°™μ΄ λ™μž‘ν•œλ‹€.
import * as R from 'ramda';

import { flatMap } from './flatMap';

const unnest = flatMap(R.identity);

const array = [[1], [2], [3]];

R.pipe(
unnest,
R.tap(n => console.log(n)), // [1, 2, 3]
)(array);

πŸ“š R.always μ‘°ν•©μžβ€‹

  • R.always μ‘°ν•©μžλŠ” λ‹€μŒμ²˜λŸΌ 두 개의 κ³ μ°¨ λ§€κ°œλ³€μˆ˜ 쀑 첫 번째 것을 λ°˜ν™˜ν•œλ‹€.
const always = x => y => x;
  • R.alwaysλŠ” 두 개의 λ§€κ°œλ³€μˆ˜κ°€ ν•„μš”ν•œ μ‘°ν•©μžμ— 마치 R.identity처럼 μ‚¬μš©λœλ‹€. 비둝 R.alwaysλŠ” 항상 첫 번째 λ§€κ°œλ³€μˆ˜κ°’λ§Œ λ°˜ν™˜ν•˜μ§€λ§Œ, R.flip(R.always)λŠ” λ°˜λŒ€λ‘œ 항상 두 번째 λ§€κ°œλ³€μˆ˜κ°’λ§Œ λ°˜ν™˜ν•œλ‹€.
import * as R from 'ramda';

const always = a => b => a;
const flip = cb => a => b => cb(b)(a);

const first = <T>(a: T) => (b: T): T => always(a)(b);
const second = <T>(a: T) => (b: T): T => flip(always)(a)(b);

console.log(first(1)(2), second(1)(2)); // 1 2

πŸ“š R.applyTo μ‘°ν•©μžβ€‹

  • νŠΉλ³„ν•˜κ²Œ 값을 첫 번째 λ§€κ°œλ³€μˆ˜λ‘œ, 그리고 이 값을 μž…λ ₯으둜 ν•˜λŠ” 콜백 ν•¨μˆ˜λ₯Ό 두 번째 λ§€κ°œλ³€μˆ˜λ‘œ λ°›μ•„ λ‹€μŒ μ½”λ“œμ²˜λŸΌ λ™μž‘ν•œλ‹€.
const applyTo = value => cb => cb(value);
  • λ‹€μŒμ€ R.applyTo μ˜ˆμ œμ΄λ‹€.
import * as R from 'ramda';

const T = value => R.pipe(
R.applyTo(value),
R.tap(value => console.log(value)),
);

const value100 = T(100);
const sameValue = value100(R.identity); // 100
const add1Value = value100(R.add(1)); // 101

πŸ“š R.ap μ‘°ν•©μžβ€‹

  • R.ap μ‘°ν•©μžλŠ” 콜백 ν•¨μˆ˜λ“€μ˜ 배열을 첫 번째 λ§€κ°œλ³€μˆ˜λ‘œ, 배열을 두 번째 λ§€κ°œλ³€μˆ˜λ‘œ μΌλ²½λ°›λŠ” 2μ°¨ κ³ μ°¨ ν•¨μˆ˜μ΄λ‹€.
const ap = ([콜백 ν•¨μˆ˜]) => λ°°μ—΄ => [μ½œλ°±ν•¨μˆ˜](λ°°μ—΄);
  • R.apλŠ” 콜백 ν•¨μˆ˜κ°€ ν•œ 개일 λ•ŒλŠ” 마치 R.map ν•¨μˆ˜μ²˜λŸΌ λ™μž‘ν•œλ‹€.
import * as R from 'ramda';

const callAndAppend = R.pipe(
R.ap([R.multiply(2)]),
R.tap(a => console.log(a)),
);

const input = [1, 2, 3];
const result = callAndAppend(input); // [2, 4, 6]
  • 그런데 콜백 ν•¨μˆ˜κ°€ λ‹€μŒμ²˜λŸΌ 두 개일 λ•ŒλŠ” 마치 R.chain(n => [n, n]) ν˜•νƒœλ‘œ λ™μž‘ν•œλ‹€.
  • R.apλŠ” 두 콜백 ν•¨μˆ˜λ₯Ό μ μš©ν•œ 각각의 배열을 λ§Œλ“  λ‹€μŒ, 연산이 λλ‚˜λ©΄ 이 배열을 λͺ¨λ‘ 톡합해 ν•œ 개둜 λ§Œλ“€μ–΄μ€€λ‹€.
import * as R from 'ramda';

const callAndAppend = R.pipe(
R.ap([R.multiply(2), R.add(10)]),
R.tap(a => console.log(a)),
);

const input = [1, 2, 3];
const result = callAndAppend(input); // [2, 4, 6, 11, 12, 13]
  • λ‹€μŒ μ½”λ“œλŠ” R.ap μ‘°ν•©μžμ˜ 이런 μ„±μ§ˆμ„ μ΄μš©ν•΄ [1, 2, 3] 배열을 μ„Έ 번 λ³΅μ œν•œ λ’€ ν†΅ν•©ν•œ 배열을 λ§Œλ“œλŠ” μ˜ˆμ΄λ‹€.
import * as R from 'ramda';

const repeat = (N, cb) => R.range(1, N + 1).map(n => cb);

const callAndAppend = R.pipe(
R.ap(repeat(3, R.identity)),
R.tap(a => console.log(a)),
);

const input = [1, 2, 3];
const result = callAndAppend(input); // [1, 2, 3, 1, 2, 3, 1, 2, 3]