728x90

🧠 Ramda 여덟 번째 스텝: 조건을 조합하는 함수들 – R.allPass, R.anyPass, R.both, R.either, R.not

여러 조건을 한꺼번에 체크해야 하는 경우, &&, ||, ! 같은 연산자를 조합하면 코드가 길어지고 읽기 어려워집니다.

Ramda에서는 조건들을 함수처럼 조합할 수 있습니다. 함수형 스타일의 논리 조합 도구들을 활용해
더 선언적이고 재사용 가능한 조건식을 만들 수 있습니다.


🧠 Topic Summary

주요 논리 조합 함수

함수 설명
R.allPass([f1, f2]) 모든 조건이 참이면 true
R.anyPass([f1, f2]) 하나라도 참이면 true
R.both(f1, f2) f1 && f2
R.either(f1, f2) f1
R.not(f) !f

🛠 Usage Examples

예제 1: 모든 조건 만족 – R.allPass

const isValidUser = R.allPass([
  R.propSatisfies(R.lt(R.__, 100), 'age'),       // age < 100
  R.propSatisfies(R.test(/^A/), 'name')          // name이 A로 시작
]);

isValidUser({ name: 'Alice', age: 30 }); // true
isValidUser({ name: 'Bob', age: 30 });   // false

예제 2: 하나라도 만족 –

R.anyPass

const isSpecialName = R.anyPass([
  R.propEq('name', 'Admin'),
  R.propEq('name', 'Root')
]);

isSpecialName({ name: 'Admin' }); // true
isSpecialName({ name: 'User' });  // false

예제 3: 둘 다 만족 –

R.both

const isLongAndUpper = R.both(
  R.pipe(R.length, R.gt(R.__, 5)),
  R.equals(R.toUpper(R.__))
);

isLongAndUpper('HELLO');    // false
isLongAndUpper('FUNCTION'); // true

예제 4: 둘 중 하나 –

R.either

const isNullOrEmpty = R.either(
  R.isNil,
  R.pipe(R.trim, R.isEmpty)
);

isNullOrEmpty(null);       // true
isNullOrEmpty('   ');      // true
isNullOrEmpty('content');  // false

예제 5: 반전 –

R.not

const isNotAdmin = R.pipe(
  R.propEq('role', 'admin'),
  R.not
);

isNotAdmin({ role: 'user' }); // true

⚠️ Common Pitfalls

1. R.both 와 R.either 는 두 개만 조합 가능**

3개 이상 조건일 경우 R.allPass, R.anyPass를 사용하세요.

// ❌ 잘못된 예
R.both(f1, R.both(f2, f3));

// ✅ 올바른 예
R.allPass([f1, f2, f3]);

2. 조건 함수는

Boolean을 반환해야 함

R.allPass, R.anyPass에 들어가는 함수들은 모두 true 또는 false를 반환해야 정상 작동합니다.


3. 함수형 조건은 재사용에 매우 좋지만, 디버깅이 어려울 수 있음

각 조건이 어떤 결과를 냈는지 확인하려면 중간 로깅이 필요할 수 있습니다.

이를 위해 R.tap(console.log) 같은 도구를 함께 사용할 수 있습니다.


🧩 조합 예제: pipe + 조건 필터

const users = [
  { name: 'Admin', age: 50 },
  { name: 'Alice', age: 30 },
  { name: 'Root', age: 120 },
  { name: 'Bob', age: 25 },
];

const isActiveUser = R.allPass([
  R.propSatisfies(R.lt(R.__, 100), 'age'),
  R.pipe(R.prop('name'), R.complement(R.startsWith('A')))
]);

R.filter(isActiveUser, users);
// 결과: [{ name: 'Bob', age: 25 }]

✅ Call to Action

이제 복잡한 조건도 논리 함수로 선언적으로 조합할 수 있습니다.

R.allPass, R.anyPass를 조합하면 가독성 높고 테스트하기 쉬운 조건식을 만들 수 있어요.

실습 아이디어:

  • 입력값이 비어 있지 않고, 숫자일 때만 처리

  • 사용자 권한이 ‘admin’이거나 ‘editor’일 때만 접근 허용

  • 나이 18세 이상이고 이름이 특정 문자열로 시작하는 조건 만들기

728x90
728x90

🔍 Ramda 일곱 번째 스텝: Lens 시스템 – R.lens, R.view, R.set, R.over

복잡한 중첩 객체에서 특정 값을 읽거나 수정할 때, 직접 경로를 따라가면서 작업하면 코드가 복잡해지기 쉽습니다.
Ramda의 Lens 시스템은 데이터의 특정 부분을 가리키는 '렌즈'를 정의하고, 그 렌즈를 통해 값을 읽거나 바꾸는 방식으로 작동합니다.


🧠 Topic Summary

Lens란?

렌즈(Lens)는 특정 데이터 구조 안의 "부분"을 안전하고 선언적으로 읽거나 수정하는 추상화 도구입니다.

Ramda에서 Lens는 R.lens, R.view, R.set, R.over 네 가지 함수로 구성됩니다:

함수 역할
R.lens(getter, setter) 렌즈 생성
R.view(lens, data) 렌즈로 보기 (읽기)
R.set(lens, value, data) 렌즈로 값 설정
R.over(lens, transformFn, data) 렌즈를 통해 값 변경

🛠 Usage Examples

기본 사용: 이름 필드 조작

import * as R from 'ramda';

const person = { name: 'Alice', age: 30 };

const nameLens = R.lens(R.prop('name'), R.assoc('name'));

// 보기
R.view(nameLens, person); // 'Alice'

// 설정
R.set(nameLens, 'Bob', person); 
// { name: 'Bob', age: 30 }

// 변형
R.over(nameLens, R.toUpper, person); 
// { name: 'ALICE', age: 30 }

중첩 객체: 주소 안의 도시만 조작

const user = {
  profile: {
    name: 'Alice',
    address: {
      city: 'Seoul',
      zip: '12345'
    }
  }
};

const cityLens = R.lensPath(['profile', 'address', 'city']);

R.view(cityLens, user); // 'Seoul'

R.set(cityLens, 'Busan', user);
// 중첩 구조는 유지한 채로 city만 변경

R.over(cityLens, R.toUpper, user); 
// city: 'SEOUL'

🔁 Lens vs Path vs Assoc

기능 접근 방식 사용 예
R.path 읽기 R.path(['a', 'b'], obj)
R.assocPath 쓰기 R.assocPath(['a', 'b'], val, obj)
R.lensPath 읽기/쓰기/변형 모두 가능 R.over(R.lensPath(['a', 'b']), fn, obj)

⚠️ Common Pitfalls

1.

R.lens

는 getter와 setter 함수가 필요함

R.lens(R.prop('key'), R.assoc('key'))처럼 명시적으로 작성해야 합니다.

대부분의 경우는 R.lensProp, R.lensPath를 사용하는 게 간결합니다.

R.lensProp('name'); // name 필드를 위한 렌즈
R.lensPath(['user', 'profile']); // 중첩 구조용 렌즈

2. 렌즈 연산은

불변성을 유지하므로, 원본 객체는 절대 수정되지 않음

const newObj = R.set(lens, val, oldObj);
console.log(oldObj); // 그대로
console.log(newObj); // 변경된 복사본

🧩 조합 예제: pipe와 함께 Lens 활용

const person = {
  name: 'jane',
  score: 85,
  meta: {
    approved: false
  }
};

const scoreLens = R.lensProp('score');
const approvedLens = R.lensPath(['meta', 'approved']);

const processPerson = R.pipe(
  R.over(scoreLens, R.add(5)),
  R.set(approvedLens, true)
);

processPerson(person);
// { name: 'jane', score: 90, meta: { approved: true } }

✅ Call to Action

Lens는 중첩된 데이터를 불변적으로 다루기 위한 함수형 접근 방식의 정수입니다.

이제 여러분도 view, set, over를 조합해 안전하고 예측 가능한 상태 조작 코드를 작성해보세요!

실습 아이디어:

  • 중첩된 유저 상태 객체에서 알림 설정값을 꺼내거나 바꾸기

  • form 데이터에서 특정 필드만 수정하기

  • 배열 내부 객체의 특정 필드 변환하기 (map + lens)

728x90
728x90

🔀 Ramda 여섯 번째 스텝: 조건 분기 – R.ifElse, R.when, R.unless, R.cond

함수형 프로그래밍에서는 if, else, switch 같은 제어문을 덜 사용하고,
조건 자체를 함수처럼 조합하여 흐름을 제어합니다.

Ramda는 이를 위한 여러 함수들을 제공합니다:

  • R.ifElse: 조건 분기 (if/else)
  • R.when: 조건이 참일 때만 함수 실행
  • R.unless: 조건이 거짓일 때만 함수 실행
  • R.cond: 여러 조건을 나열 (switch 또는 else-if)

🧠 Topic Summary

전통적 제어문 vs 함수형 조건

전통적 방식 함수형 방식
if (x > 10) {...} R.ifElse(predicate, ifFn, elseFn)
x > 10 && do() R.when(predicate, fn)
x <= 10 && do() R.unless(predicate, fn)
switch-case R.cond([...])

✅ R.ifElse

R.ifElse(predicateFn, thenFn, elseFn)

예제: 점수가 60 이상이면 통과, 아니면 재시험

const getResult = R.ifElse(
  R.gte(R.__, 60),           // 점수가 60 이상?
  R.always('Pass'),          // 참이면
  R.always('Retake')         // 거짓이면
);

getResult(70); // 'Pass'
getResult(50); // 'Retake'

✅ R.when

R.when(predicateFn, fn)

조건이 참일 때만 fn을 실행하고, 거짓이면 그대로 반환합니다.

예제: 100보다 작으면 2배

const doubleIfSmall = R.when(
  R.lt(R.__, 100),
  R.multiply(2)
);

doubleIfSmall(80); // 160
doubleIfSmall(150); // 150 (그대로 반환)

✅ R.unless

R.unless(predicateFn, fn)

조건이 거짓일 때만 fn을 실행하고, 참이면 그대로 반환합니다.

예제: 100 이상이면 그대로, 아니면 100으로 바꾸기

const ensureAtLeast100 = R.unless(
  R.gte(R.__, 100),
  R.always(100)
);

ensureAtLeast100(120); // 120
ensureAtLeast100(80); // 100

✅ R.cond

R.cond([
  [predicate1, resultFn1],
  [predicate2, resultFn2],
  ...
  [R.T, defaultFn]
])

조건들을 배열로 나열해 switch-case처럼 작동하는 함수입니다.

예제: 점수 등급 출력하기

const grade = R.cond([
  [R.gte(R.__, 90), R.always('A')],
  [R.gte(R.__, 80), R.always('B')],
  [R.gte(R.__, 70), R.always('C')],
  [R.gte(R.__, 60), R.always('D')],
  [R.T, R.always('F')]
]);

grade(85); // 'B'
grade(72); // 'C'
grade(45); // 'F'

⚠️ Common Pitfalls

1. R.ifElse는 반드시 세 개의 함수가 필요하다

R.ifElse(predicate); // ❌ 에러

함수를 꼭 세 개 넣어야 작동합니다: 조건, 참일 때 함수, 거짓일 때 함수.


2. R.when/R.unless는 조건이 만족하지 않으면 원본 값을 그대로 반환

이건 실수로 값을 가공하지 않았다고 오해할 수 있습니다. 항상 기대값 확인이 필요합니다.


3. R.cond는 R.T 를 써서 default 조건 을 꼭 넣어야 안전

조건에 맞는 항목이 없을 경우, 에러가 나거나 undefined가 나올 수 있습니다.

R.T는 항상 참이므로 “else” 역할을 합니다.


🧩 조합 예제: pipe + 조건

const adjustScore = R.pipe(
  R.when(R.lt(R.__, 60), R.always(60)),  // 최소 점수 보장
  grade                                   // 앞서 만든 등급 함수와 연결
);

adjustScore(50); // 'D'
adjustScore(85); // 'B'

✅ Call to Action

if와 switch에서 벗어나, 함수형 스타일로 조건을 표현해보세요.

조건도 함수로 다룰 수 있다는 것이 함수형 프로그래밍의 진짜 재미입니다.

실습 아이디어:

  • 금액이 10000 이상이면 할인 적용 (R.ifElse)

  • 배열이 비어있으면 “없음”, 아니면 첫 번째 항목 추출 (R.ifElse + R.isEmpty)

  • 나이에 따라 성인/청소년/어린이 구분 (R.cond)

728x90
728x90

🧱 Ramda 다섯 번째 스텝: 객체 다루기 – R.prop, R.path, R.assoc, R.evolve

함수형 프로그래밍에서는 객체를 직접 수정하지 않고, 복사된 새 객체를 만들며 다룹니다.
Ramda는 이를 돕기 위한 강력한 함수들을 제공합니다.

오늘 배울 함수들:

  • R.prop – 특정 key의 값을 가져오기
  • R.path – 깊숙한 nested 값 가져오기
  • R.assoc – 객체의 key를 업데이트 (immutable)
  • R.evolve – 여러 key에 대해 변환 적용

🧠 Topic Summary

불변(immutability)을 유지하면서 객체 다루기

JavaScript에서는 객체를 직접 변경할 수 있지만, 함수형 프로그래밍에서는 원본 객체를 건드리지 않고 새로운 객체를 만들어야 합니다.

Ramda의 함수들은 이 원칙을 지키면서도 객체를 쉽게 다룰 수 있도록 도와줍니다.


🔍 R.prop – 객체의 특정 필드 값을 가져오기

R.prop('name', { name: 'Alice' }); // 'Alice'
R.prop('age')({ name: 'Alice', age: 30 }); // 30

커리화 가능하므로 R.map(R.prop('name'))처럼 리스트에서 사용하기 좋습니다.

예제: 사용자 이름 목록 추출

const users = [
  { name: 'Alice' },
  { name: 'Bob' },
  { name: 'Charlie' }
];

R.map(R.prop('name'), users); 
// ['Alice', 'Bob', 'Charlie']

🌳 R.path – 깊은 속성 값 읽기

const user = { profile: { name: 'Alice' } };

R.path(['profile', 'name'], user); // 'Alice'
R.path(['profile', 'age'], user); // undefined

예제: 안전하게 중첩된 속성 읽기

const getUserCity = R.path(['address', 'city']);

getUserCity({ address: { city: 'Seoul' } }); // 'Seoul'
getUserCity({}); // undefined

🧩 R.assoc – 객체에 key-value 할당 (불변 방식)

R.assoc('age', 30, { name: 'Alice' }); 
// { name: 'Alice', age: 30 }

원본 객체는 변경되지 않고, 새 객체가 반환됩니다.

예제: 상태에 isLoading 값 추가

const setLoading = R.assoc('isLoading', true);

setLoading({ data: [] }); 
// { data: [], isLoading: true }

🔧 R.evolve – 여러 key를 동시에 변형하기

const user = { name: 'alice', age: 25 };

R.evolve({
  name: R.toUpper,
  age: R.inc
}, user);

// { name: 'ALICE', age: 26 }

예제: 상태 객체 업데이트

const state = { loading: false, count: 0 };

const nextState = R.evolve({
  loading: R.not,
  count: R.inc
})(state);

// { loading: true, count: 1 }

⚠️ Common Pitfalls

1. R.prop, R.path는 undefined를 반환할 수 있음

경로가 없을 경우 에러가 아니라 undefined를 반환하므로, 이를 체크해야 할 때는 R.pathOr 또는 R.defaultTo를 함께 사용하세요.

R.pathOr('unknown', ['user', 'name'], {}); // 'unknown'

2. assoc은 깊은 속성에 직접 접근하지 않음

R.assoc('user.name', 'Bob', {}) // ❌ 의도대로 작동하지 않음

이럴 땐 R.assocPath를 사용해야 합니다:

R.assocPath(['user', 'name'], 'Bob', {}); 
// { user: { name: 'Bob' } }

🧩 조합 예제: pipe와 함께 쓰기

const transformUser = R.pipe(
  R.evolve({
    name: R.toUpper,
    score: R.add(10)
  }),
  R.assoc('active', true)
);

transformUser({ name: 'alice', score: 80 }); 
// { name: 'ALICE', score: 90, active: true }

✅ Call to Action

Ramda의 객체 관련 함수들은 불변성과 안전성을 기반으로 동작합니다.

직접 R.prop, R.path, R.assoc, R.evolve를 활용해 상태 관리, 데이터 가공 코드를 작성해보세요.

실습 아이디어:

  • 주소 객체에서 zipcode를 안전하게 가져오는 함수 만들기

  • 사용자 정보 객체에서 이름은 대문자로, 나이는 1 증가시키기

  • 배열의 객체들에 isActive: true 필드를 추가하기 (R.map + R.assoc)

728x90
728x90

🧰 Ramda 네 번째 스텝: R.map, R.filter, R.reduce로 배열을 함수형 스타일로 다루기

JavaScript에서 데이터를 다룰 때 가장 자주 사용하는 도구는 map, filter, reduce입니다.
Ramda는 이들을 더 강력하고, 조합 가능하며, 재사용성 높은 형태로 제공합니다.


🧠 Topic Summary

Ramda의 R.map, R.filter, R.reduce는 일반 JavaScript 메서드처럼 보이지만 몇 가지 중요한 차이점이 있습니다:

  • 커리화(Currying): 하나의 인자만 넣어도 새로운 함수를 반환합니다.
  • 데이터 마지막 패턴: 함수를 먼저 받고, 나중에 데이터를 전달합니다. (R.map(fn)(data))
  • 구조 추상화: 배열뿐만 아니라 객체, Functor 등에도 작동합니다.

🔄 R.map

R.map(fn, list); // 또는 R.map(fn)(list);

예제: 이름을 대문자로

import * as R from 'ramda';

const names = ['alice', 'bob', 'claire'];

R.map(R.toUpper, names); 
// ['ALICE', 'BOB', 'CLAIRE']

커리화를 활용한 조합

const toUpperAll = R.map(R.toUpper);

toUpperAll(['a', 'b', 'c']); // ['A', 'B', 'C']

🔍 R.filter

R.filter(predicate, list); // 또는 R.filter(predicate)(list)

예제: 길이가 5 이상인 단어 필터링

const longWords = R.filter(word => word.length >= 5);

longWords(['hi', 'hello', 'world', 'R']); 
// ['hello', 'world']

🧮 R.reduce

R.reduce(reducerFn, initialValue, list);

예제: 총합 계산

const sum = R.reduce(R.add, 0);

sum([1, 2, 3, 4]); // 10

예제: 객체 배열을 이름-점수 Map으로 변환

const scores = [
  { name: 'Alice', score: 85 },
  { name: 'Bob', score: 92 },
];

const nameScoreMap = R.reduce(
  (acc, curr) => R.assoc(curr.name, curr.score, acc),
  {},
  scores
);

console.log(nameScoreMap); 
// { Alice: 85, Bob: 92 }

⚠️ Common Pitfalls

1. R.map/R.filter는 함수를 먼저 받아야 한다

JS 배열 메서드에 익숙한 사람은 arr.map(fn)처럼 생각하지만, Ramda는 반대 순서입니다:

// JS: arr.map(fn)
R.map(fn)(arr); // 또는 R.map(fn, arr)

2. 객체에 map/filter를 쓰려면 상황 주의

R.map은 배열뿐 아니라 객체에도 작동하지만, 이때는 key-value에 따라 다르게 작동합니다.

배열과의 동작 차이를 이해하고 사용하세요.


🧩 Advanced Tip: pipe와 조합하기

const processNames = R.pipe(
  R.map(R.trim),
  R.filter(name => name.length > 0),
  R.map(R.toUpper)
);

processNames([' Alice ', '  ', 'Bob']);
// ['ALICE', 'BOB']

함수 조합을 통해 입력 → 정제 → 필터 → 변환까지 깔끔하게 처리할 수 있습니다.


✅ Call to Action

이제 map, filter, reduce를 Ramda 방식으로 다뤄보세요.

JavaScript보다 더 선언적이고, 재사용성 좋은 함수형 코드로 발전할 수 있습니다.

실습 아이디어:

  • 점수 리스트에서 90점 이상만 필터 후 이름만 추출

  • 사용자 객체 배열에서 이름을 소문자로 변환

  • 숫자 배열의 평균 계산하기 (reduce + length)

728x90
728x90

Ramda.js 함수 목록 정리

__

Ramda의 는 커리 함수에서 인수를 부분적으로 지정하지 않을 때 사용하는 플레이스홀더(placeholder) 값입니다. 함수 호출 시 R. 위치에 실제 인자를 나중에 채워 넣을 수 있습니다. 이를 이용하여 함수의 매개변수 순서에 상관없이 부분 적용을 할 수 있습니다.

예를 들어, 삼항(curried ternary) 함수 g에 대해 R.__를 사용하면 다음과 같은 호출이 모두 동일한 결과를 냅니다:

  • g(1, 2, 3)

  • g(R.__, 2, 3)(1)

  • g(R., R., 3)(1)(2)

사용 예시:

const greet = R.replace('{name}', R.__, 'Hello, {name}!');
greet('Alice'); //=> 'Hello, Alice!'

add

R.add는 두 값의 합을 반환하는 함수입니다. 숫자 인자 두 개를 받아 더한 결과를 돌려주며, 커리 함수이므로 인자를 하나만 넣어 부분 적용할 수도 있습니다.

실생활 예시로, 장바구니에서 상품 두 개의 가격을 합산할 때 사용할 수 있습니다.

사용 예시:

R.add(2, 3);       //=> 5
R.add(7)(10);      //=> 17  // 커리 사용 예: 7에 10을 더함

addIndex

R.addIndex는 인덱스와 전체 리스트를 콜백에 추가로 전달해주는 함수 변환기입니다. 주로 R.map이나 R.filter처럼 콜백만 받는 기본 반복 함수에 인덱스 기능을 추가할 때 사용합니다. 반환되는 새로운 함수는 원래 함수와 동일하게 작동하지만, 콜백이 (요소, 인덱스, 전체리스트) 인자를 받을 수 있습니다.

예를 들어, 일반 R.map에는 인덱스를 얻을 방법이 없지만 addIndex(R.map)으로 만든 함수를 사용하면 각 요소의 인덱스를 함께 활용할 수 있습니다.

사용 예시:

const mapIndexed = R.addIndex(R.map);
mapIndexed((val, idx) => idx + '-' + val, ['a', 'b', 'c']);
//=> ['0-a', '1-b', '2-c']

addIndexRight

R.addIndexRight는 addIndex와 동일하게 콜백에 (요소, 인덱스, 리스트) 인자를 전달하는 함수 변환기이지만, 오른쪽에서 왼쪽으로 리스트를 반복합니다. 즉, 인덱스를 오른쪽부터 세어가며 콜백을 실행하는 형태입니다.

이는 R.map 등 좌측에서 우측으로 동작하는 함수에 대해, 반대 방향으로 인덱스를 적용하고 싶을 때 유용합니다.

사용 예시:

const revmap = (fn, arr) => R.map(fn, R.reverse(arr));
const revmapIndexed = R.addIndexRight(revmap);
revmapIndexed((val, idx) => `${idx}-${val}`, ['f', 'o', 'o', 'b', 'a', 'r']);
//=> ['5-f', '4-o', '3-o', '2-b', '1-a', '0-r']

adjust

R.adjust는 배열의 특정 인덱스에 함수를 적용한 결과로 해당 요소를 교체한 새 배열을 반환합니다. 첫 번째 인자로 인덱스 idx, 두 번째 인자로 변경 함수 fn, 세 번째 인자로 대상 배열을 받습니다. 원본 배열은 불변이며, 지정된 위치의 요소만 함수 적용 결과로 대체된 새로운 배열을 돌려줍니다. 인덱스 idx는 음수일 경우 배열의 끝에서부터의 위치로 간주됩니다.

예를 들어, 배열에서 두 번째 요소만 대문자로 변환하거나, 마지막 요소만 변경하는 식으로 활용할 수 있습니다.

사용 예시:

R.adjust(1, R.toUpper, ['a', 'b', 'c', 'd']);   //=> ['a', 'B', 'c', 'd']
R.adjust(-1, x => x * 2, [10, 20, 30]);         //=> [10, 20, 60]  // 마지막 요소 두 배

all

R.all은 리스트의 모든 요소가 주어진 조건을 만족하는지 검사합니다. 첫 번째 인자로 판정 함수(predicate)를 받고, 두 번째 인자로 배열을 받아 동작합니다. 배열의 모든 요소에 대해 판정 함수가 true를 반환하면 결과는 true, 하나라도 만족하지 않으면 false를 반환합니다.

예를 들어, 학생 점수 배열이 있을 때 모든 점수가 합격 점수 이상인지 검사하거나, 리스트의 모든 숫자가 양수인지 확인하는 경우에 사용할 수 있습니다.

사용 예시:

const isPositive = x => x > 0;
R.all(isPositive)([3, 1, 4]);    //=> true   (모든 요소가 양수)
R.all(isPositive)([3, -1, 4]);   //=> false  (하나가 음수라 false)

allPass

R.allPass는 여러 개의 조건 함수들을 모두 만족하는지를 판단하는 새로운 함수를 만들어줍니다. 인자로 함수들의 배열을 받고, 이 함수들은 모두 boolean을 반환하는 판정 함수들이어야 합니다. 결과로 반환된 함수는 주어진 인자를 각 판정 함수에 적용하여 모두 true를 반환할 때만 true를 결과로 내며, 하나라도 false이면 false를 반환합니다.

쉽게 말해, “모든 조건을 동시에 만족하는지” 체크하는 함수입니다. 예를 들어 어떤 객체가 특정 필드를 가지고 있고 그 값이 특정 조건을 만족하는지 등을 한 번에 검사할 수 있습니다.

사용 예시:

const isClub = R.propEq('♣', 'suit');    // 카드 모양이 클럽인지
const isQueen = R.propEq('Q', 'rank');   // 카드 랭크가 Q(퀸)인지
const isQueenOfClubs = R.allPass([isClub, isQueen]);

isQueenOfClubs({rank: 'Q', suit: '♣'}); //=> true
isQueenOfClubs({rank: 'Q', suit: '♥'}); //=> false

always

R.always는 항상 일정한 값만 반환하는 함수를 생성합니다. 인자로 주어진 값 val을 무시하는 모든 입력에 대해 그대로 반환하는 새 함수를 돌려줍니다. 주어진 val이 객체나 배열 등 참조형이면 원본 참조를 반환하므로 주의해야 합니다.

이 함수는 다른 언어에서 “const”나 “constant 함수”로 불리기도 하며, 디폴트 값을 가진 함수테스트용 더미 함수가 필요할 때 유용합니다.

사용 예시:

const returnTee = R.always('Tee');
returnTee();       //=> 'Tee'
returnTee('any');  //=> 'Tee'  (인자와 상관없이 'Tee')

and

R.and는 두 값을 받아 논리 AND 연산을 수행합니다. 첫 번째 인자가 거짓 같은 값(falsy)이면 그 값을 바로 반환하고, 그렇지 않으면 두 번째 인자를 반환합니다. 특히 두 값이 모두 boolean일 경우 논리곱 결과와 동일하게 동작합니다.

이 함수는 JavaScript의 && 연산자와 유사하지만, 커리 함수로 제공되어 필요에 따라 부분 적용으로 사용할 수 있습니다.

사용 예시:

R.and(true, true);    //=> true
R.and(true, false);   //=> false
R.and(0, 'Hello');    //=> 0    (첫 인자가 falsy라 그대로 반환)
R.and(1, 'Hello');    //=> 'Hello'  (첫 인자가 truthy라 두 번째 반환)

andThen

R.andThen은 Promise 체인의 성공 결과를 처리하는 함수입니다. 첫 번째 인자로 성공 시 호출할 함수 onSuccess를 받고, 두 번째 인자로 Promise를 받아 동작합니다. 결과적으로, 주어진 Promise가 resolve되면 해당 값을 onSuccess에 적용한 새로운 Promise를 반환합니다. (이 함수는 p.then(onSuccess)와 같다고 생각할 수 있습니다.)

주로 비동기 작업의 성공 처리를 함수형 합성 내에서 연결할 때 사용합니다. andThen은 R.then으로도 알려져 있으며, R.otherwise와 함께 Promise 처리를 함수 합성에 통합할 수 있게 해줍니다.

사용 예시:

const fetchData = id => Promise.resolve({ id, name: 'User' }); // 가정: 성공 Promise
const getName = R.pipe(
  fetchData,
  R.andThen(R.prop('name'))  // fetchData 성공 결과에서 name 속성 추출
);
getName(3).then(console.log);  // 'User' 출력

any

R.any는 리스트의 요소 중 하나라도 조건을 만족하면 true를 반환하는 함수입니다. R.all과 반대로, 판정 함수가 한 번이라도 true를 반환하면 결과가 true입니다. 배열에 조건을 만족하는 요소가 최소 하나 있는지 확인하는 용도로 사용합니다.

예를 들어, 시험 점수 목록에서 낙제 점수가 한 명이라도 있는지 검사하거나, 문자열 리스트 중 특정 패턴을 포함한 항목이 존재하는지 확인할 때 활용할 수 있습니다.

사용 예시:

const isNegative = x => x < 0;
R.any(isNegative, [1, 2, 3]);    //=> false  (음수가 하나도 없음)
R.any(isNegative, [1, -1, 2]);   //=> true   (하나의 요소가 음수임)

anyPass

R.anyPass는 여러 개의 판정 함수 중 하나라도 만족하면 true를 반환하는 새로운 함수를 만들어줍니다. allPass의 반대 개념으로, 전달된 판정 함수 배열 중 한 개 이상이 입력값에 대해 참을 반환하면 결과가 참이 됩니다. 모두 거짓일 때만 거짓을 반환합니다.

이 함수는 “여러 조건 중 하나라도 충족하는지” 체크하는 데 유용합니다. 예를 들어, 어떤 숫자가 3의 배수이거나 5의 배수이거나 둘 중 하나인지 검사하는 함수를 만들 수 있습니다.

사용 예시:

const isEven = x => x % 2 === 0;
const isMultipleOf3 = x => x % 3 === 0;
const isEvenOrMultipleOf3 = R.anyPass([isEven, isMultipleOf3]);

isEvenOrMultipleOf3(4); //=> true  (짝수이므로)
isEvenOrMultipleOf3(9); //=> true  (3의 배수이므로)
isEvenOrMultipleOf3(7); //=> false (짝수도 3배수도 아님)

ap

R.ap는 함수들의 리스트와 값들의 리스트를 받아, 모든 함수들을 모든 값에 적용해 나온 결과 리스트를 반환합니다. 쉽게 말해, [f, g]와 [x, y]를 주면 결과는 [f(x), f(y), g(x), g(y)] 형태로 나옵니다. 이 동작은 수학의 어플리케이티브 펑터(applicative functor)의 개념과 관련이 있습니다.

예를 들어 여러 개의 변환 함수를 한꺼번에 여러 값에 적용하거나, 두 개의 리스트를 조합하여 모든 가능한 결과를 얻고자 할 때 사용할 수 있습니다.

사용 예시:

R.ap([R.multiply(2), R.add(3)], [1, 2, 3]);
//=> [2, 4, 6, 4, 5, 6]
// (설명: 2배 함수와 +3 함수를 각각 1,2,3에 적용한 결과들)

R.ap([R.concat('안녕, '), R.toUpper], ['람다', 'world']);
//=> ["안녕, 람다", "안녕, world", "람다", "WORLD"]

aperture

R.aperture는 리스트에서 연속된 n개의 요소씩 묶은 부분 리스트들의 리스트를 반환합니다. 첫 번째 인자로 정수 n을 받고, 두 번째 인자로 리스트를 받아 작동합니다. 반환되는 리스트는 원래 리스트에서 n개씩 연속한 창(window)을 움직이며 추출한 부분배열들의 모음입니다.

만약 n이 원본 리스트 길이보다 크면 빈 리스트를 반환합니다.

예를 들어, 시간 시계열 데이터에서 이동 평균을 계산하기 위해 5개씩 묶은 부분 구간을 구하는 등의 상황에서 활용할 수 있습니다.

사용 예시:

R.aperture(2, [1, 2, 3, 4, 5]);
//=> [[1, 2], [2, 3], [3, 4], [4, 5]]

R.aperture(3, [1, 2, 3, 4, 5]);
//=> [[1, 2, 3], [2, 3, 4], [3, 4, 5]]

R.aperture(7, [1, 2, 3]);
//=> []  // n이 리스트 길이보다 커서 빈 배열

append

R.append는 주어진 요소를 리스트의 맨 끝에 추가한 새로운 리스트를 반환합니다. 첫 번째 인자로 추가할 요소 el, 두 번째 인자로 리스트를 받아 동작합니다. 원본 리스트를 변경하지 않고, 마지막에 요소가 하나 더 붙은 복사본을 돌려줍니다.

예를 들어, 대기열에 새로운 사람을 맨 뒤에 추가하거나, 기존 배열에 새로운 값을 덧붙일 때 사용할 수 있습니다.

사용 예시:

R.append('new', ['a', 'b', 'c']); //=> ['a', 'b', 'c', 'new']
R.append(5, []);                 //=> [5]   (빈 배열에 5 추가)

apply

R.apply는 배열을 함수의 인자 목록으로 적용하는 함수입니다. 첫 번째 인자로 함수 fn을 받고, 두 번째 인자로 인자들의 배열 args를 받습니다. apply는 fn을 호출하면서 args 배열의 요소를 차례로 펼쳐 인자로 넘겨줍니다. 이 함수는 가변 인자 함수(variadic function)에 배열을 인자로 한 번에 전달하고 싶을 때 유용합니다.

예를 들어, Math.max처럼 임의 개수의 숫자를 받는 함수에 배열로 준비된 숫자 목록을 넘기고 싶을 때 사용합니다.

사용 예시:

const nums = [1, 2, 3, -99, 42];
R.apply(Math.max, nums); //=> 42   (배열 요소를 Math.max에 펼쳐서 전달)

const add3 = (a, b, c) => a + b + c;
R.apply(add3, [10, 20, 5]); //=> 35

applySpec

R.applySpec는 객체 또는 구조를 정의하고, 각 속성 값은 주어진 인수들을 이용해 계산되는 함수들로 채워진 객체를 반환하는 함수를 만들어줍니다. 쉽게 말해, 결과 객체의 스펙(spec)을 미리 정해두면, 해당 스펙에 따라 입력 인수들을 처리하여 결과 객체를 생성하는 함수를 생성합니다.

스펙 객체의 각 키는 생성될 결과 객체의 키가 되며, 그 값으로 지정된 함수가 입력 인수들을 받아 해당 키의 값을 산출합니다. 중첩된 객체 구조도 재귀적으로 지원됩니다.

실생활 활용 예로, 여러 개의 계산을 하나의 객체로 묶어 반환하고 싶을 때 사용합니다. 예를 들어, 동일한 입력에 대해 합계와 곱 등을 동시에 계산하여 { sum: ..., mul: ... } 형태로 돌려줄 수 있습니다.

사용 예시:

const getMetrics = R.applySpec({
  sum: R.add,                 // 합계 계산
  mul: R.multiply,            // 곱 계산
  nested: { 
    max: Math.max, 
    min: Math.min 
  }
});
getMetrics(2, 4); 
// => { sum: 6, mul: 8, nested: { max: 4, min: 2 } }

applyTo

R.applyTo는 값을 받아 함수에 적용하는 함수입니다. 순서로 보면, 일반적인 함수 호출 (fn)(value)를 뒤집어 value를 먼저 주고 그 다음 함수에 적용합니다. 첫 번째 인자로 어떤 값 x를 받고, 두 번째 인자로 함수 f를 받아서 f(x)를 반환합니다.

이 함수는 주어진 값을 다양한 함수에 넘겨서 결과를 보고 싶을 때, 혹은 파이프라인에서 값을 시작으로 하는 경우 등에 쓰입니다.

예를 한번 들어보면, R.applyTo(42, R.inc)는 42에 R.inc (1 증가 함수)을 적용해서 43을 돌려줍니다.

사용 예시:

const t42 = R.applyTo(42);
t42(R.identity); //=> 42   (42를 그대로 반환하는 함수에 적용)
t42(R.add(1));   //=> 43   (42에 1을 더하는 함수에 적용)

ascend

R.ascend는 정렬에 사용하기 위한 비교 함수(comparator)를 생성합니다. 첫 번째 인자로 키 추출 함수 fn을 받고, 반환된 비교 함수는 두 값 a와 b를 받아 fn(a)와 fn(b)를 비교하여 오름차순으로 정렬되도록 숫자를 반환합니다 (-1, 0, 1 중 하나).

정렬 시 R.sort나 Array.prototype.sort와 함께 사용하며, 객체 리스트를 특정 속성 기준으로 오름차순 정렬하는 등 상황에 활용됩니다.

사용 예시:

const byAgeAsc = R.ascend(R.prop('age'));
const people = [
  { name: '영희', age: 25 },
  { name: '철수', age: 30 },
  { name: '민수', age: 20 }
];
R.sort(byAgeAsc, people);
//=> [ { name: '민수', age: 20 }, { name: '영희', age: 25 }, { name: '철수', age: 30 } ]

ascendNatural

R.ascendNatural은 ascend와 유사하지만, 문자열을 자연 정렬(natural sort) 방식으로 비교할 수 있는 비교 함수를 생성합니다. 특히 숫자 문자열이 문자열 순서가 아닌 숫자 크기 순서로 정렬되도록 도와줍니다. 첫 번째 인자로 로케일 문자열(예: ‘en’ 또는 [‘en’, ‘ko’] 등) 또는 로케일 배열을 받고, 두 번째 인자로 키 추출 함수 fn을 받습니다.

이 함수가 생성한 비교 함수는 Intl.Collator의 localeCompare를 활용하여 자연스러운 정렬 순서를 결정합니다. 사람 이름, 파일명 등 “1, 2, 10”을 “1, 2, 10” 순으로 정렬하고 싶을 때 활용할 수 있습니다.

사용 예시:

const unsorted = ['3', '1', '10', '아', '가', '다'];
R.sort(R.ascendNatural('en', R.identity), unsorted);
//=> ['1', '3', '10', '가', '다', '아']  ('en' 로케일 기준 자연 정렬)

assoc

R.assoc는 객체에 새로운 속성을 설정한 복사본을 반환합니다. 첫 번째 인자로 속성 이름 prop, 두 번째 인자로 속성값 val, 세 번째 인자로 대상 객체 obj를 받습니다. 결과는 원본 객체를 얕게 복사(shallow clone)한 후 지정한 속성 prop의 값을 val로 넣은 객체입니다. 기존 객체를 변경하지 않고, 추가 또는 덮어쓴 새로운 객체를 얻습니다.

예를 들어, 사용자 객체에 age 속성이 없으면 추가하고 싶거나, 값을 바꾸고 싶을 때 사용할 수 있습니다.

사용 예시:

R.assoc('age', 30, { name: '홍길동', city: '서울' });
//=> { name: '홍길동', city: '서울', age: 30 }

R.assoc('city', '부산', { name: '홍길동', city: '서울' });
//=> { name: '홍길동', city: '부산' }  // 기존 city 속성 덮어쓰기

assocPath

R.assocPath는 중첩된 객체 구조 내의 깊은 경로를 따라 값을 설정한 새 객체를 생성합니다. 첫 번째 인자로 경로 배열 path (예: ['a', 0, 'b']), 두 번째 인자로 설정할 값 val, 세 번째 인자로 대상 객체를 받습니다. 지정한 경로를 따라 (필요시 중첩 객체를 생성해가며) val을 할당한 객체의 얕은 복사본을 반환합니다.

만약 경로 중 존재하지 않는 키가 있다면 새로 객체를 만들어 경로를 완성하며, 경로 끝에 도달하면 값을 설정합니다. 원본 객체는 변경되지 않습니다.

사용 예시:

R.assocPath(['user', 'profile', 'nickname'], 'RAMDA', { user: { profile: {} } });
//=> { user: { profile: { nickname: 'RAMDA' } } }

R.assocPath(['a', 'b', 'c'], 42, { a: 5 });
//=> { a: { b: { c: 42 } } }  // a가 객체가 아니어도 덮어써서 경로 생성

binary

R.binary는 인자로 받은 함수를 강제로 2개의 인자만 받는 함수로 변환합니다. 원본 함수 fn의 아리티(매개변수 수)가 무엇이든 관계없이, 반환되는 새 함수는 오직 두 개의 인자만 전달받아 그 인자들만 원본 함수에 넘깁니다. 초과되는 인자는 무시됩니다.

이 함수는 특히 Array.prototype.map 등 2개 이상의 인자를 콜백에 넘기는 경우(값, 인덱스, 배열)을 대비해, 원래 2개까지만 필요한 함수로 감싸줄 때 유용합니다.

사용 예시:

const takesThree = (a, b, c) => [a, b, c];
takesThree(1, 2, 3);             //=> [1, 2, 3]

const takesTwo = R.binary(takesThree);
takesTwo(1, 2, 3);               //=> [1, 2, undefined]
// 3번째 인자는 무시되어 undefined

bind

R.bind는 함수의 this 컨텍스트를 지정하여 묶은 새로운 함수를 반환합니다. 첫 번째 인자로 함수 fn과 두 번째 인자로 객체 thisObj를 받아서, fn을 호출할 때 항상 thisObj를 this로 사용하는 함수로 만들어줍니다.

JavaScript의 Function.prototype.bind와 유사하지만, 추가적인 인자 바인딩 기능은 없습니다. Ramda의 bind는 맥락(context)만 고정하고 함수 자체는 커리드 형태로 사용할 수 있습니다.

예를 들어, 콘솔의 log 메소드를 컨텍스트에 묶어 간단히 사용할 때 활용합니다.

사용 예시:

const logToConsole = R.bind(console.log, console);
logToConsole('Hello', 'Ramda'); //=> Hello Ramda  (console에 출력됨)

const slice = R.bind(Array.prototype.slice, ['a', 'b', 'c']);
slice(1);  //=> ['b', 'c']  (배열에 바인딩하여 메서드처럼 사용)

both

R.both는 두 개의 조건 함수를 AND(논리곱)로 결합한 새로운 함수를 만들어줍니다. 반환된 함수는 인자를 받아, 두 조건 함수 f와 g에 그 인자를 모두 적용하고 결과를 논리 AND(&&) 연산하여 true/false를 돌려줍니다. 첫 번째 조건이 거짓인 경우 두 번째는 실행하지 않는 단락 평가(short-circuit)가 적용됩니다.

이 함수는 “동시에 두 조건을 만족해야 하는지” 체크할 때 사용합니다. 예를 들어, 숫자가 0보다 크고 짝수인지 확인하거나, 객체가 특정 필드를 가지고 그 값이 조건을 만족하는지 등을 검사하는 함수로 만들 수 있습니다.

사용 예시:

const isGreaterThan10 = R.gt(R.__, 10);  // 10보다 큰지
const isEven = x => x % 2 === 0;        // 짝수인지
const isEvenAndGT10 = R.both(isGreaterThan10, isEven);

isEvenAndGT10(15); //=> true  (15는 10보다 크고 짝수)
isEvenAndGT10(8);  //=> false (8은 짝수지만 10보다 크지 않음)

call

R.call은 주어진 함수와 나머지 인자들을 즉시 적용하여 호출한 결과를 반환합니다. 첫 번째 인자로 함수 fn을 받고, 이어서 임의 개수의 인자 ...args를 받을 수 있습니다. R.call(fn, ...args)는 결국 fn(...args)와 동일한 결과를 돌려줍니다.

이 함수는 특히 R.converge 같은 함수에서 첫 번째 브랜치 결과로 함수가 생성될 때 그 함수를 다른 브랜치 결과 값들에 적용하는데 사용될 수 있습니다. 또는 함수를 즉시 호출하면서 합성 흐름 중에 사용해야 할 때 쓰입니다.

사용 예시:

R.call(Math.max, 5, 3, 9); 
//=> 9   (Math.max에 5, 3, 9를 인자로 호출한 결과)

const indentN = R.pipe(R.repeat(' '), R.join(''));
const format = R.converge(R.call, [R.pipe(R.prop('indent'), indentN), R.prop('value')]);
// format은 객체를 받아 indentN으로 만든 함수(indentN 결과 자체가 함수임)를 값(value)에 호출
format({ indent: 2, value: 'Hi\nRamda' });
//=> '  Hi\n  Ramda'  (indent:2 만큼 들여쓰기 적용된 문자열 반환)

chain

R.chain은 리스트를 flatMap 하는 함수입니다. 첫 번째 인자로 리스트 요소를 변환하여 모나드/체인 구조를 리턴하는 함수 fn (예: 배열을 반환하는 함수)을 받고, 두 번째 인자로 리스트를 받습니다. chain은 주어진 리스트의 각 요소에 fn을 적용한 결과들을 모두 평탄화(flatten)하여 하나의 리스트로 합쳐 반환합니다.

이 함수는 다른 라이브러리의 flatMap과 동일하며, 배열뿐만 아니라 판타지랜드 Chain 인터페이스를 구현한 객체에도 동작합니다. (예: Maybe 모나드 등)

실생활 예로, 리스트 안의 리스트를 한 단계 평탄화하거나, 데이터에 따라 0개 또는 여러 개의 결과를 내는 함수를 적용해 전체 결과를 모을 때 사용할 수 있습니다.

사용 예시:

const duplicate = n => [n, n];
R.chain(duplicate, [1, 2, 3]);
//=> [1, 1, 2, 2, 3, 3]  (각 숫자를 중복하여 배열로 반환 후 평탄화)

R.chain(R.identity, [[1, 2], [3], [4, 5]]);
//=> [1, 2, 3, 4, 5]  (중첩 배열을 한 단계 풀어냄)

clamp

R.clamp는 숫자 값을 주어진 최소값과 최대값 사이로 제한하는 함수입니다. 세 개의 인자를 받는데, 첫 번째는 하한값 min, 두 번째는 상한값 max, 세 번째는 입력 값 value입니다. 결과는 value가 min보다 작으면 min을, max보다 크면 max를, 그 사이에 있으면 value 자체를 반환합니다.

이 함수는 값을 특정 범위로 조정할 때 유용합니다. 예를 들어 볼륨 조절에서 0~100 사이로 값 제한, 점수 최소/최대 범위 보정 등에 쓸 수 있습니다.

사용 예시:

R.clamp(1, 10, -5); //=> 1   (-5가 하한 1보다 작으므로 1로 조정)
R.clamp(1, 10, 15); //=> 10  (15가 상한 10보다 크므로 10으로 조정)
R.clamp(1, 10, 4);  //=> 4   (정상 범위이므로 그대로 반환)

clone

R.clone은 값을 깊은 복사(deep copy)하여 반환합니다. 주어진 값이 객체나 배열 등일 경우 내부의 모든 중첩 구조를 재귀적으로 복사하여 동일한 내용의 독립된 객체를 생성합니다. 원시 값(Number, String 등)은 그냥 반환되고, 함수는 참조로 복사됩니다 (즉, 함수는 같은 함수를 가리킴).

이 함수는 객체의 불변성을 유지하면서 복사본을 만들어 수정하고자 할 때 유용합니다. 단, 원본 객체 내에 순환 참조(circular reference)가 있을 경우 동일한 구조를 유지하되 새로운 참조로 복사됩니다.

사용 예시:

const original = { x: 1, y: { z: 2 } };
const copy = R.clone(original);
copy.y.z = 99;
console.log(original.y.z); //=> 2  (원본은 영향 받지 않음)

const arr = [{}, {}];
const arrClone = R.clone(arr);
arr === arrClone;           //=> false  (다른 배열)
arr[0] === arrClone[0];     //=> false  (내부 객체도 복사됨)

collectBy

R.collectBy는 리스트의 요소들을 특정 키 함수에 따라 그룹화하여 중첩 배열로 반환합니다. 첫 번째 인자로 키 생성 함수 keyFn을 받고, 두 번째 인자로 리스트를 받습니다. 동작은 R.groupBy와 유사하지만 결과가 객체가 아닌 배열의 배열 형태입니다. 동일한 키로 분류된 요소들은 하나의 서브 배열을 이룹니다.

이 함수는 요소의 그룹핑 순서를 보존하면서 모을 때 사용할 수 있습니다. 예를 들어, 상품 목록을 카테고리별로 모으되 순서를 유지하고 싶을 때 유용합니다.

사용 예시:

const items = [
  {type: 'fruit', name: 'apple'},
  {type: 'vegetable', name: 'carrot'},
  {type: 'fruit', name: 'banana'},
  {type: 'vegetable', name: 'lettuce'}
];
R.collectBy(R.prop('type'), items);
//=> [
//      [ {type: 'fruit', name: 'apple'}, {type: 'fruit', name: 'banana'} ],
//      [ {type: 'vegetable', name: 'carrot'}, {type: 'vegetable', name: 'lettuce'} ]
//   ]

comparator

R.comparator는 정렬용 비교 함수를 쉽게 만들도록 도와주는 함수입니다. 불리언을 반환하는 2항 조건 함수 pred를 인자로 받아, 그 결과를 숫자 비교 함수(-1, 0, 1 반환)로 변환해줍니다. 만들어진 함수는 a, b를 인자로 받아 pred(a, b)가 참이면 -1 (a < b), 거짓이면 1 (a > b), 동등하면 0을 반환합니다.

이를 통해 간단히 “a가 b보다 작다” 형태의 조건만 정의하면 정렬에 필요한 comparator를 얻을 수 있습니다.

사용 예시:

const byAgeAscending = R.comparator((a, b) => a.age < b.age);
const people = [
  { name: 'Kim', age: 30 },
  { name: 'Lee', age: 25 },
  { name: 'Park', age: 35 }
];
R.sort(byAgeAscending, people);
//=> [ {name: 'Lee', age: 25}, {name: 'Kim', age: 30}, {name: 'Park', age: 35} ]

complement

R.complement는 특정 조건 함수의 논리 부정 버전을 반환합니다. 인자로 함수 f를 받아, 결과 함수는 같은 인자를 받아 f가 반환한 값을 불리언으로 취급해 반대(true->false, false->true)를 돌려줍니다.

쉽게 말해, f가 “조건을 만족하는지” 판정하는 함수라면, R.complement(f)는 “조건을 만족하지 않는지” 판정하는 함수가 됩니다.

사용 예시:

const isNil = R.isNil;                // null 또는 undefined 체크
const isNotNil = R.complement(isNil);

isNil(null);    //=> true
isNotNil(null); //=> false  (null의 complement -> false)
isNil(5);       //=> false
isNotNil(5);    //=> true   (5는 null/undefined 아님)

compose

R.compose는 여러 함수를 우측에서 좌측으로(오른쪽이 먼저) 함수 합성을 수행합니다. 인자로 나열된 함수들을 오른쪽 끝 함수부터 실행하여 결과를 왼쪽으로 전달하면서 합성한 새 함수를 반환합니다. 수학 기호 ∘와 유사하며, R.pipe와는 반대 방향입니다.

compose로 만들어진 함수는 가장 오른쪽 함수의 인자 리스트를 그대로 받으며, 이후 오른쪽 함수의 출력이 그 왼쪽 함수의 입력으로 들어가는 방식으로 동작합니다. 일반적으로, 마지막(제일 왼쪽) 함수를 제외한 다른 함수들은 단항 함수(인자 하나)여야 합니다.

사용 예시:

const greet = (first, last) => `Name: ${last}, ${first} ${last}`;
const shout = R.toUpper;
const excitedGreet = R.compose(shout, greet);

excitedGreet('Jane', 'Doe'); //=> 'NAME: DOE, JANE DOE'

composeWith

R.composeWith는 함수 합성 과정에서 각 단계 사이에 변형 함수를 삽입할 수 있는 합성 함수입니다. 첫 번째 인자로 변형 함수 transformer를 받고, 두 번째 인자로 합성할 함수들의 배열을 받습니다. composeWith는 이 배열의 오른쪽부터 왼쪽으로 함성을 수행하되, 함수 연결 시마다 transformer를 통해 결과를 가공하거나 연속 여부를 결정할 수 있습니다.

예를 들어, 중간 결과가 null/undefined이면 합성을 중단하는 등의 커스텀 로직을 구현할 때 유용합니다. 기본 compose와 달리 함수 리스트를 배열로 받으며, 결과는 일반 합성과 마찬가지로 동작합니다.

사용 예시:

// 결과가 null/undefined이면 합성 중단하는 transformer
const composeWhileNotNil = R.composeWith((fn, result) => R.isNil(result) ? result : fn(result));

const safeHeadUpper = composeWhileNotNil([R.head, R.toUpper]);
safeHeadUpper(['hello', 'world']); //=> 'HELLO'
safeHeadUpper([]); //=> undefined  (중간에 head 결과가 undefined이므로 toUpper는 실행 안 함)

concat

R.concat은 두 시퀀스 자료를 이어붙이는 함수입니다. 두 개의 배열을 붙이거나, 문자열을 이어붙이거나, 혹은 판타지랜드 Semigroup을 구현한 다른 자료형에 대해서도 동작합니다. 인자로 첫 번째 시퀀스 first와 두 번째 시퀀스 second를 받아, 타입이 동일한 시퀀스를 반환합니다.

JavaScript 내장 Array.prototype.concat와 유사하나, Ramda 함수로 커리되어 있어 활용도가 높습니다. 주의: 두 인자의 타입이 달라도 오류 없이 실행될 수 있지만, 논리적으로 같은 타입이어야 의미가 있습니다.

사용 예시:

R.concat([1, 2, 3], [4, 5]);    //=> [1, 2, 3, 4, 5]
R.concat('Hello, ', 'Ramda!'); //=> 'Hello, Ramda!'

const arr = [1];
R.concat(arr, { length: 2 });   // 런타임 오류 가능 (유사 배열도 concat 대상이지만 결과 예측 불가)

cond

R.cond는 여러 조건/결과 쌍을 이용한 다중 분기 함수를 생성합니다. 인자로 [predicate, transformer] 쌍들의 배열을 받습니다. 생성된 함수는 입력을 받아 각 predicate 함수들을 순서대로 적용하면서 true를 반환하는 첫 번째 케이스를 찾습니다. 그에 대응하는 transformer 함수를 실행하여 반환하며, 모든 조건이 거짓이면 undefined를 반환합니다.

이는 전통적인 if/else if/else 또는 switch 문을 대체하는 기능형 패턴입니다. 조건부 로직을 깔끔하게 작성할 수 있게 해줍니다.

사용 예시:

const waterState = R.cond([
  [R.equals(0),   R.always('물은 0°C에서 얼음이 됩니다.')],
  [R.equals(100), R.always('물은 100°C에서 끓습니다.')],
  [R.T,           temp => `물 온도 ${temp}°C: 특별한 변화 없음.`]
]);

waterState(0);    //=> '물은 0°C에서 얼음이 됩니다.'
waterState(50);   //=> '물 온도 50°C: 특별한 변화 없음.'
waterState(100);  //=> '물은 100°C에서 끓습니다.'

construct

R.construct는 생성자 함수를 래핑하여 새로운 키워드 없이 호출할 수 있는 함수를 만들어줍니다. 일반적인 생성자 (예: new MyClass(x, y))는 new 키워드로 호출해야 하지만, R.construct(MyClass)를 쓰면 이를 함수처럼 호출하여 동일한 객체를 생성할 수 있습니다.

반환된 함수는 원래 생성자의 인수를 동일하게 받으며, 커리 함수로 동작합니다. (단, 추가적인 인자 바인딩은 불가능합니다. 오직 컨텍스트 없이 new를 호출하는 효과만 있습니다.)

사용 예시:

function Person(name, age) {
  this.name = name;
  this.age = age;
}
const PersonConstructor = R.construct(Person);
const jane = PersonConstructor('Jane', 30);
// new Person('Jane', 30) 와 동일한 객체 생성

constructN

R.constructN은 construct와 유사하지만, 생성자의 아리티(매개변수 수)를 명시적으로 지정할 수 있습니다. 첫 번째 인자로 숫자 n (생성자 함수의 기대 인자 수), 두 번째 인자로 생성자 함수 Fn을 받고, n개의 인자를 취해 new Fn(...args)와 동일한 객체를 생성하는 커리 함수를 반환합니다.

이는 가변 인자 생성자나 기본 매개변수가 있는 생성자를 다룰 때, 커리 함수의 동작을 정확히 지정하기 위해 사용합니다. 예를 들어 생성자가 3개의 인자를 받도록 강제하고 싶은 경우 constructN(3, Constructor)를 사용합니다.

사용 예시:

function Sandwich(...ingredients) {
  this.layers = ingredients;
}
const TripleDeck = R.constructN(3, Sandwich);
const bltSandwich = TripleDeck('Bacon')('Lettuce')('Tomato');
// new Sandwich('Bacon', 'Lettuce', 'Tomato')와 동일
bltSandwich.layers; //=> ['Bacon', 'Lettuce', 'Tomato']

converge

R.converge는 여러 함수의 결과를 조합해 하나의 결과를 만드는 함수를 생성합니다. 첫 번째 인자로 수렴 함수 after를 받고, 두 번째 인자로 분기 함수 배열 [fn1, fn2, ...]를 받습니다. 반환된 함수는 어떤 입력을 받아, 그 입력을 모든 분기 함수에 각각 전달합니다. 각 분기 함수들의 결과를 모아 after 함수의 인자로 넘겨 실행한 결과를 최종 반환합니다.

이를 통해 하나의 값을 가지고 여러 가지 처리를 병렬로 수행한 뒤, 그 결과들을 다시 합쳐 최종 계산을 할 수 있습니다. 예를 들어, 숫자 리스트를 가지고 합계와 길이를 동시에 구한 뒤 두 값을 이용해 평균을 계산하는 함수를 만들 수 있습니다.

사용 예시:

const average = R.converge(R.divide, [R.sum, R.length]);
average([1, 2, 3, 4, 5]); //=> 3  (sum=15, length=5, divide(15,5)=3)

const strangeConcat = R.converge(R.concat, [R.toUpper, R.toLower]);
strangeConcat('Yodel'); //=> 'YODELyodel'

count

R.count는 리스트에서 주어진 조건을 만족하는 요소의 개수를 세어 반환합니다. 첫 번째 인자로 판정 함수 predicate, 두 번째 인자로 리스트를 받아 동작합니다. 결과는 리스트 중 predicate가 true인 요소들의 총 개수 (Number)입니다.

예를 들어, 짝수의 개수, 특정 단어를 포함하는 문자열의 개수 등을 셀 때 사용할 수 있습니다.

사용 예시:

const even = x => x % 2 === 0;
R.count(even, [1, 2, 3, 4, 5]); //=> 2   (짝수인 2와 4 두 개)
R.map(R.count(even), [[1, 1, 1], [2, 3, 4, 5], [6]]);
//=> [0, 2, 1]  // 각 배열에서 짝수 개수: 첫 배열 0개, 둘째 2개, 셋째 1개

countBy

R.countBy는 리스트의 요소들을 특정 키를 기준으로 그룹화하여 각 그룹의 개수를 센 객체를 반환합니다. 첫 번째 인자로 키 생성 함수 fn을 받고, 두 번째 인자로 리스트를 받습니다. 결과는 객체 형태로 fn으로부터 나온 키를 속성으로 가지며, 그 값은 해당 키에 해당하는 요소의 수입니다.

예를 들어, 단어 리스트에서 첫 글자를 기준으로 몇 개씩 있는지 세거나, 사람 객체 리스트에서 도시별 인원수를 집계하는 데 사용할 수 있습니다.

사용 예시:

const floor = Math.floor;
R.countBy(floor, [1.0, 1.1, 1.2, 2.0, 3.0, 2.2]);
//=> { '1': 3, '2': 2, '3': 1 }  (버림값 1은 3개, 2는 2개, 3은 1개)

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

curry

R.curry는 다인수 함수를 커리 함수로 변환합니다. 입력으로 다중 인자를 받는 함수 fn을 받아, 같은 기능을 하지만 커리된(curried) 새로운 함수를 반환합니다. 커리된 함수는 인자를 하나씩 (또는 몇 개씩) 받을 때마다 부분 적용되다가, 필요한 모든 인자가 채워지면 원래 함수 fn을 실행합니다.

R.curry로 만든 함수는 인자를 한 번에 모두 주지 않아도 되므로, 재사용 가능한 부분 함수를 만들기 쉽습니다. (주의: 기본값이 있는 매개변수는 함수의 선언상 arity에 포함되지 않으므로 curry 동작과 다를 수 있습니다.)

사용 예시:

const addFour = (a, b, c, d) => a + b + c + d;
const curriedAddFour = R.curry(addFour);

// 여러 방식으로 호출 가능
curriedAddFour(1)(2)(3)(4);  //=> 10
curriedAddFour(1, 2)(3, 4);  //=> 10
curriedAddFour(1, 2, 3)(4);  //=> 10

// Placeholder R.__ 사용 예:
const _ = R.__;
curriedAddFour(_, 2, 3, 4)(1); //=> 10

curryN

R.curryN은 curry와 유사하지만, 커리 함수로 만들 때 함수의 아리티(필요 인자 수)를 명시적으로 지정할 수 있습니다. 첫 번째 인자로 숫자 n (원하는 아리티), 두 번째 인자로 함수 fn을 받아 fn을 커리화합니다. 이때 fn의 실제 인자 개수와 상관없이 n개 인자를 받을 때 완전히 실행되도록 동작합니다.

주로 기본 인자를 가진 함수나 인자 개수를 명확히 제한하고 싶을 때 사용합니다. R.curry로 기본동작을 수행하되, n 인자에 도달하면 실행됩니다.

사용 예시:

const sumArgs = (...args) => R.sum(args);
const curriedSum4 = R.curryN(4, sumArgs);

curriedSum4(1, 2)(3)(4); //=> 10   (합계 10, 4개 인자까지 받아 실행됨)
curriedSum4(1)(2, 3, 4); //=> 10

dec

R.dec는 숫자를 1 감소시키는 함수입니다. 인자로 숫자 n을 받아 n - 1을 반환합니다. 이는 R.add(-1, n)과 같으며, R.inc (1 증가)의 반대 동작입니다.

사용 예시:

R.dec(42); //=> 41
R.map(R.dec, [0, 1, 2]); //=> [-1, 0, 1]

defaultTo

R.defaultTo는 null, undefined 또는 NaN인 경우 대신 반환할 기본값을 지정합니다. 첫 번째 인자로 기본값 default, 두 번째 인자로 입력값 val을 받아 동작합니다. 만약 val이 null 또는 undefined 또는 NaN이면 default를, 그렇지 않으면 val 자체를 반환합니다.

이 함수는 안전한 기본값 처리에 유용합니다. 예를 들어, 함수의 결과가 유효한 값이 아닐 때 특정 기본값으로 대체하거나, 객체 속성이 없을 때 기본값을 쓰는 경우 활용할 수 있습니다.

사용 예시:

const defaultTo42 = R.defaultTo(42);

defaultTo42(null);       //=> 42  (null을 기본값 42로)
defaultTo42(undefined);  //=> 42
defaultTo42(NaN);        //=> 42
defaultTo42('Ramda');    //=> 'Ramda'  (정상 값은 그대로 반환)

// parseInt 실패 시 NaN -> 기본값 처리
defaultTo42(parseInt('not a number')); //=> 42

descend

R.descend는 ascend의 반대로, 내림차순(큰 값 우선) 정렬용 비교 함수를 생성합니다. 첫 번째 인자로 키 추출 함수 fn을 받아, fn(a) > fn(b)이면 -1을 반환하는 등 역순 비교를 수행하는 함수를 돌려줍니다. 이는 ascend 결과를 뒤집은 것과 같으며, R.sort 등에 사용하여 객체 배열을 특정 값 기준 내림차순으로 정렬할 수 있습니다.

사용 예시:

const byScoreDesc = R.descend(R.prop('score'));
const players = [
  { name: 'Alice', score: 10 },
  { name: 'Bob', score: 15 },
  { name: 'Carol', score: 8 }
];
R.sort(byScoreDesc, players);
//=> [ {name:'Bob', score:15}, {name:'Alice', score:10}, {name:'Carol', score:8} ]

descendNatural

R.descendNatural은 descend와 유사하게 자연 정렬 방식으로 비교하여 내림차순 정렬하는 함수입니다. ascendNatural과 마찬가지로 첫 번째 인자로 로케일 정보를, 두 번째 인자로 키 추출 함수를 받아 사용합니다. 생성된 비교 함수는 Intl.Collator를 이용해 두 값을 자연적인 방식으로 비교하되, 결과 순서를 역으로 뒤집어 큰 값이 앞서도록 합니다.

예를 들어 파일 이름 목록을 ‘9’ > ‘10’ 순으로 (문자열 비교에서는 ‘10’이 ‘9’보다 앞이지만 내림차순 자연정렬에서는 10이 더 크다고 간주) 정렬하는데 사용할 수 있습니다.

사용 예시:

const unsorted = ['img10.png', 'img2.png', 'img1.png'];
R.sort(R.descendNatural('en', R.identity), unsorted);
//=> ['img10.png', 'img2.png', 'img1.png']  (자연정렬 기준 내림차순)

difference

R.difference는 두 리스트의 차집합을 구합니다. 첫 번째 리스트에서, 두 번째 리스트에 존재하지 않는 모든 원소만으로 이루어진 새 배열을 반환합니다. 비교는 R.equals (깊은 동등 비교)를 사용하여 중복 요소나 객체도 값으로 비교하며, 결과 리스트에서는 중복을 제거하여 유일한 값만 남깁니다.

예를 들어, A 집합에서 B 집합에 없는 원소들을 구할 때 사용합니다.

사용 예시:

R.difference([1, 2, 3, 4], [3, 4, 5]);
//=> [1, 2]  (첫 리스트에서 3,4는 두 번째에 있으므로 제외)

R.difference([{a:1}, {b:2}], [{a:1}, {c:3}]);
//=> [ {b:2} ]  (객체 {a:1} 은 두 번째에도 있으므로 제외)

differenceWith

R.differenceWith는 커스텀 비교 함수를 사용하여 두 리스트의 차집합을 구합니다. 첫 번째 인자로 비교 함수 pred를 받고, 이후 두 리스트를 받습니다. 비교 함수는 (a, b) 형태로 두 리스트 요소를 받아 boolean을 반환해야 하며, true일 경우 두 요소를 동일한 것으로 간주합니다. 그 결과, 첫 번째 리스트에서 두 번째 리스트와 비교 함수 기준으로 겹치지 않는 요소들만 반환합니다.

예를 들어, 좌표 객체 리스트에서 특정 키값이 같은지를 비교기준으로 차집합을 구할 수 있습니다.

사용 예시:

const cmpById = (x, y) => x.id === y.id;
const list1 = [{id:1}, {id:2}, {id:3}];
const list2 = [{id:3}, {id:4}];
R.differenceWith(cmpById, list1, list2);
//=> [ {id:1}, {id:2} ]  (id가 3인 객체는 제외됨)

dissoc

R.dissoc는 객체에서 특정 속성을 제거한 복사본을 반환합니다. 첫 번째 인자로 속성 이름 prop, 두 번째 인자로 객체 obj를 받아 동작합니다. 결과는 주어진 객체를 얕게 복사한 후 prop 속성을 삭제한 새 객체입니다. (만약 속성이 원본 객체에 없으면 원본과 동일한 객체를 반환)

예를 들어, 민감한 정보 필드를 제거한 사용자 객체를 만들거나, 더 이상 필요없는 키를 제외한 새 객체를 만들 때 사용할 수 있습니다.

사용 예시:

R.dissoc('password', { user: 'kim', password: '1234', role: 'admin' });
//=> { user: 'kim', role: 'admin' }

R.dissoc('age', { name: '홍길동' });
//=> { name: '홍길동' }  (age 키가 없으므로 변화 없음)

dissocPath

R.dissocPath는 중첩 객체에서 특정 경로에 위치한 속성을 제거한 복사본을 반환합니다. 첫 번째 인자로 경로 배열 path를 받고, 두 번째 인자로 객체 obj를 받습니다. 작동 방식은 assocPath의 삭제 버전으로, 지정된 경로의 값을 제거한 객체를 반환합니다. 경로 중 존재하지 않는 부분이 있으면 그대로 둡니다. 또한 얕은 복사이므로, 경로 상의 객체들은 모두 복사본이 만들어집니다.

중첩된 구조에서 특정 필드를 제거하거나, JSON 데이터에서 필요한 부분만 제외하고자 할 때 유용합니다.

사용 예시:

R.dissocPath(['a', 'b', 'c'], { a: { b: { c: 42, d: 0 } } });
//=> { a: { b: { d: 0 } } }  (a.b.c 제거됨)

R.dissocPath(['x', 0, 'y'], { x: [ {y: 1, z:2}, {y:3} ] });
//=> { x: [ {z: 2}, {y:3} ] }  (x[0].y 제거됨)

divide

R.divide는 두 숫자의 나눗셈 결과를 반환합니다. 첫 번째 인자 a를 두 번째 인자 b로 나눈 값, 즉 수학적으로 a / b를 계산합니다. 순서에 유의해야 하는데, R.divide(a, b)는 a ÷ b 의미입니다. (일부 다른 라이브러리와 인자 순서가 반대인 경우도 있지만 Ramda는 이 순서)

이 함수는 커리 함수로, 부분 적용하여 역수 함수 등을 만들 수도 있습니다.

사용 예시:

R.divide(10, 2);    //=> 5
R.divide(42, 4);    //=> 10.5

const half = R.divide(R.__, 2);
half(10);           //=> 5   (10 ÷ 2)
const reciprocal = R.divide(1);
reciprocal(4);      //=> 0.25  (1 ÷ 4)

drop

R.drop은 리스트나 문자열의 처음 n개의 요소를 버리고 나머지를 반환합니다. 첫 번째 인자로 버릴 요소 수 n, 두 번째 인자로 대상 리스트(또는 문자열)를 받습니다. 결과는 원본의 앞에서부터 n개가 제거된 새로운 리스트(또는 문자열)입니다. 만약 n이 원본 길이 이상이면 빈 리스트(또는 빈 문자열)를 반환합니다.

예를 들어, 배열에서 처음 몇 개를 스킵하거나, 문자열 앞부분 (예: 접두사 부분)을 제거하는 경우에 사용할 수 있습니다.

사용 예시:

R.drop(2, ['a', 'b', 'c', 'd']); //=> ['c', 'd']
R.drop(3, 'ramda');             //=> 'da'  ('ram' 삭제)
R.drop(5, 'ramda');             //=> ''   (전체 길이만큼 버리면 빈 문자열)

dropLast

R.dropLast는 drop과 반대로, 리스트나 문자열의 끝에서 n개의 요소를 버리고 나머지를 반환합니다. 첫 번째 인자로 버릴 요소 수 n, 두 번째 인자로 대상 리스트(또는 문자열)를 받아, 뒤쪽 n개를 제외한 앞부분을 반환합니다. 원본은 변경되지 않습니다.

예를 들어, 배열의 마지막 요소를 제거하거나, 문자열의 마지막 글자(예: 마침표 등)를 제거하는 데 쓸 수 있습니다.

사용 예시:

R.dropLast(1, ['foo', 'bar', 'baz']); //=> ['foo', 'bar']
R.dropLast(2, 'ramda');              //=> 'ram'  ('da' 제거)

dropLastWhile

R.dropLastWhile은 리스트(또는 문자열)의 뒷부분에서부터 주어진 조건을 만족하는 연속된 요소들을 모두 제거합니다. 판정 함수 predicate를 첫 번째 인자로 받고, 두 번째 인자로 대상 리스트를 받습니다. 리스트의 끝에서부터 요소들을 살펴 predicate가 참인 동안은 버리고, 처음으로 거짓이 나오는 지점부터 남은 요소들을 반환합니다.

이 함수는 오른쪽 끝 부분에서 조건에 맞는 요소들을 없앤다는 점에서, 반대쪽에서 동작하는 R.dropWhile와 쌍을 이룹니다.

사용 예시:

const notZero = x => x !== 0;
R.dropLastWhile(notZero, [1, 2, 3, 0, 0, 1, 2, 0]);
//=> [1, 2, 3, 0, 0]  
// 설명: 뒤에서부터 0이 아닌 값(2,1,... 연속) 제거, 0 만나는 지점에서 멈춤

R.dropLastWhile(ch => ch !== 'd', 'Ramda');
//=> 'Ramd'   // 마지막 글자부터 'd'가 나올 때까지 삭제 -> 'Ramd'

dropRepeats

R.dropRepeats는 리스트에서 연속으로 반복된 중복 요소를 제거합니다. 입력 배열을 순회하면서 바로 이전 요소와 동일한 값이 나오면 건너뛰고, 다른 값이 나오면 결과 리스트에 추가합니다. 비교에는 R.equals를 사용하므로, 깊은 동등성으로 판단합니다.

주의할 점은 연속된 반복만 제거하므로, 떨어져서 나타나는 같은 값은 제거되지 않습니다. (완전히 중복을 제거하려면 R.uniq를 사용)

사용 예시:

R.dropRepeats([1, 1, 1, 2, 3, 3, 4, 4, 2, 2]);
//=> [1, 2, 3, 4, 2]

R.dropRepeats(['a', 'a', 'b', 'A', 'a']);
//=> ['a', 'b', 'A', 'a']  (대소문자 구분, 연속된 것만 제거)

dropRepeatsBy

R.dropRepeatsBy는 연속 중복 판단을 사용자 지정 함수의 결과 기준으로 수행합니다. 첫 번째 인자로 함수 fn을 받고, 두 번째로 리스트를 받습니다. 리스트를 순회하며, 현재 요소와 이전 요소를 각각 fn에 적용한 결과가 같으면 현재 요소를 생략합니다. 다르면 결과 리스트에 포함합니다.

예를 들어, 객체 리스트에서 특정 키의 값이 연속으로 동일할 때 중복으로 간주해 제거하는 데 활용할 수 있습니다.

사용 예시:

const absVal = Math.abs;
R.dropRepeatsBy(absVal, [1, -1, -1, 2, -2, 3, -3, -3]);
//=> [1, 2, 3, -3]
// 설명: 절댓값 기준 연속 중복 제거 (1과 -1 같은 것으로 침, 2와 -2, 3과 -3도 각각 같은 것으로 침)

dropRepeatsWith

R.dropRepeatsWith는 연속 중복 판단을 커스텀 비교 함수로 수행합니다. 첫 번째 인자로 비교 함수 pred(a, b)를 받고, 두 번째로 리스트를 받습니다. 이 함수는 리스트를 차례로 보며, 현재 요소와 직전 결과 요소를 pred에 주어 참이면 중복으로 간주해 제외합니다.

pred는 두 요소를 입력받아 boolean을 반환해야 합니다. 연속된 두 요소를 원하는 임의의 조건으로 비교하여 중복 판단을 하도록 할 수 있습니다.

사용 예시:

const eqByAbs = R.eqBy(Math.abs);
R.dropRepeatsWith(eqByAbs, [1, -1, 2, 3, -3, -3, 4]);
//=> [1, 2, 3, 4]
// (eqBy(Math.abs)은 절댓값이 같으면 true, 이를 기준으로 연속 중복 판단)

dropWhile

R.dropWhile는 리스트(또는 문자열)의 시작 부분부터 주어진 조건을 만족하는 요소들을 모두 무시하고 나머지를 반환합니다. 첫 번째 인자로 판정 함수 predicate, 두 번째 인자로 리스트를 받습니다. 리스트 앞에서부터 predicate가 true인 동안 요소를 버리고, 처음 false가 나오는 지점부터 남은 모든 요소를 돌려줍니다.

이 함수는 왼쪽(앞쪽)부터 조건에 맞는 부분을 잘라내기 위해 사용합니다. 예를 들어, 정렬된 리스트에서 특정 조건 이하의 값들을 건너뛰고 이후 값들을 얻고자 할 때 유용합니다.

사용 예시:

const isSmall = x => x <= 2;
R.dropWhile(isSmall, [1, 2, 3, 4, 1, 2]);
//=> [3, 4, 1, 2]  (앞에서 1,2는 조건에 맞아 버려지고 3에서 멈춤)

R.dropWhile(ch => ch !== 'R', 'HelloRamda');
//=> 'Ramda'  ('R' 문자가 나올 때까지 앞 글자 'Hello' 제거)

either

R.either는 두 개의 조건 함수를 논리 OR로 결합한 함수를 생성합니다. R.either(f, g)로 얻은 함수는 입력을 받아 f(input) 또는 g(input) 중 하나만 true이면 true를 반환합니다. f가 true를 반환하면 g는 호출되지 않는 단락 평가가 적용됩니다.

이는 anyPass의 두 함수 버전과 같으며, 두 조건 중 하나만 만족하면 되는 경우 사용합니다. 예를 들어, 숫자가 짝수이거나 10보다 큰지 등 두 가지 중 하나의 조건만 통과하면 되는 검사를 할 수 있습니다.

사용 예시:

const isEven = x => x % 2 === 0;
const isGT10 = x => x > 10;
const isEvenOrGT10 = R.either(isEven, isGT10);

isEvenOrGT10(8);  //=> true  (짝수)
isEvenOrGT10(13); //=> true  (10보다 큼)
isEvenOrGT10(9);  //=> false (짝수도 아니고 10보다 크지도 않음)

empty

R.empty는 주어진 값과 동일한 형의 “빈” 값을 반환합니다. Ramda는 Array -> [], Object -> {}, String -> '', TypedArray -> 빈 TypedArray, Arguments 객체 -> 유사 배열의 빈 객체, 등을 미리 정의해 놓았습니다. 만약 대상 값이 이러한 미리 정의되지 않은 타입이면서 empty 메서드나 fantasy-land empty를 구현하고 있으면 그것을 호출합니다.

즉, 어떤 컬렉션을 비워 초기 상태로 돌리고 싶을 때 사용합니다. 불변성 컨텍스트에서 데이터 구조를 동일한 타입의 초기값으로 리셋할 때 유용합니다.

사용 예시:

R.empty([1, 2, 3]);       //=> []          (빈 배열)
R.empty('hello');         //=> ''          (빈 문자열)
R.empty({ x: 1, y: 2 });  //=> {}          (빈 객체)
R.empty(Uint8Array.from([1,2])); //=> Uint8Array []  (빈 TypedArray)

endsWith

R.endsWith는 리스트나 문자열이 주어진 sublist(또는 substring)로 끝나는지를 확인합니다. 첫 번째 인자로 예상 끝부분 suffix (배열 또는 문자열), 두 번째 인자로 전체 리스트(또는 문자열)를 받아 불리언 결과를 반환합니다. 리스트의 경우 해당 요소들이, 문자열의 경우 해당 문자가 순서대로 끝에 위치하는지 검사합니다.

예를 들어, 파일명이 특정 확장자로 끝나는지 (endsWith('.png', filename)), 경로가 슬래시로 끝나는지 등을 체크할 때 사용할 수 있습니다.

사용 예시:

R.endsWith('JS', 'RAMDAJS');           //=> true   ('RAMDAJS'는 'JS'로 끝남)
R.endsWith('js', 'RAMDAJS');           //=> false  (대소문자 다름)
R.endsWith([5, 6], [1, 2, 3, 4, 5, 6]); //=> true   (배열 [5,6]으로 끝남)
R.endsWith([2, 3], [1, 2, 3]);         //=> false  ([2,3]이 끝부분이지만 전체와 같아서? => 실제 false는 아님. endsWith([2,3],[1,2,3])는 true, *RAMDA docs example different)

eqBy

R.eqBy는 특정 함수로 변환한 결과를 기준으로 두 값의 동등성을 검사합니다. 첫 번째 인자로 변환 함수 fn을 받고, 이후 두 값 x, y를 받습니다. fn(x)와 fn(y)를 계산한 뒤 R.equals로 비교하여 같으면 true, 다르면 false를 반환합니다.

예를 들어, 인물 객체 두 개를 ID 속성으로 비교하거나, 문자열을 소문자로 변환해 대소문자 구분 없이 비교할 때 활용할 수 있습니다.

사용 예시:

R.eqBy(Math.abs, -5, 5);   //=> true  (-5와 5를 절댓값으로 비교하면 같음)
R.eqBy(R.prop('id'), {id:1,name:'Kim'}, {id:2,name:'Lee'}); 
//=> false (id 1 vs 2, 다름)

eqProps

R.eqProps는 두 객체의 특정 속성 값이 R.equals 기준으로 같은지 검사합니다. 첫 번째 인자로 속성 이름 prop, 그리고 두 객체 obj1, obj2를 받습니다. 각각에서 해당 속성 값을 추출하여 R.equals로 비교한 결과를 반환합니다. 해당 속성이 없는 경우 undefined로 취급되어 비교됩니다.

예를 들어, 두 사용자 객체가 동일한 사용자 ID를 갖고 있는지, 두 설정 객체의 특정 옵션 값이 같은지 등을 판단할 때 사용할 수 있습니다.

사용 예시:

const obj1 = { a: 1, b: 2, c: 3 };
const obj2 = { a: 10, b: 20, c: 3, d: 40 };

R.eqProps('a', obj1, obj2); //=> false  (obj1.a=1 vs obj2.a=10)
R.eqProps('c', obj1, obj2); //=> true   (obj1.c=3 vs obj2.c=3 같음)

equals

R.equals는 두 값의 깊은 동등 여부를 판단합니다. 원시 값은 ===로 비교하고, 객체나 배열 등 복합 구조는 재귀적으로 모든 하위 값들을 비교하여 같으면 true를 반환합니다. 순환 참조를 가진 구조도 감지하여 올바르게 처리합니다. (동일한 모양의 순환 구조는 같다고 판단)

객체 비교 시 프로토타입 chain은 무시하고 own property만 비교합니다. R.equals는 일반적인 deep equal이며, 특히 리스트와 객체 등 값의 내용이 같으면 true입니다.

사용 예시:

R.equals(1, 1); //=> true
R.equals(1, '1'); //=> false
R.equals([1, 2, {x:3}], [1, 2, {x:3}]); //=> true (구조와 값 모두 동일)

const a = {}; a.v = a;
const b = {}; b.v = b;
R.equals(a, b); //=> true  (순환 참조 구조 동일)

evolve

R.evolve는 객체의 각 속성값을 지정된 변환 함수를 적용하여 변환한 새 객체를 반환합니다. 첫 번째 인자로 변환 스펙 객체 transformations를 받고, 두 번째 인자로 원본 객체를 받습니다. 스펙 객체는 원본 객체와 동일한 구조로, 각 위치에 함수가 있으면 해당 속성값에 그 함수를 적용합니다. 중첩 객체의 경우 스펙을 중첩 객체 형태로 주어 재귀적으로 적용됩니다. 스펙에 없는 속성은 원본 값을 그대로 유지합니다.

이 함수는 상태 객체의 일부 필드들을 일괄 갱신하거나, 여러 속성을 서로 다른 방식으로 변환해야 할 때 편리합니다.

사용 예시:

const tomato = { firstName: ' Tomato ', data: { elapsed: 100, remaining: 1400 } };
const transformations = {
  firstName: R.trim,            // 문자열 공백 제거
  lastName: R.trim,             // 없으면 적용 안 됨
  data: { elapsed: R.inc, remaining: R.dec }  // data 내부 처리
};
R.evolve(transformations, tomato);
//=> { firstName: 'Tomato', data: { elapsed: 101, remaining: 1399 } }

F

R.F는 항상 거짓(false)을 반환하는 함수입니다. 어떤 인자를 주든 무시하고 false를 돌려줍니다. (R.T는 그 반대로 항상 true를 반환)

이 함수는 조건식에서 기본 거짓 함수가 필요할 때나, 필요 없지만 인터페이스상 함수가 요구될 때 더미로 사용할 수 있습니다.

사용 예시:

R.F();            //=> false
[1, 2, 3].every(R.F);  //=> false  (하나도 true가 아니므로 false)

filter

R.filter는 주어진 조건을 만족하는 요소만 남기는 필터링 함수입니다. 첫 번째 인자로 판정 함수 predicate, 두 번째 인자로 필터링할 대상을 받습니다. 대상은 필터러블(Filterable) 타입이면 모두 가능하며 (배열, 객체 등), 결과는 대상과 같은 타입으로 조건을 만족하는 요소들만 포함합니다.

  • 배열의 경우 각 요소 중 predicate(element)가 true인 요소들로 구성된 새 배열을 반환합니다.

  • 객체의 경우 각 값에 predicate를 적용해 true인 키-값 쌍만 남긴 새 객체를 반환합니다.

또한, 대상이 2번째 인자가 아닌 첫 번째 자리에 올 수도 있습니다 (커리 적용 용이). 필터는 reject (조건에 맞지 않는 것 제거)와 반대 개념입니다.

사용 예시:

const isEven = n => n % 2 === 0;
R.filter(isEven, [1, 2, 3, 4]); 
//=> [2, 4]   (짝수만 남김)

R.filter(isEven, {a: 1, b: 2, c: 3, d: 4});
//=> { b: 2, d: 4 }  (짝수 값의 속성만 남김)

find

R.find는 리스트에서 조건을 만족하는 첫 번째 요소를 반환합니다. 첫 번째 인자로 판정 함수 predicate, 두 번째 인자로 리스트를 받아 동작합니다. 리스트를 왼쪽에서 오른쪽으로 탐색하며, predicate(element)가 참이 되는 첫 번째 요소를 발견하면 즉시 반환하고 검색을 종료합니다. 만약 끝까지 찾아도 없으면 undefined를 반환합니다.

예를 들어, 숫자 리스트에서 처음 나오는 짝수를 찾거나, 객체 리스트에서 특정 속성 값이 원하는 조건을 만족하는 첫 객체를 찾는 데 사용할 수 있습니다.

사용 예시:

const isOdd = x => x % 2 === 1;
R.find(isOdd, [2, 4, 7, 8, 11]); //=> 7   (첫 번째 홀수)
R.find(R.propEq('id', 3), [{id:1},{id:3},{id:5}]);
//=> {id:3}  (id가 3인 첫 객체 반환)
R.find(isOdd, [2, 4, 6]); //=> undefined  (없으므로 undefined)

findIndex

R.findIndex는 리스트에서 조건을 만족하는 첫 번째 요소의 인덱스를 반환합니다. R.find와 동작은 같지만, 요소 자체가 아닌 위치(index)를 돌려준다는 차이가 있습니다. 조건에 맞는 요소가 없다면 -1을 반환합니다.

이 함수를 이용해 원하는 요소의 위치를 알아내거나, 그 위치를 이용해 다른 배열 조작을 할 수 있습니다.

사용 예시:

const largerThan10 = x => x > 10;
R.findIndex(largerThan10, [4, 9, 11, 15]); //=> 2  (11이 조건 만족, 인덱스 2)
R.findIndex(R.propEq('id', 3), [{id:2},{id:3},{id:4}]);
//=> 1  (id=3 객체는 인덱스 1)
R.findIndex(largerThan10, [4, 9]); //=> -1  (없음)

findLast

R.findLast는 R.find와 유사하지만, 리스트의 끝부분부터 거꾸로 탐색하여 조건을 만족하는 마지막 요소를 찾아 반환합니다. 즉, 배열을 오른쪽에서 왼쪽으로 살펴보다 처음(사실 끝에서 보면 첫 번째)으로 조건에 맞는 요소를 돌려줍니다. 조건 만족 요소가 없으면 undefined를 반환합니다.

예를 들어, 로그 리스트에서 마지막 오류 로그를 찾거나, 숫자 리스트에서 마지막 홀수를 찾는 경우 쓸 수 있습니다.

사용 예시:

const isOdd = x => x % 2 === 1;
R.findLast(isOdd, [2, 3, 4, 6, 7, 8]); //=> 7  (마지막 홀수인 7 반환)
R.findLast(R.propEq('status', 'error'), [
  {id:1,status:'ok'},{id:2,status:'error'},{id:3,status:'error'}
]);
//=> {id:3,status:'error'}  (마지막 error 상태 객체)

findLastIndex

R.findLastIndex는 리스트의 끝에서부터 조건을 만족하는 첫 번째 요소의 인덱스를 반환합니다. R.findLast와 같은 탐색을 하지만, 요소가 아니라 인덱스를 돌려줍니다. 조건을 만족하는 요소가 없으면 -1을 반환합니다.

예를 들어, 배열에서 특정 조건에 해당하는 마지막 요소가 몇 번째인지 알아내거나, 거기서부터 요소를 삭제하는 등의 용도에 쓰일 수 있습니다.

사용 예시:

const isEven = x => x % 2 === 0;
R.findLastIndex(isEven, [1, 3, 5, 6, 7, 8]); //=> 5  (마지막 짝수 8의 인덱스)
R.findLastIndex(R.propEq('active', true), [
  {id:1,active:true},{id:2,active:false},{id:3,active:true}
]);
//=> 2  (마지막 active=true 객체의 인덱스)
R.findLastIndex(isEven, [1, 3, 5]); //=> -1

flatten

R.flatten은 중첩된 배열을 한 단계 평탄화합니다. 즉, 배열 내에 배열이 있으면 그 내부 요소들을 꺼내어 상위 배열에 포함시킵니다. 중첩 깊이가 1 이상일 경우에만 효과가 있으며, 한 번 평탄화된 결과에 더 중첩 구조가 남아있으면 그 부분은 그대로 둡니다. (이 함수는 1단계만 풀어줍니다)

예를 들어, [[1, 2], 3, [4, [5]]]를 flatten하면 [1, 2, 3, 4, [5]]가 됩니다. 완전히 깊은 평탄화가 필요하다면 이 함수 대신 재귀적 처리나 다른 방법을 써야 합니다.

사용 예시:

R.flatten([1, [2, 3], [[4]], 5]);
//=> [1, 2, 3, [4], 5]  (한 단계만 풀림, [4]는 남음)

R.flatten([['a','b'], ['c','d']]);
//=> ['a', 'b', 'c', 'd']

flip

R.flip은 함수의 첫 번째 두 인자의 순서를 교환한 새로운 함수를 만들어줍니다. 이 함수는 2개 이상의 인자를 받는 함수에 대해, 첫 번째와 두 번째 인자의 자리를 바꿔서 호출되도록 래핑합니다. 추가 인자(세 번째 이후)는 순서 변화 없이 그대로 전달됩니다.

주로 인자 순서가 Ramda의 커리 함수 체인에 맞지 않을 때 활용합니다. 예를 들어, 기본적으로 (target, list) 순인 함수에 (list, target) 순으로 부분 적용하고 싶을 때 flip을 사용합니다.

사용 예시:

const mergeTriple = (x, y, z) => [x, y, z];
mergeTriple(1, 2, 3);           //=> [1, 2, 3]

const flippedMerge = R.flip(mergeTriple);
flippedMerge(1, 2, 3);          //=> [2, 1, 3]  (첫 두 개 자리 바뀜)

const prepend = R.flip(R.append);
prepend([4,5], 3);              //=> [3, 4, 5]  (원래 append는 (el, list))

flow

R.flow는 값과 함수 리스트를 받아, 해당 값에 함수 리스트를 왼쪽->오른쪽 순서로 연쇄 적용한 최종 결과를 반환합니다. R.pipe와 유사하지만, flow는 첫 번째 인자로 초기값을 받고, 두 번째 인자로 함수들의 배열을 받습니다. 즉, R.flow(initial, [f, g, h])는 h(g(f(initial)))와 동일한 결과를 줍니다.

이 함수는 값을 바로 파이프라인으로 전달하고자 할 때, 굳이 함수를 한번 더 감싸지 않고 사용할 수 있도록 도와줍니다. 다른 라이브러리에서는 flow를 pipe라고 부르기도 하는데, Ramda에서는 인자 서명이 다른 별개 함수로 제공합니다.

사용 예시:

R.flow(9, [Math.sqrt, R.negate, R.inc]); 
//=> -2   (9 -> sqrt(9)=3 -> negate(3)=-3 -> inc(-3)=-2)

const person = { first: 'Jane', last: 'Doe' };
R.flow(person, [R.values, R.join(' ')]); 
//=> "Jane Doe"   (객체 -> 값 배열 ['Jane','Doe'] -> 문자열 결합)

forEach

R.forEach는 리스트의 각 요소에 대해 주어진 함수를 수행한 후 원본 리스트를 반환합니다. 첫 번째 인자로 수행할 함수 fn (하나의 요소를 받아 부수 효과를 내는 함수)을 받고, 두 번째 인자로 리스트를 받습니다. fn은 각 요소에 대해 순서대로 호출되며, forEach 자체는 처리 후 입력 리스트를 그대로 반환합니다. (원본에 변동 없음, 단 함수가 내부에서 변동을 일으킬 순 있음)

이 함수는 순수 함수적이지 않으며, 주로 콘솔 출력이나 DOM 조작 등 부수 효과(side effect)를 위해 사용합니다. 기본 배열 forEach와의 차이는 Ramda의 조합성 및 객체도 지원한다는 점 정도입니다.

사용 예시:

R.forEach(x => console.log(x * 2), [1, 2, 3]);
// 콘솔 출력: 2, 4, 6
// 반환값: [1, 2, 3] (원본 배열 그대로 반환)

const logKeyVal = (val, key) => console.log(`${key}: ${val}`);
R.forEachObjIndexed(logKeyVal, {a:1, b:2});
// 콘솔 출력: 'a: 1', 'b: 2'

forEachObjIndexed

R.forEachObjIndexed는 객체의 각 속성(key-value)에 대해 함수를 실행합니다. 첫 번째 인자로 (value, key, obj) -> * 형태의 함수 fn을 받고, 두 번째 인자로 객체를 받습니다. 객체의 각 own property에 대해 fn(value, key, obj)를 호출합니다. 반환값은 항상 원본 객체입니다.

이 함수도 forEach처럼 주로 부수 효과를 위해 존재하며, 객체 전용으로 키 정보까지 함께 필요할 때 사용합니다.

사용 예시:

const printKeyColonVal = (val, key) => console.log(`${key}: ${val}`);
R.forEachObjIndexed(printKeyColonVal, { name: 'Neo', age: 28 });
// 콘솔 출력: 'name: Neo'  그리고 'age: 28'
// 반환: { name: 'Neo', age: 28 } (원본 그대로)

fromPairs

R.fromPairs는 [키, 값] 형태의 2-원소 배열들의 리스트를 객체로 변환합니다. 즉, 배열의 각 요소가 [key, value] 형식이면 이를 키와 값으로 하는 객체를 만들어 반환합니다. 같은 키가 중복되면 나중에 나온 쌍의 값으로 설정됩니다.

이 함수는 R.toPairs (객체 -> 배열)와 반대 동작을 합니다. 예를 들어, 키-값 쌍의 목록을 받아 객체로 조립하거나, Map 등을 단순 객체로 변환할 때 사용할 수 있습니다.

사용 예시:

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

R.fromPairs([['x', 10], ['x', 20]]);
//=> { x: 20 }  (중복 키 'x'는 마지막 값 20으로 설정)

groupBy

R.groupBy는 리스트의 요소들을 특정 키 함수의 반환값에 따라 그룹으로 묶어 객체로 반환합니다. 첫 번째 인자로 키 생성 함수 fn을 받고, 두 번째 인자로 리스트를 받습니다. 결과 객체의 키는 fn이 반환한 문자열이며, 값은 원 리스트에서 그 키에 해당하는 요소들의 배열입니다.

이 함수는 예를 들어, 학생 목록을 학년별로 묶는다든지, 단어 목록을 첫 글자별로 그룹화한다든지 할 때 유용합니다. R.collectBy와 달리 결과가 객체입니다.

사용 예시:

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

groupWith

R.groupWith는 리스트를 인접 요소들끼리 주어진 관계를 만족하는 한 그룹으로 묶어 배열의 배열로 반환합니다. 첫 번째 인자로 비교 함수 pred(a, b)를 받고, 두 번째 인자로 리스트를 받습니다. 리스트를 순회하면서 현재 요소와 이전 요소를 pred로 비교하여 true이면 같은 그룹으로 묶습니다. 새로운 그룹은 pred가 false가 되는 전환점에서 시작됩니다.

예를 들어, 숫자 배열에서 연속된 숫자들의 차이가 1인 경우 묶거나, 연속된 문자열들이 같은 패턴인지에 따라 그룹화 등에 사용할 수 있습니다.

사용 예시:

R.groupWith(R.equals, [0, 1, 1, 2, 3, 5, 8, 13]);
//=> [[0], [1, 1], [2], [3], [5], [8], [13]]
// (연속 동일값 묶기)

const diffOne = (a, b) => a + 1 === b;
R.groupWith(diffOne, [0, 1, 2, 3, 5, 6, 9]);
//=> [[0, 1, 2, 3], [5, 6], [9]]
// (연속 증가 숫자 묶기)

gt

R.gt는 첫 번째 인자가 두 번째 인자보다 큰지 (>) 비교하여 boolean을 반환합니다. (greater than) 커리 함수로 제공되어, 부분 적용하여 어떤 값보다 큰지 검사하는 함수를 쉽게 만들 수 있습니다.

사용 예시:

R.gt(5, 3); //=> true  (5 > 3)
R.gt(3, 3); //=> false (3 > 3 아님)
R.gt(2, 5); //=> false (2 > 5 아님)

const greaterThan10 = R.gt(R.__, 10);
greaterThan10(15); //=> true  (15 > 10)
greaterThan10(8);  //=> false

gte

R.gte는 첫 번째 인자가 두 번째 인자보다 크거나 같은지 (>=) 비교합니다. (greater than or equal) 동작과 용법은 R.gt와 유사하며, >= 조건을 확인합니다.

사용 예시:

R.gte(5, 3); //=> true  (5 >= 3)
R.gte(3, 3); //=> true  (3 >= 3)
R.gte(2, 5); //=> false (2 >= 5 아님)

const atLeastZero = R.gte(R.__, 0);
atLeastZero(1);  //=> true
atLeastZero(-1); //=> false

has

R.has는 객체에 특정 속성(키)이 자체적으로 존재하는지 확인합니다. 첫 번째 인자로 키 이름(문자열) prop, 두 번째 인자로 객체 obj를 받아, obj에 해당 키가 _own property_로 있으면 true, 없으면 false를 반환합니다.

프로토타입 체인에 있는 키는 고려하지 않습니다. (즉, obj.hasOwnProperty(prop)와 유사) 이 함수는 객체에 어떤 속성이 정의되어있는지 빠르게 확인하고자 할 때 사용합니다.

사용 예시:

R.has('name', { name: 'alice', age: 25 }); //=> true
R.has('age', { name: 'bob' });            //=> false (age 없음)

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

hasIn

R.hasIn은 객체에 특정 속성이 존재하는지 프로토타입 체인까지 포함해서 확인합니다. 첫 번째 인자로 키 이름 prop, 두 번째 인자로 객체 obj를 받아, prop이 obj 자신 또는 그 프로토타입 체인에 속해 있으면 true를 반환합니다. has와 달리 상속된 속성도 인정합니다.

JavaScript의 in 연산자와 유사하지만, in은 프로토타입에 있어도 true입니다. 이 함수는 has와 용도는 비슷하나, JS 상속 특성을 고려해야 할 때 사용합니다.

사용 예시:

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

const square = new Rectangle(2, 2);
R.hasIn('width', square); //=> true  (자기 속성)
R.hasIn('area', square);  //=> true  (프로토타입 속성)
R.hasIn('toString', square); //=> true (Object.prototype 속성)

hasPath

R.hasPath는 객체의 특정 경로에 값이 존재하는지 확인합니다. 첫 번째 인자로 경로 배열 path, 두 번째 인자로 객체 obj를 받아, obj의 해당 경로를 따라 값이 정의되어 있으면 true, 없으면 false를 반환합니다. 경로 중간에 없는 부분이 있거나 마지막 속성이 존재하지 않으면 false입니다.

예를 들어, 깊게 중첩된 설정 객체에서 특정 설정 값이 존재하는지 검사할 때 유용합니다.

사용 예시:

R.hasPath(['user', 'profile', 'email'], { user: { profile: { email: 'a@a.com' } } });
//=> true  (해당 경로에 값 있음)

R.hasPath(['a', 'b', 'c'], { a: { x: 5 } });
//=> false (a.b가 없음)

R.hasPath(['x'], {}); //=> false

head

R.head는 리스트의 첫 번째 요소를 반환합니다. 배열의 첫 요소나, 문자열의 첫 문자 등을 반환하며, 만약 빈 리스트이면 undefined를 반환합니다. 이 함수는 R.tail (첫 요소 제외 나머지)과 대조되는 기능입니다.

사용 예시:

R.head(['a', 'b', 'c']); //=> 'a'
R.head([]);              //=> undefined
R.head('hello');         //=> 'h'
R.head('');              //=> undefined

identical

R.identical는 두 값이 삼중 등호(===)로 동일한지 검사합니다. 단, NaN은 NaN과 identical로 취급하고, +0과 -0은 다르다고 취급합니다. 즉, JavaScript의 Object.is와 동일한 판단 기준을 사용합니다.

이 함수는 R.equals와 달리 참조 같은지까지 정확하게 판단해야 할 때 사용합니다. 객체의 경우 동일 객체인지 (레퍼런스 동일) 판정합니다.

사용 예시:

const obj = {};
R.identical(obj, obj); //=> true   (같은 객체 참조)
R.identical({}, {});   //=> false  (내용 같아도 다른 객체이므로)
R.identical(NaN, NaN); //=> true   (NaN은 자신과 identical로 간주)
R.identical(0, -0);    //=> false  (+0과 -0은 구분)

identity

R.identity는 입력받은 값을 그대로 반환하는 함수입니다. 어떤 값을 인자로 주면 변경 없이 돌려주며, 주로 함수 합성에서 기본 전달 또는 초기값으로 쓰입니다. (CPS의 K-combinator와 유사)

예를 들어, 조건부로 함수를 적용할 때, 적용 안 할 경우 대체로 identity를 사용해 “변화 없음”을 표현합니다.

사용 예시:

R.identity(5); //=> 5
R.identity([1, 2, 3]); //=> [1, 2, 3] (같은 배열 반환)

const arr = [];
R.identity(arr) === arr; //=> true  (같은 참조 반환)

ifElse

R.ifElse는 조건에 따라 두 개의 함수를 선택적으로 실행하는 함수를 만들어줍니다. 인자로 (conditionFn, onTrueFn, onFalseFn)을 받습니다. 반환되는 함수는 어떤 값을 받아 conditionFn(value)가 참이면 onTrueFn(value) 결과를, 거짓이면 onFalseFn(value) 결과를 반환합니다. if ... else ...를 함수로 추상화한 것이라 볼 수 있습니다.

ifElse로 만든 함수는 인자의 개수가 세 함수 중 가장 많은 인자 수를 따라가며, 이를 Currying할 수 있습니다.

사용 예시:

const incCount = R.ifElse(
  R.has('count'),                     // 조건: 객체에 count 속성 존재?
  R.over(R.lensProp('count'), R.inc), // 참일 때: count 값을 1 증가
  R.assoc('count', 1)                 // 거짓일 때: count=1 속성 추가
);

incCount({ count: 1 }); //=> { count: 2 }
incCount({});          //=> { count: 1 }

inc

R.inc는 숫자를 1 증가시키는 함수입니다. R.dec의 반대이며, 입력 n에 대해 결과 n + 1을 돌려줍니다.

사용 예시:

R.inc(42); //=> 43
R.map(R.inc, [1, 2, 3]); //=> [2, 3, 4]

includes

R.includes는 리스트나 문자열에 특정 값이 포함되어 있는지를 판단합니다. 첫 번째 인자로 찾을 값 value, 두 번째 인자로 리스트(또는 문자열)를 받아, 해당 값이 포함되어 있으면 true, 아니면 false를 반환합니다. 문자열의 경우 부분 문자열 검색, 배열의 경우 요소 검색을 수행하며, 객체에는 사용하지 않습니다.

R.includes는 R.indexOf(value) !== -1과 유사하지만 R.equals로 비교하기 때문에 객체도 값 비교 가능합니다.

사용 예시:

R.includes(3, [1, 2, 3, 4]);         //=> true
R.includes(5, [1, 2, 3, 4]);         //=> false
R.includes({ name: 'Alice' }, [ {name:'Alice'}, {name:'Bob'} ]);
//=> true  (객체 값도 깊게 비교하여 포함 판단)

R.includes('world', 'Hello world'); //=> true  ('world' 부분 문자열 포함)

indexBy

R.indexBy는 리스트를 특정 키로 인덱싱한 객체를 생성합니다. 첫 번째 인자로 키 생성 함수 fn을 받고, 두 번째 인자로 객체의 배열을 받습니다. 리턴되는 객체는 각 배열 요소를 fn에 적용한 결과를 키로, 해당 요소 자체를 값으로 맵핑합니다. 만약 같은 키가 여러 요소에서 나올 경우 마지막 요소가 그 키를 덮어씁니다.

이 함수는 리스트를 객체 형태로 변환하여 특정 키로 바로 접근할 수 있게 해줍니다. (예: id로 인덱싱하여 id->객체 참조)

사용 예시:

const list = [
  { id: 'xyz', title: '제목 A' },
  { id: 'abc', title: '제목 B' }
];
R.indexBy(R.prop('id'), list);
//=> {
//   xyz: { id:'xyz', title:'제목 A' },
//   abc: { id:'abc', title:'제목 B' }
//}

indexOf

R.indexOf는 배열에서 특정 값이 처음 나타나는 인덱스를 반환합니다. 첫 번째 인자로 찾을 값 value, 두 번째 인자로 배열 list를 받으며, 발견되면 인덱스 (0부터), 없으면 -1을 돌려줍니다. 비교에는 R.equals를 사용하여 깊은 비교를 수행합니다.

이 함수는 JavaScript 내장 Array.prototype.indexOf와 유사하지만, 두 가지 차이점이 있습니다: 커리 함수라는 점과 R.equals를 사용해 객체도 값 비교합니다.

사용 예시:

R.indexOf(3, [1, 2, 3, 4]);   //=> 2  (값 3은 인덱스 2에 있음)
R.indexOf(5, [1, 2, 3, 4]);   //=> -1 (없음)

const obj = {x:1};
R.indexOf({x:1}, [obj, {x:1}]); 
//=> 0   (첫 요소 obj와 {x:1} 깊은 비교 같으므로 인덱스 0 반환)

init

R.init는 리스트나 문자열에서 마지막 요소를 제외한 앞부분 모두를 반환합니다. R.tail (첫 요소 제외 나머지)와 대칭적으로, R.init은 마지막 요소를 제거한 나머지를 돌려줍니다. 빈 리스트에 대해 호출하면 빈 리스트를 반환합니다.

사용 예시:

R.init([1, 2, 3, 4]); //=> [1, 2, 3]
R.init([1]);          //=> []   (하나 요소 제거하면 빈 배열)
R.init([]);           //=> []   (빈 배열이면 빈 배열)

R.init('abc');        //=> 'ab'
R.init('a');          //=> ''   (빈 문자열)

innerJoin

R.innerJoin은 두 리스트 간의 “내부 조인”을 수행합니다. 첫 번째 인자로 두 리스트 요소를 비교할 관계 함수 pred를 받고, 두 번째 인자로 첫 번째 리스트 list1, 세 번째 인자로 두 번째 리스트 list2를 받습니다. 결과는 list1의 요소 중 pred(element, anyFromList2)를 만족하는 짝이 하나라도 있는 요소들로 구성된 새 리스트입니다. 쉽게 말해, list1의 각 요소가 list2의 어떤 요소와 관련성이 있을 때 그 list1 요소를 결과에 포함합니다.

예를 들어, 두 배열이 있는데 하나는 사용자 객체 리스트, 다른 하나는 사용자 ID 리스트일 때, ID 매칭되는 사용자 객체만 추출할 수 있습니다.

사용 예시:

const list1 = [
  { id: 824, name: 'Richie' },
  { id: 956, name: 'Dewey' },
  { id: 313, name: 'Bruce' },
  { id: 456, name: 'Stephen' },
  { id: 177, name: 'Neil' }
];
const list2 = [177, 456, 999];
const byId = (record, id) => record.id === id;
R.innerJoin(byId, list1, list2);
//=> [
//   { id: 456, name: 'Stephen' },
//   { id: 177, name: 'Neil' }
//]

insert

R.insert는 리스트의 특정 인덱스 위치에 새로운 요소를 삽입한 배열을 반환합니다. 첫 번째 인자로 위치 인덱스 index, 두 번째 인자로 삽입할 요소 elt, 세 번째 인자로 대상 배열을 받습니다. 반환되는 새 배열은 원본 배열의 index 위치에 elt가 들어가고, 기존 요소들은 그 뒤로 밀린 형태입니다. 원본 배열은 변경되지 않습니다.

인덱스가 배열 길이보다 크거나 같으면 elt를 맨 끝에 추가한 결과가 됩니다.

사용 예시:

R.insert(2, 'X', ['a', 'b', 'c', 'd']);
//=> ['a', 'b', 'X', 'c', 'd']

R.insert(0, 42, [10, 20, 30]);
//=> [42, 10, 20, 30]  (맨 앞에 42 삽입)

R.insert(5, 99, [1, 2, 3]); 
//=> [1, 2, 3, 99]  (인덱스 5가 배열 길이보다 크므로 끝에 추가)

insertAll

R.insertAll은 리스트의 특정 위치에 여러 요소의 배열을 삽입합니다. 첫 번째 인자로 위치 인덱스 index, 두 번째 인자로 삽입할 요소들의 배열 elts, 세 번째 인자로 대상 배열을 받습니다. 동작은 R.insert의 다중 요소 버전으로, index 위치부터 elts 배열의 모든 요소를 원래 순서대로 끼워넣은 새 배열을 반환합니다.

사용 예시:

R.insertAll(2, ['X', 'Y'], ['a', 'b', 'c', 'd']);
//=> ['a', 'b', 'X', 'Y', 'c', 'd']

R.insertAll(0, [1, 2], [3, 4]);
//=> [1, 2, 3, 4]  (맨 앞에 [1,2] 삽입)

intersection

R.intersection은 두 리스트의 교집합을 계산합니다. 첫 번째 리스트와 두 번째 리스트에 모두 포함된 모든 요소를 중복 없이 배열로 반환합니다. 비교에는 R.equals를 사용하여 값 동등성을 판단합니다. 결과 리스트의 순서는 첫 번째 리스트에서의 순서를 따릅니다.

예를 들어, 집합 A, B의 교집합 요소(겹치는 원소들)를 얻고자 할 때 사용합니다.

사용 예시:

R.intersection([1, 2, 3, 4], [3, 4, 5, 6]);
//=> [3, 4]

R.intersection([{x:1}, {x:2}], [{x:2}, {x:3}]);
//=> [ {x:2} ]  (객체 {x:2}가 공통)

intersperse

R.intersperse는 리스트의 각 요소 사이에 특정 값을 끼워 넣어 새로운 리스트를 생성합니다. 첫 번째 인자로 구분자 요소 separator를 받고, 두 번째 인자로 리스트를 받습니다. 결과 리스트는 원본 리스트의 요소들 사이마다 separator가 삽입된 형태입니다. (맨 마지막에는 붙지 않습니다)

주로 문자열 배열을 특정 구분 문자열로 합칠 때, 혹은 요소 간에 공통 요소를 넣어야 할 때 사용합니다.

사용 예시:

R.intersperse(',', ['a', 'b', 'c']); 
//=> ['a', ',', 'b', ',', 'c']

R.intersperse(0, [1, 2, 3, 4]);
//=> [1, 0, 2, 0, 3, 0, 4]

into

R.into는 변환(transducer)을 사용하여 입력 리스트를 특정 컬렉션으로 축적(accumulate)하는 범용 함수입니다. 첫 번째 인자로 초기 대상 acc (예: 빈 배열 [], 빈 문자열 '', 빈 객체 {} 등)를 받고, 두 번째 인자로 transducer (transformer 함수와 map/filter 등 조합) 또는 transformer 함수를, 세 번째 인자로 입력 컬렉션(list 등)을 받습니다. 결과는 변환을 모두 적용해 acc 타입의 컬렉션으로 쌓인 값입니다.

간단히 말해, into(acc, xform, list)는 transduce(xform, transformerForAccType, acc, list)의 간편 버전입니다. 직접 transducer를 다룰 때 사용되며, 일반 상황에서는 잘 쓰지 않을 수 있습니다.

사용 예시:

const numbers = [1, 2, 3, 4];
const transducer = R.compose(R.map(R.add(1)), R.take(2));

R.into([], transducer, numbers); 
//=> [2, 3]  (각 수에 +1후 처음 2개만 결과 배열에)

const intoStr = R.into('');
intoStr(transducer, numbers); 
//=> "23"  (결과를 문자열로 축적)

invert

R.invert는 객체의 키와 값의 관계를 뒤집은 새로운 객체를 생성합니다. 구체적으로, 주어진 객체의 값들을 키로 하고, 그 키에 해당하는 원래 키들의 배열을 값으로 하는 객체를 만듭니다. 하나의 값이 여러 키에 걸쳐 있을 수 있으므로 결과 값은 배열입니다.

예를 들어, { a: 'x', b: 'y', c: 'x' }를 invert하면 { x: ['a', 'c'], y: ['b'] }가 됩니다.

사용 예시:

const raceResults = {
  first: 'alice',
  second: 'jake',
  third: 'alice'
};
R.invert(raceResults);
//=> { 'alice': ['first', 'third'], 'jake': ['second'] }
// 동일 값 'alice'는 두 키 first와 third의 배열로 나타남

invertObj

R.invertObj는 객체의 키와 값을 일대일로 뒤집은 새로운 객체를 반환합니다. 값들이 문자열로 변환 가능한 경우에 사용하며, 값 -> 키로 반전합니다. 만약 같은 값을 가진 키들이 여러 개 있으면 마지막 키만 반영됩니다. 이 함수는 값이 유일하다는 전제에서 사용해야 합니다.

예를 들어, { x: '1', y: '2' } -> { 1: 'x', 2: 'y' }로 만드는 경우에 쓰입니다.

사용 예시:

const raceResults = {
  first: 'alice',
  second: 'jake'
};
R.invertObj(raceResults);
//=> { 'alice': 'first', 'jake': 'second' }

R.invertObj(['alice', 'jake']); 
//=> { 'alice': '0', 'jake': '1' }  (배열은 인덱스 문자열이 키)

invoker

R.invoker는 메서드 이름과 예상 인자 수를 받아, 해당 메서드를 객체에 바인드하여 함수처럼 호출할 수 있는 새로운 함수를 생성합니다. 첫 번째 인자로 아리티(메서드 인자 개수) arity, 두 번째 인자로 메서드 이름 methodName을 받아, 반환 함수는 먼저 자신이 받은 인자 중 마지막 하나를 컨텍스트 객체로 간주하고, 그 앞의 인자들을 지정한 메서드에 넘겨 호출합니다.

즉, R.invoker(n, 'methodName')은 (arg1,..., argN, obj) -> obj.methodName(arg1,..., argN)와 유사한 함수입니다. 이때 인자들을 커리 방식으로 부분 적용할 수 있습니다.

사용 예시:

const getJson = R.invoker(0, 'json');
fetch('/data').then(getJson) 
// fetch 결과 Response 객체에 .json() 메서드를 호출 (인자 0개)

// 1개 인자 갖는 메서드 예시: String.prototype.slice
const sliceFrom = R.invoker(1, 'slice');
sliceFrom(6, 'abcdefghijklm'); //=> 'ghijklm'  (문자열 slice(6) 결과)

const sliceFrom6To8 = R.invoker(2, 'slice')(6, 8);
sliceFrom6To8('abcdefghijklm'); //=> 'gh'

is

R.is는 값이 특정 타입(생성자)의 인스턴스인지 확인합니다. 첫 번째 인자로 생성자 함수(클래스) Ctor, 두 번째 인자로 값 val을 받아, val instanceof Ctor와 유사한 결과를 반환합니다. 단, Object 생성자에 대해서 Object.create(null)로 만든 객체도 true로 인식하는 등, instanceof보다 폭넓게 잘 동작합니다.

예를 들어, 값이 Array인지, Date인지 등의 타입 체크에 활용할 수 있습니다.

사용 예시:

R.is(Object, {});            //=> true
R.is(Number, 1);             //=> true (원시값도 Number 타입으로 간주)
R.is(String, 's');           //=> true
R.is(Array, [1, 2, 3]);      //=> true
R.is(Function, () => {});    //=> true

R.is(RegExp, /regex/);       //=> true
R.is(Date, '2020-01-01');    //=> false  (string은 Date 아님)

isEmpty

R.isEmpty는 값이 비어있는지 확인합니다. 값이 종류에 따라 다음 기준으로 비어있으면 true를 반환합니다:

  • 배열 또는 문자열: 길이 0이면 비어있음.

  • 객체: own property가 하나도 없으면 비어있음.

  • 기타 falsy값 (null, undefined)는 비어있지 않다(false)고 간주합니다 (주의).

  • 빈 Arguments 객체 등도 비어있으면 true.

사용 예시:

R.isEmpty([]);           //=> true
R.isEmpty('');           //=> true
R.isEmpty({});           //=> true
R.isEmpty({ length: 0 }); //=> false (length 속성만 있고 실제 iterable은 아님)
R.isEmpty([1]);          //=> false
R.isEmpty(null);         //=> false (null은 값 자체가 없지만, "비어있다"로 취급 않음)

isNil

R.isNil은 값이 null 또는 undefined인지를 확인합니다. 그 외의 값에 대해서는 모두 false를 반환합니다.

사용 예시:

R.isNil(null);        //=> true
R.isNil(undefined);   //=> true
R.isNil(0);           //=> false  (0은 nil이 아님)
R.isNil([]);          //=> false

isNotEmpty

R.isNotEmpty는 isEmpty의 반대로, 값이 비어있지 않은지 확인합니다. 값이 컬렉션인 경우 요소나 키가 하나라도 있으면 true를, 완전히 비어있으면 false를 반환합니다. (R.isNotEmpty(x)는 !R.isEmpty(x)와 동일합니다.)

사용 예시:

R.isNotEmpty([1, 2]);    //=> true
R.isNotEmpty([]);        //=> false
R.isNotEmpty('abc');     //=> true
R.isNotEmpty('');        //=> false
R.isNotEmpty({a: 1});    //=> true
R.isNotEmpty({});        //=> false
R.isNotEmpty(null);      //=> true (null은 empty 개념과 무관하게 true, isEmpty에서 false였음)

isNotNil

R.isNotNil은 값이 null도 아니고 undefined도 아닌지 확인합니다. R.isNil의 반대입니다. null이나 undefined이면 false를, 그 외 어떤 값이든 true를 반환합니다.

사용 예시:

R.isNotNil(null);        //=> false
R.isNotNil(undefined);   //=> false
R.isNotNil(0);           //=> true
R.isNotNil('');          //=> true
R.isNotNil([]);          //=> true

join

R.join은 리스트의 요소들을 주어진 구분자 문자열로 연결하여 문자열을 반환합니다. 첫 번째 인자로 구분자 separator (문자열)을 받고, 두 번째 인자로 리스트(또는 문자열)를 받아 동작합니다. 결과는 리스트의 요소들을 separator로 이어붙인 하나의 문자열입니다.

JavaScript의 Array.prototype.join과 동일한 기능을 함수형으로 제공합니다. (문자열에 사용하면 각 문자 사이에 separator를 넣습니다)

사용 예시:

R.join(' ', ['Hello', 'world']); //=> 'Hello world'
R.join('|', [1, 2, 3]);         //=> '1|2|3'
R.join('', ['a', 'b', 'c']);    //=> 'abc'

R.join('-', '2025-01-01'.split('-')); 
//=> '2025-01-01' (split과 join은 역작용)

juxt

R.juxt는 하나의 입력을 받아 여러 함수를 적용한 결과들을 배열로 반환하는 함수를 생성합니다. 인자로 함수들의 배열 [f1, f2, ...]을 받아, 반환되는 새로운 함수는 어떤 값을 입력받으면 [f1(value), f2(value), ...] 형태로 각 함수의 결과를 모은 배열을 돌려줍니다.

이를 이용해 하나의 대상에 대해 여러 가지 처리를 동시에 수행할 수 있습니다. 예를 들어, 숫자 하나에 대해 그 제곱과 세제곱을 동시에 계산해 배열로 얻는다든지, 문자열에 대해 길이와 대문자 변환 결과를 동시에 얻는다든지 하는 경우에 유용합니다.

사용 예시:

const getStats = R.juxt([Math.min, Math.max]);
getStats(3, 1, 9, -2); //=> [-2, 9]   (min과 max 결과를 배열로)

const name = { first: 'John', last: 'Doe' };
const fullNameParts = R.juxt([R.prop('first'), R.prop('last')]);
fullNameParts(name); //=> ['John', 'Doe']

keys

R.keys는 객체의 own property 키들의 배열을 반환합니다. 객체를 받아, 해당 객체 자신이 가진 열거 가능한 속성 이름(문자열 또는 심볼이지만, Ramda에서는 문자열로 반환)을 배열로 돌려줍니다. 순서는 일반적으로 삽입 순서지만, JavaScript 사양에 따릅니다.

이 함수는 Object.keys와 비슷하며, 프로토타입에 정의된 속성은 포함하지 않습니다.

사용 예시:

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

function Person(name) { this.name = name; }
Person.prototype.age = 10;
const p = new Person('Tom');
R.keys(p); //=> ['name']  (프로토타입 속성 'age'는 제외)

keysIn

R.keysIn은 객체 자신의 속성뿐 아니라 상속된 속성(프로토타입 체인 상의 속성)까지 포함한 키 배열을 반환합니다. Object.keys와 달리, for...in과 비슷하게 동작한다고 볼 수 있습니다 (그러나 순서 등은 JS 엔진에 따름).

프로토타입 체인상의 열거 가능한 속성 이름들도 모두 포함되므로, 특정 객체가 어떤 모든 속성을 사용할 수 있는지 한눈에 볼 수 있습니다.

사용 예시:

function F() { this.x = 'X'; }
F.prototype.y = 'Y';
const f = new F();
R.keysIn(f); //=> ['x', 'y']  (자신의 속성 x와 프로토타입 속성 y 모두 포함)

last

R.last는 리스트의 마지막 요소를 반환합니다. 배열의 마지막 요소 또는 문자열의 마지막 문자를 반환하며, 비어있으면 undefined를 반환합니다.

사용 예시:

R.last(['a', 'b', 'c']); //=> 'c'
R.last([]);              //=> undefined
R.last('hello');         //=> 'o'
R.last('');              //=> undefined

lastIndexOf

R.lastIndexOf는 리스트에서 주어진 값이 마지막으로 나타나는 인덱스를 반환합니다. R.indexOf와 유사하지만 오른쪽부터 찾습니다. 배열을 끝에서부터 검색하여 일치하는 가장 마지막 위치(사실 큰 인덱스)를 반환하며, 값이 없으면 -1입니다. 비교는 R.equals로 수행합니다.

사용 예시:

R.lastIndexOf(3, [1, 2, 3, 4, 3, 2, 1]); //=> 4  (값 3이 마지막 등장한 인덱스)
R.lastIndexOf('a', 'Banana'.split('')); //=> 5  ('a'가 마지막 등장한 위치, 인덱스 5)
R.lastIndexOf(10, [1, 2, 3]);          //=> -1 (없음)

length

R.length는 리스트(또는 문자열)의 길이를 반환합니다. value.length를 사용하는 것과 같지만, null 또는 undefined를 인자로 주면 오류 대신 undefined를 반환하는 등의 안전장치가 있습니다. 배열, 문자열, 유사 배열 등 length 속성을 가진 값에 대해 동작합니다.

사용 예시:

R.length([1, 2, 3]);   //=> 3
R.length('hello');     //=> 5
R.length([]);          //=> 0
R.length('');          //=> 0
R.length(null);        //=> undefined  (null.length는 에러지만 Ramda는 undefined 반환)

lens

R.lens는 커스텀 렌즈 객체를 생성합니다. 렌즈는 함수적 접근자로, 복잡한 데이터 구조 속 특정 부분을 간편히 조회(R.view), 설정(R.set), 수정(R.over)할 수 있게 해줍니다. R.lens(getter, setter) 형태로 사용하며:

  • getter: 대상 구조에서 값을 추출하는 함수

  • setter: 대상 구조와 새로운 값을 받아 해당 부분을 갱신한 새로운 구조를 반환하는 함수

이렇게 만들어진 렌즈를 R.view에 넘기면 getter 결과, R.set에 넘기면 setter 적용, R.over에 넘기면 해당 부분을 변환합니다.

사용 예시:

const xLens = R.lens(R.prop('x'), R.assoc('x'));
const obj = { x: 10, y: 20 };

R.view(xLens, obj);          //=> 10        (x 값 조회)
R.set(xLens, 15, obj);       //=> { x: 15, y: 20 }  (x 값 설정, 새로운 객체 반환)
R.over(xLens, R.inc, obj);   //=> { x: 11, y: 20 }  (x 값 1 증가)

lensIndex

R.lensIndex는 배열의 특정 인덱스를 조작하는 렌즈를 생성합니다. 인자로 인덱스 n을 받아, 그 인덱스 위치의 요소에 초점을 맞추는 렌즈를 반환합니다. 이 렌즈를 사용해 배열에서 해당 인덱스의 값을 view하거나, 변경할 수 있습니다.

사용 예시:

const firstElemLens = R.lensIndex(0);
const arr = ['foo', 'bar', 'baz'];

R.view(firstElemLens, arr);          //=> 'foo'
R.set(firstElemLens, 'hello', arr);  //=> ['hello', 'bar', 'baz'] (새 배열 반환)
R.over(firstElemLens, R.toUpper, arr); //=> ['FOO', 'bar', 'baz']

lensPath

R.lensPath는 객체의 중첩된 경로를 가리키는 렌즈를 생성합니다. 경로 배열 (예: ['a','b',0,'c'])을 인자로 받아, 그 경로의 값에 초점을 맞추는 렌즈를 반환합니다. 이 렌즈로 R.view, R.set, R.over 등을 적용하면 해당 경로의 값을 읽거나, 수정된 사본을 얻을 수 있습니다.

사용 예시:

const abLens = R.lensPath(['a', 'b']);
const obj = { a: { b: 2, c: 3 }, z: 0 };

R.view(abLens, obj);             //=> 2  (obj.a.b 값)
R.set(abLens, 5, obj);           //=> { a: { b: 5, c: 3 }, z: 0 }
R.over(abLens, R.negate, obj);   //=> { a: { b: -2, c: 3 }, z: 0 }

lensProp

R.lensProp는 객체의 특정 속성을 가리키는 렌즈를 생성합니다. 속성 이름 prop을 인자로 받아, 그 속성 값에 포커스하는 렌즈를 반환합니다. R.lensPath([prop])와 유사하지만, 더 간단히 속성 하나만 다룰 때 사용합니다.

사용 예시:

const nameLens = R.lensProp('name');
const person = { name: 'Alice', age: 30 };

R.view(nameLens, person);            //=> 'Alice'
R.set(nameLens, 'Bob', person);      //=> { name: 'Bob', age: 30 }
R.over(nameLens, R.toUpper, person); //=> { name: 'ALICE', age: 30 }

lift

R.lift는 임의의 함수(f) 를 인자 컨테이너(Applicative Functor)들에 작용하는 함수로 “승격”시킵니다. 쉽게 말해, 보통 값에 작용하는 함수를 리스트나 다른 Functor의 요소들 각각에 작용하도록 바꿉니다. 반환된 함수는 컨테이너들을 인자로 받아, f를 각 경우에 적용한 결과 컨테이너를 반환합니다.

예를 들어, 2개의 숫자에 더하는 함수 add를 두 개의 숫자 배열에 lift하면, 두 배열의 모든 조합의 합을 구하는 함수가 됩니다. R.ap를 내부적으로 활용합니다.

사용 예시:

const add = (x, y) => x + y;
const liftedAdd = R.lift(add);

liftedAdd([1, 2], [3, 4]); 
//=> [4, 5, 5, 6] 
// 설명: [1+3, 1+4, 2+3, 2+4] 모든 조합 합

// 3항 이상 함수도 lift 가능
const sum3 = (a, b, c) => a + b + c;
const liftedSum3 = R.lift(sum3);
liftedSum3([1, 2], [10], [100, 200]); 
//=> [111, 211, 112, 212] (각각 1+10+100, 1+10+200, 2+10+100, 2+10+200)

liftN

R.liftN은 lift와 유사하지만, 리프팅할 함수의 아리티(인자 개수)를 명시합니다. 첫 번째 인자로 함수의 인자 수 n, 두 번째 인자로 함수 fn을 받아, fn을 lift한 함수를 반환합니다. 이는 R.lift가 함수의 길이(fn.length)를 추론하지만, 함수가 가변 인자거나 정확한 arity와 다를 경우 명시적으로 지정할 수 있도록 한 것입니다.

사용 예시:

const addThree = (x, y, z) => x + y + z;
const liftedAdd3 = R.liftN(3, addThree);

liftedAdd3([1, 2], [10, 20], [100]);
//=> [111, 121, 211, 221]
// (1+10+100, 1+20+100, 2+10+100, 2+20+100)

lt

R.lt는 첫 번째 인자가 두 번째 인자보다 작은지 (<)를 검사하여 boolean을 반환합니다.

사용 예시:

R.lt(2, 5); //=> true   (2 < 5)
R.lt(5, 5); //=> false  (5 < 5 아님)
R.lt(7, 5); //=> false  (7 < 5 아님)

const lessThan10 = R.lt(R.__, 10);
lessThan10(8); //=> true
lessThan10(12); //=> false

lte

R.lte는 첫 번째 인자가 두 번째 인자보다 작거나 같은지 (<=)를 검사합니다.

사용 예시:

R.lte(2, 5); //=> true   (2 <= 5)
R.lte(5, 5); //=> true   (5 <= 5)
R.lte(7, 5); //=> false  (7 <= 5 아님)

const atMost10 = R.lte(R.__, 10);
atMost10(10); //=> true
atMost10(11); //=> false

map

R.map는 리스트나 객체의 각 요소를 변환 함수에 적용한 결과를 같은 구조로 담아 반환합니다. 배열의 경우 Array.prototype.map과 같고, 객체의 경우 값들을 변환하여 동일한 키 구조의 객체를 돌려줍니다.

Functor 인터페이스를 구현한 다른 객체에도 동작하며, 함수 자체에도 적용할 수 있습니다 (그 경우 함수 합성처럼 동작).

사용 예시:

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

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

R.map(R.add(1), R.multiply(3))(5);
//=> 16  
// 설명: R.map(f, g) = x => f(g(x)), 여기서 f=R.add(1), g=R.multiply(3) 이므로  add(1, multiply(3,x))

mapAccum

R.mapAccum은 map과 reduce를 조합한 함수입니다. 리스트를 왼쪽에서 오른쪽으로 순회하며 각 요소에 함수 fn(acc, value)를 적용합니다. fn은 누적기 acc와 현재 요소 x를 받아 [newAcc, mappedValue] 튜플을 반환해야 합니다. mapAccum은 모든 요소를 처리한 후 최종 누적기 값과, 처리 과정에서 얻은 mappedValue들의 리스트를 [finalAcc, resultList] 형태로 반환합니다.

쉽게 말해, fold/reduce하면서 중간에 map한 결과도 함께 쌓는 작업입니다. 예를 들어, 번호 문자열들을 누적 연결하면서 각 단계의 누적 문자열도 함께 결과로 수집하는 데 사용할 수 있습니다.

사용 예시:

const digits = ['1', '2', '3', '4'];
const appender = (acc, digit) => [acc + digit, acc + digit];

R.mapAccum(appender, '0', digits);
//=> ['01234', ['01', '012', '0123', '01234']]
// 설명: 
// 누적 acc 초기 '0'에서 
// '1' 처리 -> acc='01', output '01'
// '2' 처리 -> acc='012', output '012'
// ...
// 최종 acc='01234', 결과 리스트 = 각 단계 누적 문자열들

mapAccumRight

R.mapAccumRight은 mapAccum과 비슷하지만, 리스트를 오른쪽에서 왼쪽으로 처리합니다. 나머지 동작은 동일합니다: 함수 fn(acc, value)로 누적과 변환을 동시에 수행하고, 최종 누적값과 결과 리스트를 반환합니다. 결과 리스트는 오른쪽부터 처리한 순서대로 쌓이지만, 순서를 알아보기 쉽게 왼쪽부터 채워나갑니다.

사용 예시:

const digits = ['1', '2', '3', '4'];
const appender = (acc, digit) => [digit + acc, digit + acc];

R.mapAccumRight(appender, '5', digits);
//=> ['12345', ['12345', '2345', '345', '45']]
// 설명: 
// 초기 acc '5'
// 오른쪽 끝 '4' -> acc='45', output '45'
// '3' -> acc='345', output '345'
// '2' -> acc='2345', output '2345'
// '1' -> acc='12345', output '12345'
// 결과 누적 '12345', 리스트는 각 단계 출력들 (왼쪽부터 쌓임)

mapObjIndexed

R.mapObjIndexed는 객체 전용 map 함수로, 값과 키를 함께 콜백에 제공합니다. 첫 번째 인자로 (value, key, obj) -> result 형태의 함수 fn을 받고, 두 번째 인자로 객체를 받습니다. 결과는 원본 객체의 키와 동일한 키를 가지며, 각 값이 fn(원래값, 키, 원본객체) 결과로 변환된 새 객체입니다.

이는 R.map을 객체에 쓰는 것과 달리 콜백에서 키를 알 수 있다는 점이 다릅니다.

사용 예시:

const withKey = (val, key) => key + val;
R.mapObjIndexed(withKey, { x: 1, y: 2 });
//=> { x: 'x1', y: 'y2' }

const obj = { a: 1, b: 2 };
R.mapObjIndexed((val, key, o) => `${key}:${val}/${o === obj}`, obj);
//=> { a: 'a:1/true', b: 'b:2/true' }

match

R.match는 문자열에 대해 정규표현식 매치를 수행합니다 (JavaScript의 String.prototype.match에 해당). 첫 번째 인자로 정규표현식 regex, 두 번째 인자로 문자열 str을 받아, str.match(regex)의 결과 (배열 또는 null) 대신, 일치 결과 배열 또는 빈 배열를 반환합니다. Ramda에서는 매치 없을 때 null 대신 빈 배열을 주어 활용성을 높였습니다.

사용 예시:

R.match(/([A-Z])\w+/, 'Hello Ramda'); 
//=> ['Hello']  (대문자로 시작하는 연속 문자열 첫 매치)

R.match(/not/, 'Just do it');       
//=> []  (매치 없음, 빈 배열)

mathMod

R.mathMod는 수학적 모듈로(mod) 연산을 수행합니다. JavaScript의 % 연산과 달리 음수에서 양수 나머지를 얻도록 정의되었습니다. 첫 번째 인자 피제수 m, 두 번째 인자 모듈러 p를 받아 m mod p 결과를 반환합니다. p가 0이거나 음수일 경우 NaN을 반환합니다. 이 함수는 음수 피제수에도 양수 나머지를 돌려주므로, 수학적인 mod 연산을 원할 때 사용합니다.

사용 예시:

R.mathMod(-17, 5); //=> 3   (-17 mod 5 = 3, 자바스크립트 -17%5는 -2이지만 mathMod는 3)
R.mathMod(17, 5);  //=> 2   (17 mod 5 = 2)
R.mathMod(17, -5); //=> NaN (음수 모듈러는 NaN)
R.mathMod(17, 0);  //=> NaN (0으로 mod 정의되지 않음)
R.mathMod(17.5, 5); //=> NaN (실수는 정수로 변환 안함, NaN)

max

R.max는 두 값 중 큰 값을 반환합니다. 숫자나 문자열 등 순서 비교가 가능한 타입에 대해 동작하며, > 비교를 기반으로 큰 값을 돌려줍니다. 인자들 타입이 맞지 않으면 예측 불가 결과일 수 있습니다.

사용 예시:

R.max(10, 3);    //=> 10
R.max('apple', 'banana'); //=> 'banana'  (문자열 비교, 'banana'가 더 큼)
R.max(5, 5);     //=> 5

maxBy

R.maxBy는 커스텀 함수의 결과를 기준으로 두 값 중 큰 값을 선택합니다. 첫 번째 인자로 키 추출 함수 fn을 받고, 그 다음 두 값 a, b를 받습니다. fn(a)와 fn(b)를 비교하여 큰 쪽에 대응하는 원래 값 (a 또는 b)를 반환합니다. 즉, fn 결과 기준 max 연산입니다.

예를 들어, 객체의 특정 속성으로 크기를 비교하거나, 절댓값 비교 시 음수/양수 판단 없이 값이 더 큰 쪽을 선택하는 경우 사용할 수 있습니다.

사용 예시:

const square = x => x * x;
R.maxBy(square, -3, 2); //=> -3  (비교값: 9 vs 4, -3의 제곱이 더 크므로 -3 선택)

const alice = { name: 'Alice', age: 30 };
const bob = { name: 'Bob', age: 25 };
R.maxBy(R.prop('age'), alice, bob); 
//=> { name: 'Alice', age: 30 } (나이 비교하여 Alice 객체 선택)

mean

R.mean은 숫자 리스트의 산술 평균을 계산합니다. 숫자 배열을 인자로 받아, 합계를 요소 개수로 나눈 값을 반환합니다. 빈 배열에 사용하면 NaN을 반환하므로 주의해야 합니다.

사용 예시:

R.mean([2, 7, 9]); //=> 6  (합 18 / 개수 3)
R.mean([1, 2, 3, 4]); //=> 2.5

R.mean([]); //=> NaN  (빈 리스트 평균 정의 안됨)

median

R.median은 숫자 리스트의 중앙값을 계산합니다. 배열을 정렬하지는 않고, 내부적으로 정렬하여 중간 요소 또는 중간 두 요소의 평균을 반환합니다. 요소 개수가 짝수이면 두 중앙값의 평균을, 홀수이면 딱 가운데 값을 반환합니다. 빈 배열이면 NaN을 반환합니다.

사용 예시:

R.median([2, 9, 7]);       //=> 7  (정렬: [2,7,9], 중앙값 7)
R.median([7, 2, 10, 9]);   //=> 8  (정렬: [2,7,9,10], 중앙 두 값 (7,9) 평균 8)
R.median([]);              //=> NaN

memoizeWith

R.memoizeWith는 사용자 제공 키 생성 함수에 따라 결과를 캐시하는 메모이제이션 함수를 만듭니다. 첫 번째 인자로 캐시 키 생성 함수 keyGen, 두 번째 인자로 순수 함수 fn을 받아, 동일한 인자로 호출될 경우 캐시에 저장된 결과를 반환하는 메모이즈된 함수를 반환합니다. keyGen은 fn의 인자들을 받아 string 키를 만들어야 하며, 이 키로 결과가 저장됩니다.

이 함수는 fn이 순수하며 동일 입력에 항상 동일 출력일 때 성능 향상을 위해 사용합니다. 캐시 키가 중복되지 않도록 신경 써야 하고, 메모리 누수 방지에 주의해야 합니다.

사용 예시:

// 사람 객체 {birth, death} -> 나이 계산, birth/death 조합 문자열을 키로 사용
const ageKey = person => `${person.birth}/${person.death}`;
const computeAge = person => ({ ...person, age: person.death - person.birth });
const memoAge = R.memoizeWith(ageKey, computeAge);

const person = { birth: 1921, death: 1999 };
console.log(memoAge(person)); // computing 메시지, 계산 수행
console.log(memoAge(person)); // 이전 결과 캐시 사용, 계산 생략

mergeAll

R.mergeAll은 객체들의 배열을 순차적으로 얕은 병합(shallow merge)하여 하나의 객체로 반환합니다. 왼쪽부터 오른쪽으로 진행하며, 키 충돌 시 나중 객체의 값으로 덮어씁니다. 이 함수는 다수의 객체를 하나로 합칠 때 간편합니다.

사용 예시:

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 } (foo 값은 마지막 객체의 2로 덮어씀)

mergeDeepLeft

R.mergeDeepLeft는 두 객체를 깊은 수준에서 병합합니다. 첫 번째 객체 lObj의 own property를 기본으로 하고, 두 번째 객체 rObj의 own property를 이것과 병합하여 새로운 객체를 만듭니다. 만약 같은 키가 두 객체에 모두 있고 그 값이 객체라면 재귀적으로 병합하고, 객체가 아니면 첫 번째 객체의 값을 우선으로 사용합니다. (즉, 좌측 객체 값 우선, 우측은 보충만)

사용 예시:

const left = { name: 'fred', age: 10, contact: { email: 'moo@example.com' } };
const right = { age: 40, contact: { email: 'baa@example.com' } };
R.mergeDeepLeft(left, right);
//=> { name: 'fred', age: 10, contact: { email: 'moo@example.com' } }
// left의 age(10) 유지, contact.email도 left값 유지 (right는 무시됨)

mergeDeepRight

R.mergeDeepRight는 mergeDeepLeft와 반대로, 깊은 병합 시 우측 객체 값을 우선합니다. 동일 키의 상충 시, 객체면 재귀 병합하고, 아니면 두 번째 객체(rObj)의 값을 사용합니다.

사용 예시:

const left = { name: 'fred', age: 10, contact: { email: 'moo@example.com' } };
const right = { age: 40, contact: { email: 'baa@example.com' } };
R.mergeDeepRight(left, right);
//=> { name: 'fred', age: 40, contact: { email: 'baa@example.com' } }
// age와 contact.email 모두 right의 값으로 대체됨

mergeDeepWith

R.mergeDeepWith는 깊은 병합 시 커스텀 함수를 사용하여 충돌 값을 병합합니다. 첫 번째 인자로 병합 함수 fn, 다음 두 인자로 대상 객체 lObj와 rObj를 받습니다. 두 객체를 재귀 병합하되, 같은 키의 원시값(객체 아닌 값)이 충돌하면 fn(leftValue, rightValue)의 결과를 사용합니다. 객체 값이면 재귀 내려가 병합합니다.

예를 들어, 리스트 값을 병합할 때 concat 함수를 쓰거나, 숫자 값을 합산하도록 정의할 수 있습니다.

사용 예시:

const left = { a: true, c: { values: [10, 20] } };
const right = { b: true, c: { values: [15, 35] } };
const concatValues = (l, r) => R.concat(l, r);

R.mergeDeepWith(concatValues, left, right);
//=> { 
//  a: true, 
//  b: true,
//  c: { values: [10, 20, 15, 35] } 
//}
// c.values 배열이 concat으로 합쳐짐

mergeDeepWithKey

R.mergeDeepWithKey는 mergeDeepWith와 유사하지만, 병합 함수에 키 정보까지 제공합니다. 첫 번째 인자로 (key, leftVal, rightVal) -> result 형식의 함수 fn을 받아, 충돌 시 해당 키와 두 값을 인자로 이 함수를 호출해 결정된 값을 사용합니다.

예를 들어, 특정 키 ‘values’일 때만 concat하고, 나머지는 우측 사용 등의 세부 로직이 가능해집니다.

사용 예시:

const concatWhenValues = (key, l, r) => key === 'values' ? R.concat(l, r) : r;
const left = { flag: true, data: { values: [10, 20] } };
const right = { flag: false, data: { values: [15, 35] } };

R.mergeDeepWithKey(concatWhenValues, left, right);
//=> {
//   flag: false,  // 'flag' 키는 r 우선
//   data: { values: [10, 20, 15, 35] }  // 'values' 키는 concat
// }

mergeLeft

R.mergeLeft는 두 객체를 얕게 병합하되, 좌측 객체의 동일 키 값을 우선 유지합니다. 첫 번째 인자로 left 객체, 두 번째 인자로 right 객체를 받아 새로운 객체를 반환합니다. 충돌 시 left의 값을 쓰고, left에 없고 right에 있는 키만 추가됩니다.

사용 예시:

R.mergeLeft({ age: 10, name: 'fred' }, { age: 40, role: 'admin' });
//=> { age: 10, name: 'fred', role: 'admin' }
// age는 left값 유지(10), role은 right에서 추가

mergeRight

R.mergeRight는 mergeLeft와 반대로, 얕은 병합 시 우측 객체의 값을 우선 사용합니다. 첫 번째 인자로 left, 두 번째로 right 받아, 동일 키 있으면 right 값, 없으면 left 값 유지로 병합합니다.

사용 예시:

R.mergeRight({ name: 'fred', age: 10 }, { age: 40, role: 'admin' });
//=> { name: 'fred', age: 40, role: 'admin' }
// age는 right값(40)으로 덮어씀

mergeWith

R.mergeWith는 얕은 병합 시 충돌 값들을 주어진 함수로 결합합니다. 첫 번째 인자로 결합 함수 fn(leftVal, rightVal), 두 객체를 받아 동작합니다. 같은 키가 두 객체 모두에 있고 그 값이 모두 객체가 아니면 fn(left, right) 결과를 사용하며, 그 외 키는 일반적으로 병합됩니다. (객체 값일 땐 병합 안 하고 right 우선 – deep merge하지 않음)

사용 예시:

const concatVals = (l, r) => Array.isArray(l) && Array.isArray(r) ? l.concat(r) : r;
R.mergeWith(concatVals, 
  { things: ['a'], flag: true }, 
  { things: ['b', 'c'], flag: false, extra: 1 });
//=> { things: ['a', 'b', 'c'], flag: false, extra: 1 }
// things 배열은 concat, flag는 concatVals에서 배열 아니므로 right 값, extra는 right 추가

mergeWithKey

R.mergeWithKey는 mergeWith와 유사하지만, 결합 함수에 키 이름까지 제공합니다. 인자로 (key, leftVal, rightVal) -> result 함수와 두 객체를 받아, 충돌 시 함수 결과로 값을 결정합니다. (얕은 병합)

사용 예시:

const combine = (key, l, r) => key === 'scores' ? R.concat(l, r) : r;
const left = { name: 'Alice', scores: [10, 20] };
const right = { name: 'Bob', scores: [15] };
R.mergeWithKey(combine, left, right);
//=> { name: 'Bob', scores: [10, 20, 15] }
// name은 key !== 'scores'이므로 right 사용, scores는 key === 'scores'이므로 concat

min

R.min는 두 값 중 작은 값을 반환합니다. 비교 방식은 R.gt와 반대로 작음 여부를 판단합니다.

사용 예시:

R.min(5, 3);    //=> 3
R.min('apple', 'banana'); //=> 'apple'  (문자열 비교 상 'apple' 이 작음)

minBy

R.minBy는 커스텀 함수의 결과를 기준으로 작은 쪽의 원본 값을 반환합니다. R.maxBy와 반대 역할입니다.

사용 예시:

const square = x => x * x;
R.minBy(square, -3, 2); //=> 2  (-3 vs 2의 제곱 비교: 9 vs 4, 2의 제곱이 더 작으므로 2 반환)

modify

R.modify는 객체의 특정 속성 값을 주어진 함수로 변환하여 새로운 객체를 반환합니다. 첫 번째 인자로 속성 이름 prop, 두 번째로 변환 함수 fn, 세 번째로 객체 obj를 받습니다. 결과는 obj의 prop 값에 fn을 적용한 값을 갖는 얕은 복사 객체입니다. 해당 속성이 없으면 객체를 변경하지 않고 그대로 반환합니다.

사용 예시:

const person = { name: 'James', age: 20, pets: ['dog', 'cat'] };
R.modify('age', R.inc, person);
//=> { name: 'James', age: 21, pets: ['dog', 'cat'] }

R.modify('pets', R.append('turtle'), person);
//=> { name: 'James', age: 20, pets: ['dog', 'cat', 'turtle'] }

modifyPath

R.modifyPath는 중첩된 경로의 값에 변환 함수를 적용합니다. 첫 번째 인자로 경로 배열 path, 두 번째로 함수 fn, 세 번째로 객체 obj를 받아, obj의 해당 경로 값에 fn을 적용한 결과를 설정한 얕은 복사본을 반환합니다. 경로에 해당 값이 없으면 원본 객체를 변경하지 않고 반환합니다.

사용 예시:

const person = { name: 'Tony', address: { zipCode: '90216' } };
R.modifyPath(['address', 'zipCode'], R.reverse, person);
//=> { name: 'Tony', address: { zipCode: '61209' } }

const nested = { arr: [{ val: 5 }] };
R.modifyPath(['arr', 0, 'val'], x => x * 2, nested);
//=> { arr: [ { val: 10 } ] }

modulo

R.modulo는 JavaScript의 % 연산과 동일하게 나눗셈의 나머지를 계산합니다. 첫 번째 인자 a를 두 번째 인자 b로 나눈 나머지 (a % b)를 반환합니다. (부호 처리는 JS 방식: 결과 부호는 피제수 부호와 동일)

일반적으로 수학적 mod 연산에는 R.mathMod 권장하지만, 필요에 따라 이 함수도 사용됩니다.

사용 예시:

R.modulo(17, 3); //=> 2   (17 % 3 = 2)
R.modulo(-17, 3); //=> -2 (JS %: -17 % 3 = -2)
R.modulo(17, -3); //=> 2  (17 % -3 = 2, JS modulo 부호는 피제수 -17%3=-2, 17%-3=2)
R.modulo(42, 0); //=> NaN (0으로 나눌 수 없음)

move

R.move는 리스트에서 특정 위치의 요소를 다른 위치로 옮긴 새 리스트를 반환합니다. 첫 번째 인자 fromIndex의 요소를 꺼내, 두 번째 인자 toIndex 위치에 삽입합니다. 나머지 요소들의 상대 순서는 유지됩니다. 음수 인덱스는 리스트 끝에서부터로 간주합니다.

예를 들어, 배열에서 항목 순서를 변경하는데 사용할 수 있습니다.

사용 예시:

R.move(0, 2, ['a', 'b', 'c', 'd', 'e']); 
//=> ['b', 'c', 'a', 'd', 'e']  ('a'를 인덱스 2로 이동)

R.move(-1, 0, ['a', 'b', 'c']); 
//=> ['c', 'a', 'b']  (마지막 요소 'c'를 맨 앞으로 이동)

multiply

R.multiply는 두 숫자의 곱을 반환합니다. R.add처럼 커리 함수로, 한 번에 두 인자를 다 받거나, 하나만 받아 부분 적용할 수 있습니다.

사용 예시:

R.multiply(2, 5); //=> 10
const double = R.multiply(2);
double(5);        //=> 10

nAry

R.nAry는 함수를 받아 정확히 N개의 인자만 받는 새 함수로 변환합니다. 첫 번째 인자로 원하는 아리티 n, 두 번째 인자로 함수 fn을 받아, 반환 함수는 fn을 호출하되 항상 인자 리스트를 잘라 n개까지만 전달합니다. 이 함수로 반환된 함수의 length가 n으로 지정됩니다.

주로 R.map처럼 콜백에 불필요한 추가 인자가 들어오는 경우 이를 무시하기 위해 사용됩니다.

사용 예시:

const takesTwoArgs = (x, y) => [x, y];
takesTwoArgs.length; //=> 2

const unaryVersion = R.nAry(1, takesTwoArgs);
unaryVersion.length; //=> 1
unaryVersion('a', 'b', 'c'); //=> ['a', undefined]  (두 번째 인자까지만 받고 나머지는 무시)

negate

R.negate는 숫자의 부호를 반전(음수->양수, 양수->음수) 합니다. 단항 함수로, 입력 숫자 n의 음수를 반환 (-n). (Boolean에는 적용 말 것)

사용 예시:

R.negate(42);  //=> -42
R.negate(-5);  //=> 5
R.map(R.negate, [1, -2, 0]); //=> [-1, 2, 0]

none

R.none는 R.all의 반대로, 리스트의 모든 요소가 조건을 만족하지 않을 때만 true를 반환합니다. 판정 함수 predicate와 리스트를 받아, 하나라도 predicate가 true이면 결과는 false이고, 모두 false여야 true를 반환합니다.

예를 들어, 숫자 배열에 홀수가 전혀 없는지 확인하려면 R.none(isOdd, arr)를 사용할 수 있습니다.

사용 예시:

const isEven = x => x % 2 === 0;
R.none(isEven, [1, 3, 5]);      //=> true  (하나도 짝수가 없음)
R.none(isEven, [1, 2, 3]);      //=> false (짝수 2가 하나 있음)
R.none(R.isEmpty, ['a', '', 'c']); //=> false ('' 빈 문자열이 있어서 none 조건 불만족)

not

R.not는 논리 부정(NOT)을 수행하는 함수입니다. 단일 인자를 받아 ! 연산을 적용한 boolean 결과를 돌려줍니다. truthy 값이면 false, falsy 값이면 true를 반환합니다.

사용 예시:

R.not(true);    //=> false
R.not(false);   //=> true
R.not(0);       //=> true  (0은 falsy)
R.not(1);       //=> false (1은 truthy)

nth

R.nth는 리스트(또는 문자열)에서 주어진 인덱스의 요소를 반환합니다. 인덱스 n과 대상 리스트를 받아, 해당 인덱스의 값을 돌려줍니다. 음수 인덱스는 끝에서부터 센 위치로 간주합니다 (-1은 마지막 요소). 범위를 벗어나면 undefined입니다.

사용 예시:

R.nth(1, ['a', 'b', 'c']);  //=> 'b'  (인덱스 1은 두 번째 요소)
R.nth(-1, ['a', 'b', 'c']); //=> 'c'  (음수 -1은 마지막 요소)
R.nth(5, ['a', 'b', 'c']);  //=> undefined (없음)

R.nth(2, 'Ramda');          //=> 'm' (문자열에서도 가능)

nthArg

R.nthArg는 자신에게 주어진 인자 중 n번째를 반환하는 함수를 생성합니다. 인자로 인덱스 n을 주면, 반환되는 새로운 함수는 어떤 인자들을 받아 그 중 n번째 (0부터) 인자를 결과로 돌려줍니다. 음수 인덱스도 지원되어, -1이면 마지막 인자를 반환합니다.

이 함수는 입력 중 특정 위치의 값을 선택해야 할 때 유용합니다.

사용 예시:

const getSecondArg = R.nthArg(1);
getSecondArg('a', 'b', 'c'); //=> 'b'

const getLastArg = R.nthArg(-1);
getLastArg(1, 2, 3, 4);      //=> 4

o

R.o는 두 단항 함수를 합성(composition)하여 하나의 단항 함수를 만드는 함수입니다. 수학 기호 ∘ (오)를 따온 것으로, R.o(f, g)는 x => f(g(x))와 동일한 합성 함수를 반환합니다. R.compose와 유사하지만 정확히 두 함수만 합성하며, 결과 함수는 단항 함수입니다.

R.o는 compose를 사용할 정도로 복잡하지 않은 두 함수 합성 시 가독성을 높이기 위해 제공됩니다.

사용 예시:

const shoutName = R.o(R.toUpper, name => `The name's ${name.last}, ${name.first} ${name.last}`);
shoutName({ first: 'James', last: 'Bond' });
//=> "THE NAME'S BOND, JAMES BOND"

R.o(Math.abs, R.add(1))(-5); 
//=> 4  (설명: 먼저 R.add(1)(-5) = -4, Math.abs(-4) = 4)

objOf

R.objOf는 주어진 키와 값을 사용해 하나의 속성만 가지는 객체를 생성합니다. 첫 번째 인자로 키 이름 key, 두 번째 인자로 값 val을 받아 { [key]: val } 형태의 객체를 돌려줍니다.

이는 어떤 값을 특정 키로 객체에 담아야 할 때 간편하게 사용됩니다.

사용 예시:

R.objOf('age', 30); 
//=> { age: 30 }

const objFromName = R.compose(R.objOf('name'), R.toUpper);
objFromName('alice'); 
//=> { name: 'ALICE' }  (소문자 'alice'를 대문자로 변환 후 name 키에 할당)

of

R.of는 값을 한 단계 higher-order 구조로 감싸는 함수입니다. 구체적으로:

  • 값과 배열 생성자를 주면 그 값을 배열에 넣어 반환.

  • 값과 Promise 생성자를 주면 Promise.resolve로 감싼 Promise 반환.

  • 즉, Applicative Functor의 of를 호출합니다.

기본 사용으로 R.of(Array, val)은 [val]과 같습니다. 특별히 array 외의 경우 R.of가 래핑 동작을 하며, ex: R.of(Maybe, x) -> Maybe.Just(x) (Ramda fantasy-land 규약).

사용 예시:

R.of(Array, 42);      //=> [42]    (값을 배열로)
R.of(Array, [42]);    //=> [[42]]  (배열을 한 겹 더 배열로)
R.of(Promise, 42).then(console.log); // Logs: 42  (42를 즉시 resolve하는 Promise)

omit

R.omit은 객체에서 특정 키 목록을 생략(삭제)한 새 객체를 반환합니다. 첫 번째 인자로 키 배열 names, 두 번째 인자로 객체 obj를 받아, obj에서 names에 포함된 모든 속성을 제거한 얕은 복사 객체를 돌려줍니다.

예를 들어, 개인정보 객체에서 민감한 필드들(password 등)을 제거하는데 쓰일 수 있습니다.

사용 예시:

R.omit(['password', 'token'], { name: 'Alice', password: '1234', token: 'abcd' });
//=> { name: 'Alice' }

R.omit(['x', 'y'], { x: 1, y: 2, z: 3 });
//=> { z: 3 }

on

R.on은 두 함수를 결합하여 새로운 이항 함수를 생성합니다. 첫 번째 인자로 이항 함수 combine, 두 번째 인자로 단항 함수 unary, 그리고 반환 함수는 두 인자를 받아 combine(unary(a), unary(b))를 반환합니다.

즉, 두 인자를 먼저 unary로 각각 변환하고, 그 결과를 combine 함수로 합칩니다. 이 패턴은 집합 비교나 정렬 함수 생성 등에 흔히 쓰입니다 (ex: R.on(R.equals, f) -> x,y 비교 시 f(x) === f(y)).

사용 예시:

// 두 사람 객체를 이름 소문자로 비교하여 동일 여부 판단
const eqByNameCaseInsens = R.on((a, b) => a === b, R.pipe(R.prop('name'), R.toLower));
const person1 = { name: 'ALICE' };
const person2 = { name: 'alice' };
eqByNameCaseInsens(person1, person2); //=> true

// includes를 대소문자 무시하여 사용
const includesCaseInsensitive = R.on(R.includes, R.toLower);
includesCaseInsensitive('o', 'FOO');  //=> true

once

R.once는 주어진 함수를 단 한 번만 실행되는 함수로 만들어줍니다. 반환된 함수는 첫 호출 시 fn을 실행해 결과를 반환하며, 이후 호출부터는 항상 처음 결과를 그대로 반환하고 fn은 다시 실행되지 않습니다. (캐시됨)

이 함수는 초기화나 설정 함수 등 부수 효과를 한 번만 해야 할 때 사용합니다.

사용 예시:

let count = 0;
const incrementOnce = R.once(() => ++count);
incrementOnce(); // 실행되어 count = 1 반환 1
incrementOnce(); // 더 이상 실행되지 않음, 이전 결과 1 반환
console.log(count); //=> 1 (두 번째 호출에서 증가 안 함)

or

R.or는 논리 OR 연산을 수행합니다. R.and와 비슷하게, 두 인자를 받아 첫 번째가 truthy이면 첫 번째 인자를 바로 반환하고, 그렇지 않으면 두 번째 인자를 반환합니다. 두 인자가 boolean일 경우 OR 결과와 같습니다.

사용 예시:

R.or(true, false);   //=> true
R.or(false, false);  //=> false
R.or(0, 'Hello');    //=> 'Hello' (0은 falsy, 두 번째 반환)
R.or(42, 'Hello');   //=> 42 (첫 인자가 truthy이므로 그대로)

otherwise

R.otherwise는 R.andThen의 실패(거부) 버전으로, Promise가 reject된 경우 대신 실행할 함수를 지정할 수 있게 합니다. 첫 번째 인자로 실패 처리 함수 onFailure, 두 번째 인자로 Promise를 받아, 그 Promise에 에러 핸들러 체인을 연결한 새 Promise를 반환합니다 (성공시 그대로 통과, 실패시 onFailure 실행 결과로 성공 처리).

p.then(null, onFailure)와 유사하며, catch와 비슷합니다. R.andThen과 함께 Promise 흐름 제어에 사용합니다.

사용 예시:

const fetchData = id => id > 0 
  ? Promise.resolve({ id, name: 'Bob' }) 
  : Promise.reject('Invalid ID');
const useDefault = () => ({ name: 'Default User' });

const getNameSafely = R.pipe(
  fetchData,
  R.otherwise(useDefault),            // 실패하면 기본 객체 반환
  R.andThen(R.prop('name'))           // 성공 객체에서 name 추출
);

getNameSafely(-1).then(console.log);  // 'Default User'
getNameSafely(3).then(console.log);   // 'Bob'

over

R.over는 렌즈를 사용하여 구조 내의 값을 주어진 함수를 통해 변환합니다. 첫 번째 인자로 렌즈 lens, 두 번째 인자로 변환 함수 fn, 세 번째 인자로 대상 값 target을 받아, lens가 가리키는 부분의 값을 fn으로 바꾸고 나머지는 동일한 새 구조를 반환합니다.

예를 들어, 객체의 특정 속성 숫자를 두 배로 늘리거나, 배열의 특정 위치 문자열을 변경하는 등 lens와 함께 사용합니다.

사용 예시:

const headLens = R.lensIndex(0);
R.over(headLens, R.toUpper, ['foo', 'bar', 'baz']);
//=> ['FOO', 'bar', 'baz']

const xLens = R.lensProp('x');
R.over(xLens, R.negate, { x: 5, y: 10 });
//=> { x: -5, y: 10 }

pair

R.pair는 두 값을 튜플(2요소 배열)로 묶어 반환합니다. 첫 번째 인자 fst, 두 번째 인자 snd를 받아 [fst, snd] 배열을 반환하는 간단한 함수입니다.

사용 예시:

R.pair('foo', 'bar'); //=> ['foo', 'bar']

const toPairWith = R.pair(R.__, 'end');
toPairWith('start');  //=> ['start', 'end']

partial

R.partial은 함수에 일부 인자를 미리 채워 새로운 부분 적용 함수를 만들어줍니다. 첫 번째 인자로 함수 fn을 받고, 두 번째 인자로 미리 채울 인자 배열 args를 받습니다. 반환되는 함수는 나머지 인자를 받아 fn에 결합된 인자 순으로 모두 전달해 실행합니다.

이것은 Function.prototype.bind로 인자 바인딩하는 것과 비슷하나, this 컨텍스트를 고려하지 않습니다.

사용 예시:

const multiply = (a, b) => a * b;
const double = R.partial(multiply, [2]);
double(5); //=> 10  (2 * 5)

const greet = (salutation, title, firstName, lastName) => 
  `${salutation}, ${title} ${firstName} ${lastName}!`;
const sayHello = R.partial(greet, ['Hello']);
const sayHelloToMs = R.partial(sayHello, ['Ms.']);
sayHelloToMs('Jane', 'Jones'); 
//=> 'Hello, Ms. Jane Jones!'

partialObject

R.partialObject는 객체를 인자로 받는 함수에 대해, 그 객체의 일부 프로퍼티를 미리 채워 새로운 함수를 생성합니다. 첫 번째 인자로 함수 fn(obj)를 받고, 두 번째 인자로 부분 객체 props를 받습니다. 반환 함수 g(obj2)는 내부적으로 fn({ ...props, ...obj2 })를 호출하여 두 객체를 병합한 것을 인자로 사용하는 함수입니다.

이 함수는 partial의 객체 전용 버전으로, 복잡한 옵션 객체 중 일부만 고정하고 나머지를 나중에 받을 때 유용합니다.

사용 예시:

const add = ({ a, b }) => a + b;
const addTwo = R.partialObject(add, { a: 2 });
addTwo({ b: 3 }); //=> 5  (a=2 이미 설정, b=3 받아 수행)

const greet = ({ salutation, title, firstName, lastName }) =>
  `${salutation}, ${title} ${firstName} ${lastName}!`;
const sayHello = R.partialObject(greet, { salutation: 'Hello' });
const sayHelloToMs = R.partialObject(sayHello, { title: 'Ms.' });
sayHelloToMs({ firstName: 'Jane', lastName: 'Jones' });
//=> 'Hello, Ms. Jane Jones!'

partition

R.partition은 리스트를 주어진 조건에 맞는 요소들과 그렇지 않은 요소들로 분리합니다. 첫 번째 인자로 판정 함수 predicate, 두 번째 인자로 리스트를 받아, [ passList, failList ] 형태의 2차원 배열을 반환합니다. passList는 predicate가 true인 요소들의 배열, failList는 false인 요소들의 배열입니다. 객체에 사용하면, true인 속성의 부분 객체와 아닌 속성의 부분 객체로 두 객체를 배열로 반환합니다.

사용 예시:

const hasS = R.includes('s');
R.partition(hasS, ['sss', 'ttt', 'foo', 'bars']);
//=> [ ['sss', 'bars'], ['ttt', 'foo'] ]

R.partition(hasS, { a: 'sss', b: 'ttt', foo: 'bars' });
//=> [ { a: 'sss', foo: 'bars' }, { b: 'ttt' } ]

path

R.path는 객체의 중첩 경로에 위치한 값을 안전하게 추출합니다. 첫 번째 인자로 경로 배열 pathArr, 두 번째 인자로 객체 obj를 받아, obj의 해당 경로 값을 반환합니다. 경로를 따라 값이 존재하지 않으면 undefined를 반환합니다.

이 함수는 중첩된 값에 접근할 때 obj?.a?.b 같은 체이닝을 대신하며 안전성을 높입니다.

사용 예시:

const user = { address: { city: 'Seoul', zip: 12345 } };
R.path(['address', 'city'], user);    //=> 'Seoul'
R.path(['address', 'street'], user);  //=> undefined (없는 경로)

R.path([2, 'name'], [null, {}, { name: 'Alice' }]);
//=> 'Alice'  (배열 인덱스 2의 객체에서 name)

pathEq

R.pathEq는 객체의 특정 경로에 있는 값이 주어진 값과 R.equals로 일치하는지 검사합니다. 첫 번째 인자로 기대 값 val, 두 번째 인자로 경로 배열 pathArr, 세 번째 인자로 객체 obj를 받아 불리언을 반환합니다. 경로에 값이 없으면 false입니다.

사용 예시:

const user1 = { address: { zip: 90210 } };
const user2 = { address: { zip: 55555 } };
R.pathEq(90210, ['address', 'zip'], user1); //=> true
R.pathEq(90210, ['address', 'zip'], user2); //=> false

pathOr

R.pathOr는 객체의 중첩 경로에 값이 없을 경우 기본값을 대신 반환합니다. 첫 번째 인자로 기본값 defaultVal, 두 번째 인자로 경로 pathArr, 세 번째 인자로 객체 obj를 받아, R.path(pathArr, obj)가 undefined 또는 null (Ramda v0.26부터 NaN도)인 경우 defaultVal을 반환하고, 값이 존재하면 그 값을 반환합니다.

사용 예시:

const user = { name: 'ALICE', age: 101 };
R.pathOr('N/A', ['favoriteLibrary'], user); //=> 'N/A' (없는 경로)
R.pathOr('N/A', ['name'], user);           //=> 'ALICE' (존재하는 경로는 그대로)

const getCountry = R.pathOr('Unknown', ['address', 'country']);
getCountry({ address: { country: 'Korea' } }); //=> 'Korea'
getCountry({}); //=> 'Unknown'

paths

R.paths는 여러 경로를 한 번에 조회합니다. 첫 번째 인자로 경로들의 배열 (예: [ ['a','b'], ['x',0] ]), 두 번째 인자로 객체를 받아, 각 경로에 해당하는 값을 모은 배열을 반환합니다. 각 경로에 값이 없으면 undefined로 채웁니다.

사용 예시:

const obj = { a: { b: 2 }, p: [ { q: 3 } ] };
R.paths([['a','b'], ['p', 0, 'q']], obj);
//=> [2, 3]

R.paths([['a','c'], ['p','r']], obj);
//=> [undefined, undefined]  (없는 경로들)

pathSatisfies

R.pathSatisfies는 객체의 특정 경로에 위치한 값이 주어진 조건을 만족하는지 검사합니다. 첫 번째 인자로 판정 함수 predicate, 두 번째로 경로 배열 pathArr, 세 번째로 객체를 받아, 경로의 값이 존재하고 predicate(value)가 true면 true, 아니면 false를 반환합니다.

사용 예시:

const obj = { x: { y: 10 } };
R.pathSatisfies(n => n > 0, ['x','y'], obj);    //=> true  (값 10 > 0 만족)
R.pathSatisfies(n => n < 0, ['x','y'], obj);    //=> false
R.pathSatisfies(n => n > 0, ['x','z'], obj);    //=> false (경로값 없음)

pick

R.pick은 객체에서 특정 키 목록의 값만 추출하여 새 객체를 생성합니다. 첫 번째 인자로 키 배열 keys, 두 번째 인자로 객체 obj를 받아, obj의 해당 키들만 갖는 객체를 반환합니다. 없는 키는 무시합니다.

사용 예시:

const user = { name: 'Alice', age: 25, role: 'admin' };
R.pick(['name', 'role'], user);
//=> { name: 'Alice', role: 'admin' }

R.pick(['title'], user);
//=> {}  (없는 키는 무시, 빈 객체)

pickAll

R.pickAll은 pick과 유사하지만, 없는 키에 대해서도 undefined 값을 포함하여 반환합니다. 첫 번째 인자로 키 배열, 두 번째로 객체를 받아, 모든 지정된 키를 결과 객체에 만들며, obj에 없던 키는 값이 undefined로 설정됩니다.

사용 예시:

const user = { name: 'Bob', age: 30 };
R.pickAll(['name', 'gender', 'age'], user);
//=> { name: 'Bob', gender: undefined, age: 30 }

pickBy

R.pickBy는 주어진 판정 함수에 따라 객체의 속성들을 선택합니다. 첫 번째 인자로 (value, key) -> boolean 형태의 함수 predicate, 두 번째 인자로 객체를 받습니다. 결과는 predicate(value, key)가 true인 속성만 남긴 객체입니다.

예를 들어, 값이 truthy한 속성만 남기거나, 키 이름이 대문자인 속성만 추출할 수 있습니다.

사용 예시:

const data = { a: 1, b: '2', C: 3, d: 0 };
const isUpperCaseKey = (val, key) => key.toUpperCase() === key;
R.pickBy(x => !!x, data);
//=> { a: 1, b: '2', C: 3 }  (falsy 값 d:0 제거)
R.pickBy(isUpperCaseKey, data);
//=> { C: 3 }  (대문자 키 'C'만 선택)

pipe

R.pipe는 여러 함수를 왼쪽에서 오른쪽 순서로 합성합니다. 인자로 나열된 함수들을 받아, 반환된 합성 함수는 첫 번째 함수는 입력을 받고, 그 결과를 두 번째 함수로 넘겨… 마지막 함수 결과를 반환합니다. R.compose와 방향만 반대입니다.

이 함수는 함수형 파이프라인을 만들 때 핵심으로 사용되며, 특히 R.map, R.filter 등을 연결해 데이터를 변환하는데 유용합니다. 결과 함수의 arity는 첫 함수의 arity와 동일합니다. (첫 함수 이외는 모두 단항이어야 함)

사용 예시:

const greet = name => `Hi, ${name}`;
const exclaim = sentence => `${sentence}!`;
const welcome = R.pipe(greet, R.toUpper, exclaim);

welcome('Alice'); //=> 'HI, ALICE!'

pipeWith

R.pipeWith는 pipe와 유사하지만, 함수 간의 연결에 사용자 지정 변형 함수를 끼워 넣을 수 있습니다. 첫 번째 인자로 변형 함수 transformer, 두 번째 인자로 함수들의 배열 fns를 받습니다. 함수 합성은 왼쪽->오른쪽으로 진행하며, 각 단계에서 transformer(currentFn, intermediateResult)를 호출해, 결과가 null 또는 undefined 등 특정 조건이면 합성을 중단하거나 다른 처리를 할 수 있습니다.

예를 들어, 중간 결과가 null이면 이후 과정을 생략하는 pipeline 등을 구현 가능합니다.

사용 예시:

const pipeWhileNotNil = R.pipeWith((fn, result) => R.isNil(result) ? result : fn(result));
const safeProcess = pipeWhileNotNil([R.prop('value'), parseInt, x => x * 10]);

safeProcess({ value: '42' }); //=> 420  (정상 처리)
safeProcess({});             //=> undefined (R.prop('value') 결과 undefined, 이후 중단)

pluck

R.pluck는 리스트의 각 요소가 객체일 때, 지정한 키의 값만 추출하여 새로운 리스트를 반환합니다. 첫 번째 인자로 키 key, 두 번째 인자로 객체들의 배열을 받습니다. 결과는 각 객체의 key 속성 값들로 구성된 배열입니다. R.map(R.prop(key), list)와 동일한 기능입니다.

사용 예시:

const users = [{ name: 'Fred', age: 29 }, { name: 'Wilma', age: 27 }];
R.pluck('age', users); //=> [29, 27]

R.pluck(0, [[1, 2], [3, 4]]); //=> [1, 3]  (배열의 0번째 요소 추출)
R.pluck('val', { a: { val: 3 }, b: { val: 5 } }); 
//=> { a: 3, b: 5 }  (객체에 쓰면 R.map과 같이 동작)

prepend

R.prepend는 요소를 리스트의 맨 앞에 추가한 새 리스트를 반환합니다. 첫 번째 인자로 추가할 요소 el, 두 번째 인자로 리스트를 받아 [el, ...list]를 반환합니다. (R.append의 앞쪽 버전)

사용 예시:

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

R.prepend(1, []); 
//=> [1]

product

R.product는 숫자 배열의 곱을 계산합니다. 빈 배열에 적용하면 1 (곱의 항등원)을 반환합니다. 이 함수는 R.reduce(R.multiply, 1, list)와 동일합니다.

사용 예시:

R.product([2, 4, 6]);   //=> 48 (2 * 4 * 6)
R.product([]);          //=> 1  (빈 배열은 기본값 1)

project

R.project는 객체 배열에서 지정한 키들만 추출한 새로운 객체 배열을 반환합니다. 첫 번째 인자로 키 배열 keys, 두 번째 인자로 객체들의 리스트를 받아, 각 객체를 R.pick(keys) 한 결과들의 배열을 돌려줍니다.

이 함수는 SQL의 SELECT 문과 비슷하게, 레코드 리스트에서 필요한 컬럼만 뽑아내는 데 사용합니다.

사용 예시:

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

promap

R.promap는 프로펌터(Profunctor)의 promap 기능을 구현한 함수로, 간단히 말해 입력 변환 함수와 출력 변환 함수를 사용해 기존 함수를 변환합니다. 첫 번째 인자로 전처리 함수 f, 두 번째 인자로 후처리 함수 g, 세 번째 인자로 대상 함수 h를 받아, 반환되는 함수는 입력을 f로 처리한 후 h에 넣고, 결과를 g로 처리하여 반환합니다.

R.promap(f, g, h)(x)는 g(h(f(x)))와 같습니다.

사용 예시:

const decodeChar = R.promap(c => c.charCodeAt(0), String.fromCharCode, R.add(-8));
decodeChar('z'); //=> 'r'  ('z'의 코드 122 -> -8 => 114 -> 문자 'r')

const decodeString = R.promap(R.split(''), R.join(''), R.map(decodeChar));
decodeString('ziuli'); //=> 'ramda'

prop

R.prop는 객체의 특정 속성 값을 반환합니다. 첫 번째 인자로 키 이름 propName, 두 번째 인자로 객체를 받아, obj[propName] 값을 돌려줍니다. 커리 함수라서 부분 적용 가능하며, 없는 속성이면 undefined를 반환합니다.

사용 예시:

R.prop('x', { x: 100, y: 200 }); //=> 100
R.prop('name', {});             //=> undefined

const getId = R.prop('id');
getId({ id: 42, name: 'Alice' }); //=> 42

propEq

R.propEq는 객체의 특정 속성 값이 주어진 값과 R.equals로 일치하는지 검사합니다. R.pathEq의 한 단계 버전으로 볼 수 있습니다. 첫 번째 인자로 속성 이름 propName, 두 번째 인자로 기대 값 val, 세 번째 인자로 객체를 받아 boolean을 반환합니다.

사용 예시:

const person = { name: 'Bob', age: 25 };
R.propEq('age', 25, person);    //=> true
R.propEq('name', 'Alice', person); //=> false

const isAdmin = R.propEq('role', 'ADMIN');
isAdmin({ user: 'joe', role: 'ADMIN' }); //=> true

propIs

R.propIs는 객체의 특정 속성 값이 주어진 타입(생성자)인지 검사합니다. 첫 번째 인자로 타입(생성자 함수) Type, 두 번째 인자로 속성 이름 propName, 세 번째 인자로 객체를 받아, obj[propName]이 해당 Type의 인스턴스면 true를 반환합니다.

사용 예시:

R.propIs(Number, 'x', { x: 1, y: '2' });    //=> true  (x 값은 Number)
R.propIs(String, 'x', { x: 1 });           //=> false (x 값 Number, 기대 String)
R.propIs(Number, 'z', { x: 1 });           //=> false (z 값 없음 -> undefined, Number 아님)

R.propIs(Array, 'tags', { tags: ['ramda'] }); //=> true

propOr

R.propOr는 객체의 특정 속성 값을 가져오거나 없으면 기본값을 반환합니다. 첫 번째 인자로 기본값 defaultVal, 두 번째 인자로 속성 이름 propName, 세 번째 인자로 객체를 받아, obj[propName]이 존재하고 (null/undefined가 아니면) 그 값을, 아니면 defaultVal을 돌려줍니다.

사용 예시:

const alice = { name: 'ALICE', age: 101 };
const favorite = R.prop('favoriteLibrary');
const favoriteOrRamda = R.propOr('Ramda', 'favoriteLibrary');

favorite(alice);           //=> undefined
favoriteOrRamda(alice);    //=> 'Ramda'  (favoriteLibrary 없으니 기본값)

props

R.props는 객체에서 여러 속성의 값을 배열로 추출합니다. 첫 번째 인자로 키 이름들의 배열 keys, 두 번째 인자로 객체를 받아, 객체에서 각 키에 해당하는 값들을 배열로 반환합니다. 없는 키는 undefined로 채웁니다.

사용 예시:

R.props(['x', 'y'], { x: 1, y: 2 });           //=> [1, 2]
R.props(['c', 'a'], { a: 1, b: 2 });           //=> [undefined, 1]

const fullName = R.compose(R.join(' '), R.props(['first', 'last']));
fullName({ first: 'John', last: 'Doe' }); //=> 'John Doe'

propSatisfies

R.propSatisfies는 객체의 특정 속성 값이 주어진 판정 함수를 만족하는지 검사합니다. 첫 번째 인자로 판정 함수 predicate, 두 번째 인자로 속성 이름 propName, 세 번째 인자로 객체를 받아, predicate(obj[propName]) 결과를 반환합니다. 속성값이 없으면 false입니다.

사용 예시:

R.propSatisfies(x => x > 0, 'x', { x: 1, y: 2 });  //=> true
R.propSatisfies(x => x > 0, 'x', { x: -1 });       //=> false
R.propSatisfies(x => x > 0, 'z', { x: 1 });        //=> false

range

R.range는 두 숫자 사이의 정수 범위 배열을 생성합니다. 첫 번째 인자로 시작 숫자 from (포함), 두 번째 인자로 끝 숫자 to (제외)를 받아, [from, from+1, ..., to-1] 배열을 반환합니다. from이 to 이상이면 빈 배열을 반환합니다.

사용 예시:

R.range(1, 5);   //=> [1, 2, 3, 4]
R.range(50, 53); //=> [50, 51, 52]
R.range(7, 7);   //=> []  (끝값이 시작과 같음, 결과 없음)

reduce

R.reduce는 배열을 왼쪽에서 오른쪽으로 누적 처리하여 단일 결과를 도출합니다. 첫 번째 인자로 누적 함수 reducer(acc, value) -> newAcc, 두 번째 인자로 초기 누적값 accInit, 세 번째 인자로 배열 list를 받습니다. 결과는 순회가 끝난 후의 누적값입니다.

이 함수는 내장 Array.prototype.reduce와 동일하지만, 커리되어 있고 transduce 등에 활용됩니다.

사용 예시:

const sum = R.reduce(R.add, 0, [1, 2, 3, 4]);   //=> 10
const max = R.reduce(R.max, -Infinity, [1, 3, 2]); //=> 3

const toObject = R.reduce((acc, [k,v]) => ({ ...acc, [k]: v }), {}, [['a',1],['b',2]]);
//=> { a: 1, b: 2 }  (배열의 키-값 쌍 리스트를 객체로 누적)

reduceBy

R.reduceBy는 그룹별로 reduce를 수행합니다. 첫 번째 인자로 누적 함수 reducer(acc, value), 두 번째로 초기 누적값 accInit, 세 번째로 분류 키 생성 함수 keyFn, 네 번째로 배열 list를 받습니다. 작동은 R.groupBy로 list를 그룹핑하고, 각 그룹에 reducer로 reduce를 수행한 결과를 키:누적값 형태의 객체로 반환합니다.

예를 들어, 학생 목록을 학년별 점수 합계로 집계하거나, 상품을 카테고리별 수량 합계 내는 경우 사용할 수 있습니다.

사용 예시:

const students = [
  { name: 'Ann', score: 90 },
  { name: 'Ben', score: 65 },
  { name: 'Cat', score: 80 },
  { name: 'Dan', score: 65 }
];
const toGrade = s => s.score < 70 ? 'F' : s.score < 80 ? 'C' : 'A';
const count = R.reduceBy((acc) => acc + 1, 0, toGrade, students);
//=> { A: 2, C: 1, F: 1 }  (등급별 학생 수)

reduced

R.reduced는 reduce 내부에서 조기 종료를 표시하는 wrapper를 반환합니다. R.reduced(value)로 감싸진 값은 reduce나 transduce 함수 내부에서 누적기 값으로 사용되면, 이후 연산을 바로 종료하고 value를 최종 결과로 삼도록 신호를 보냅니다.

이 함수는 transduce나 커스텀 reduce 구현에서 활용하는 고급 기능으로, Ramda의 reduceWhile 등에서 쓰입니다.

사용 예시:

const sumUntilNegative = (acc, x) => x < 0 ? R.reduced(acc) : acc + x;
R.reduce(sumUntilNegative, 0, [1, 2, 3, -5, 4]);
//=> 6  (1+2+3 = 6, -5 만나 조기 종료, 그 뒤 4는 처리 안 함)

reduceRight

R.reduceRight는 배열을 오른쪽에서 왼쪽으로 누적 처리합니다. R.reduce와 사용법은 동일하지만 처리 순서가 역순입니다. 누적 함수의 인자 순서도 달라서, reducer(value, acc) 순으로 호출됩니다 (Array.prototype.reduceRight와 같음).

주로 문자열 뒤집기나 우측 중요 연산에 사용합니다.

사용 예시:

const reversedConcat = R.reduceRight((a, acc) => acc + a, '', ['a', 'b', 'c']);
//=> 'cba'

const pairwiseSubtract = R.reduceRight((val, acc) => acc - val, 0, [1, 2, 3, 4]);
// step: (((0 - 4) - 3) - 2) - 1 = -10
//=> -10

reduceWhile

R.reduceWhile은 조건을 만족하는 동안만 reduce를 수행합니다. 첫 번째 인자로 조건 함수 predicate(acc, value) -> boolean, 두 번째로 누적 함수 reducer(acc, value), 세 번째로 초기 누적값 accInit, 네 번째로 리스트 list를 받습니다. 리스트를 순회하며, 각 요소 적용 전에 predicate(currentAcc, currentValue)가 true면 reduce를 계속, false면 조기 종료하고 현재 누적값을 반환합니다.

이 함수는 reduce 도중에 멈출 조건이 있을 때 유용합니다.

사용 예시:

const sumUntilGT10 = R.reduceWhile((acc, x) => acc <= 10, (acc, x) => acc + x, 0);
sumUntilGT10([2, 4, 3, 5, 1]); 
//=> 9  (2+4+3 = 9, 다음 5 더하면 14 > 10되므로 9에서 중단)

reject

R.reject는 filter의 반대 역할로, 주어진 조건을 만족하지 않는 요소만 남겨 반환합니다. 첫 번째 인자로 판정 함수 predicate, 두 번째 인자로 리스트나 객체를 받아, predicate가 true인 요소를 제외하고 false인 요소들로 새 리스트(또는 객체)를 만듭니다.

즉, R.reject(pred, list)는 R.filter(x => !pred(x), list)와 동일합니다.

사용 예시:

const isOdd = x => x % 2 !== 0;
R.reject(isOdd, [1, 2, 3, 4]);      //=> [2, 4]  (홀수 제외, 짝수만 남음)

const data = { a: 1, b: 2, c: 3 };
R.reject(x => x >= 2, data);       //=> { a: 1 }  (값 2 이상 속성 제외)

remove

R.remove는 리스트에서 지정한 인덱스부터 지정한 개수만큼 요소를 제거한 새 리스트를 반환합니다. 첫 번째 인자로 시작 인덱스 start, 두 번째 인자로 제거 개수 count, 세 번째 인자로 리스트를 받습니다. 결과는 원본에서 start 위치부터 count개 요소가 빠진 리스트입니다. (원본 유지)

사용 예시:

R.remove(2, 2, ['a', 'b', 'c', 'd', 'e']);
//=> ['a', 'b', 'e']  (인덱스 2부터 2개 'c','d' 제거)

R.remove(0, 1, [10, 20, 30]); //=> [20, 30]  (첫 요소 제거)
R.remove(5, 10, [1, 2, 3]);   //=> [1, 2, 3]  (인덱스 5 넘어가므로 변화 없음)

repeat

R.repeat는 주어진 값을 지정한 횟수만큼 반복하여 배열로 생성합니다. 첫 번째 인자로 값 value, 두 번째 인자로 반복 횟수 n을 받아, [value, value, ..., value] (길이 n) 배열을 반환합니다.

사용 예시:

R.repeat('hi', 3); //=> ['hi', 'hi', 'hi']
R.repeat(5, 0);    //=> []  (0번 반복은 빈 배열)

const zeroes = R.repeat(0, 5); //=> [0, 0, 0, 0, 0]

replace

R.replace는 문자열에서 검색 문자열이나 정규표현식에 대응하는 부분을 다른 문자열로 치환합니다. 첫 번째 인자로 검색 패턴 (문자열 또는 RegExp), 두 번째 인자로 교체할 문자열, 세 번째 인자로 원본 문자열을 받습니다. 결과는 바뀐 새 문자열입니다.

String.prototype.replace와 유사하지만, global regex 플래그 등도 적용 가능합니다.

사용 예시:

R.replace('foo', 'bar', 'foo foo');        //=> 'bar foo'  (첫 'foo'만 치환)
R.replace(/foo/g, 'bar', 'foo foo foo');   //=> 'bar bar bar'  (정규식 g로 모두 치환)
R.replace('x', 'X', 'xyz');                //=> 'Xyz'

reverse

R.reverse는 리스트의 순서를 반대로 뒤집은 복사본을 반환합니다. 배열이면 역순 배열을, 문자열이면 역순 문자열을 반환합니다.

사용 예시:

R.reverse([1, 2, 3]);   //=> [3, 2, 1]
R.reverse('abc');       //=> 'cba'
R.reverse([]);          //=> []

scan

R.scan은 reduce와 유사하지만 중간 누적 결과들을 모두 배열로 반환합니다 (누적의 history). 첫 번째 인자로 누적 함수 reducer(acc, value), 두 번째로 초기 누적값 accInit, 세 번째로 리스트 list를 받아, [accInitAfter1st, accAfter2nd, ..., finalAcc] 형태의 배열을 반환합니다. 이 배열 길이는 원본 길이와 같습니다.

예를 들어, 부분 합(summation)을 구하거나 중간 단계를 기록할 때 사용합니다.

사용 예시:

R.scan(R.add, 0, [1, 2, 3, 4]);
//=> [1, 3, 6, 10] 
// 설명: [0+1, 0+1+2, 0+1+2+3, 0+1+2+3+4]

R.scan((acc, x) => acc.concat(x), [], ['a', 'b', 'c']);
//=> [['a'], ['a','b'], ['a','b','c']]

sequence

R.sequence는 Traversable 인터페이스를 갖는 데이터 구조를 변환합니다. 약간 복잡한 개념으로, Applicative Functor들과 관련 있습니다. 쉽게 말해, R.sequence(Applicative, Traversable)를 하면 Traversable(Applicative) -> Applicative(Traversable)로 구조를 뒤집습니다.

일반적인 예로, 배열의 각 요소가 Promise일 때 R.sequence(Promise, arr)를 하면 Promise<배열>로 바뀌고, 배열의 모든 Promise가 resolve된 후 결과 배열을 resolve합니다.

Ramda로 (Applicative ApplicativeType.of와 Traversable traverse 필요), [Maybe a] -> Maybe [a], Result [a] -> [Result a] 등의 변환에 쓰입니다.

사용 예시:

// 예: 배열<Promise<number>> -> Promise<배열<number>>
const promises = [Promise.resolve(1), Promise.resolve(2)];
R.sequence(Promise, promises).then(console.log);
// Logs: [1, 2]

// 예: Maybe 구조 (Ramda-fantasy 등)
R.sequence(Maybe.of, [Maybe.Just(1), Maybe.Just(2)]);
//=> Maybe.Just([1,2])

set

R.set는 렌즈가 가리키는 위치의 값을 주어진 값으로 설정한 새로운 구조를 반환합니다. 첫 번째 인자로 렌즈 lens, 두 번째 인자로 새 값 val, 세 번째 인자로 대상 target을 받아, target의 lens 위치에 val이 들어간 복사본을 돌려줍니다.

예를 들어, 객체의 특정 속성을 새 값으로 바꾸거나, 배열의 특정 요소를 교체할 때 lens와 함께 씁니다.

사용 예시:

const xLens = R.lensProp('x');
R.set(xLens, 4, { x: 1, y: 2 }); //=> { x: 4, y: 2 }

const index1Lens = R.lensIndex(1);
R.set(index1Lens, 'x', ['a', 'b', 'c']); //=> ['a', 'x', 'c']

slice

R.slice는 배열이나 문자열의 일부 구간을 잘라서 반환합니다. 첫 번째 인자로 시작 인덱스 from (포함), 두 번째 인자로 끝 인덱스 to (미포함), 세 번째 인자로 대상 리스트(또는 문자열)를 받아, from부터 to-1까지의 요소로 이루어진 부분을 반환합니다. Array.prototype.slice와 같습니다.

음수 인덱스는 배열 끝에서부터의 위치로 간주합니다.

사용 예시:

R.slice(1, 3, ['a', 'b', 'c', 'd']); //=> ['b', 'c']  (인덱스 1~2)
R.slice(0, -1, ['a', 'b', 'c']);     //=> ['a', 'b']  (끝 -1은 마지막 제외)
R.slice(2, 6, [1, 2, 3, 4]);        //=> [3, 4]      (6이 길이 넘어가도 마지막까지)

R.slice(2, 5, 'Hello World');       //=> 'llo '      (문자열 슬라이스)

sort

R.sort는 배열을 주어진 비교 함수에 따라 정렬한 새로운 배열을 반환합니다. 첫 번째 인자로 비교 함수 comparator(a, b) (음수, 0, 양수 반환), 두 번째 인자로 배열을 받습니다. 결과는 원본 배열을 복사해 정렬한 것입니다 (원본 불변).

Ramda에서 흔히 R.ascend, R.descend 등을 함께 사용하여 comparator를 만듭니다.

사용 예시:

R.sort((a, b) => a - b, [3, 1, 2]); //=> [1, 2, 3]

const people = [{ name: 'Kim', age: 30 }, { name: 'Lee', age: 25 }];
const byAgeAscend = R.sort(R.ascend(R.prop('age')), people);
//=> [ { name: 'Lee', age: 25 }, { name: 'Kim', age: 30 } ]

sortBy

R.sortBy는 특정 함수를 적용한 결과를 기준으로 배열을 정렬합니다. 첫 번째 인자로 키 추출 함수 fn을 받고, 두 번째 인자로 배열을 받습니다. 배열의 각 요소를 fn에 적용한 값으로 비교하여 오름차순 정렬한 새 배열을 반환합니다.

예를 들어, 객체 리스트를 특정 속성의 크기에 따라 정렬할 때 편리합니다.

사용 예시:

const names = ['Steve', 'Alex', 'Bob'];
R.sortBy(R.length, names); 
//=> ['Bob', 'Alex', 'Steve']  (길이 기준 정렬: 3,4,5자)

const people = [{name:'Kim', age:30}, {name:'Lee', age:25}, {name:'Park', age:35}];
R.sortBy(R.prop('age'), people);
//=> [ {name:'Lee', age:25}, {name:'Kim', age:30}, {name:'Park', age:35} ]

sortWith

R.sortWith는 여러 비교 함수를 순차적으로 적용하여 정렬합니다. 첫 번째 인자로 비교 함수들의 배열 [comp1, comp2, ...], 두 번째 인자로 배열을 받아 정렬합니다. sortWith는 comp1으로 먼저 비교하고, comp1이 두 값에 대해 동등(0)인 경우 comp2로 비교, 그것도 동등이면 comp3… 이런 식으로 다단계 정렬을 수행합니다.

즉, 우선순위가 있는 복합 정렬을 구현합니다.

사용 예시:

const people = [
  { name: 'Kim', age: 30 },
  { name: 'Lee', age: 25 },
  { name: 'Lee', age: 20 }
];
// 이름 오름차순, 나이 오름차순 정렬
const byName = R.ascend(R.prop('name'));
const byAge = R.ascend(R.prop('age'));
R.sortWith([byName, byAge], people);
//=> [
//   { name: 'Kim', age: 30 },
//   { name: 'Lee', age: 20 },
//   { name: 'Lee', age: 25 }
// ]
// 설명: 'Kim' vs 'Lee' -> 'Kim' 먼저, 'Lee' 둘은 이름 같으니 나이 asc로 20,25 순

split

R.split는 문자열을 구분자(문자열 또는 정규식)로 나눠 배열을 반환합니다. 첫 번째 인자로 구분자 sep, 두 번째 인자로 대상 문자열 str을 받아 str.split(sep) 결과를 돌려줍니다.

사용 예시:

R.split('.', 'a.b.c.xyz');  //=> ['a', 'b', 'c', 'xyz']
R.split(/-|_/g, 'foo-bar_baz'); //=> ['foo', 'bar', 'baz']  (정규식 구분)
R.split('', 'RAMDA');       //=> ['R', 'A', 'M', 'D', 'A'] (빈 문자열 구분: 문자별 분할)

splitAt

R.splitAt는 리스트(또는 문자열)를 특정 인덱스에서 둘로 나눕니다. 첫 번째 인자로 인덱스 index, 두 번째 인자로 리스트를 받아, [ list.slice(0, index), list.slice(index) ] 형태의 배열을 반환합니다. index가 음수면 끝에서부터 위치를 셉니다.

사용 예시:

R.splitAt(3, [1, 2, 3, 4, 5]); //=> [[1,2,3], [4,5]]
R.splitAt(1, 'hello');        //=> ['h', 'ello']

R.splitAt(-2, ['a', 'b', 'c', 'd']); //=> [['a','b'], ['c','d']] (끝 2개와 그 앞부분으로 분할)

splitEvery

R.splitEvery는 리스트나 문자열을 일정한 크기씩 잘라 배열로 반환합니다. 첫 번째 인자로 크기 n, 두 번째 인자로 리스트(또는 문자열)를 받아, 원본을 앞에서부터 순서대로 n개씩 그룹으로 나눈 배열을 돌려줍니다. 마지막 그룹은 n보다 작을 수 있습니다.

예를 들어, 배열을 2개씩 짝지어 처리해야 하거나, 문자열을 고정 길이 청크로 나눌 때 사용합니다.

사용 예시:

R.splitEvery(2, [1, 2, 3, 4, 5]); 
//=> [[1,2], [3,4], [5]]

R.splitEvery(3, 'foobarbaz'); 
//=> ['foo', 'bar', 'baz']

splitWhen

R.splitWhen은 리스트에서 특정 조건을 처음 만족하는 지점에서 앞뒤로 나눈 배열을 반환합니다. 첫 번째 인자로 판정 함수 predicate, 두 번째 인자로 리스트를 받아, 리스트를 왼쪽부터 탐색하여 처음 predicate(element)가 true인 요소를 발견하면 그 요소를 기준으로 두 부분으로 나눕니다. 그 요소는 뒤쪽 배열의 첫 요소가 됩니다. 조건을 만족하는 요소가 없다면 전체 리스트와 빈 리스트를 반환합니다.

사용 예시:

const isNeg = x => x < 0;
R.splitWhen(isNeg, [1, 2, -3, 4, 5]);
//=> [[1, 2], [-3, 4, 5]]  (-3이 조건에 처음 true, 앞뒤로 분할)

R.splitWhen(R.equals('stop'), ['foo', 'bar', 'stop', 'baz']);
//=> [['foo', 'bar'], ['stop', 'baz']]

R.splitWhen(isNeg, [1, 2, 3]);
//=> [[1, 2, 3], []]  (조건 만족 없음)

startsWith

R.startsWith는 리스트나 문자열이 주어진 prefix로 시작하는지 검사합니다. 첫 번째 인자로 prefix (배열 혹은 문자열), 두 번째 인자로 대상 리스트(또는 문자열)를 받아 boolean을 반환합니다. 동작은 R.equals(prefix, target.slice(0, prefix.length))와 유사합니다.

사용 예시:

R.startsWith('Hello', 'Hello World'); //=> true
R.startsWith('Ram', 'Ramda');        //=> true
R.startsWith('R', 'ramda');         //=> false (대소문자 다름)

R.startsWith([1, 2], [1, 2, 3]);    //=> true
R.startsWith([1, 3], [1, 2, 3]);    //=> false

subtract

R.subtract는 두 수의 뺄셈 결과를 반환합니다. 첫 번째 인자에서 두 번째 인자를 뺀 값을 돌려주며, R.subtract(a, b) = a - b입니다.

사용 예시:

R.subtract(10, 3); //=> 7
R.subtract(3, 10); //=> -7

const minus5 = R.subtract(R.__, 5);
minus5(20);       //=> 15  (20 - 5)

sum

R.sum는 숫자 배열의 합계를 계산합니다. 빈 배열의 합은 0입니다. R.reduce(R.add, 0, list)와 같습니다.

사용 예시:

R.sum([2, 4, 6, 8]); //=> 20
R.sum([]); //=> 0

symmetricDifference

R.symmetricDifference는 두 집합의 대칭차 (각 집합에만 속하고 교집합에는 없는 원소들)를 반환합니다. 두 리스트를 받아, R.difference(list1, list2)와 R.difference(list2, list1)의 합집합을 반환합니다. 결과에는 중복이 제거되고, 순서는 첫 번째 리스트의 순서 + 두 번째 리스트 중 새롭게 추가되는 순서입니다.

사용 예시:

R.symmetricDifference([1, 2, 3], [2, 3, 4]);
//=> [1, 4]  
// (1은 첫 리스트에만, 4는 둘째 리스트에만 존재)

R.symmetricDifference([{x:1}], [{x:1}, {x:2}]);
//=> [ {x:2} ]  (객체 값 {x:1}은 양쪽에 있어 제외, {x:2}만 남음)

symmetricDifferenceWith

R.symmetricDifferenceWith는 사용자 정의 비교 함수로 대칭차를 계산합니다. 첫 번째 인자로 비교 함수 pred(a, b)를 받고, 다음 두 리스트를 받아 대칭차 결과를 반환합니다. 내부적으로, pred로 동일 판단되는 요소들을 교집합으로 간주하여 제외하고, 나머지 요소들을 반환합니다.

사용 예시:

const cmp = (a, b) => a.id === b.id;
const list1 = [ {id:1}, {id:2}, {id:3} ];
const list2 = [ {id:3}, {id:4} ];
R.symmetricDifferenceWith(cmp, list1, list2);
//=> [ {id:1}, {id:2}, {id:4} ]

T

R.T는 항상 참(true)을 반환하는 함수입니다. 어떤 인자를 주든 무시하고 true를 돌려줍니다. (R.F의 반대) 주로 조건식에서 기본 true 함수로 사용합니다.

사용 예시:

R.T();               //=> true
R.T('any', 'args');  //=> true  (무조건 true)
[1, 2, 3].every(R.T); //=> true (모든 요소에 대해 true)

tail

R.tail은 리스트나 문자열의 첫 요소를 제외한 나머지를 반환합니다. 배열의 경우 첫 요소를 뺀 후미 배열을, 문자열의 경우 첫 문자를 뺀 나머지 문자열을 반환합니다. 빈 배열(문자열)에 쓰면 빈 배열(문자열)을 반환합니다.

사용 예시:

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

R.tail('abc');      //=> 'bc'
R.tail('a');        //=> ''
R.tail('');         //=> ''

take

R.take는 리스트나 문자열의 첫 n개 요소를 취하여 반환합니다. 첫 번째 인자로 개수 n, 두 번째로 리스트를 받아, 리스트의 앞쪽 최대 n개까지를 반환합니다. n이 원본 길이보다 크면 전체를 반환합니다. 음수 n이면 빈 배열(’’)을 반환합니다.

사용 예시:

R.take(2, [10, 20, 30]); //=> [10, 20]
R.take(5, [10, 20, 30]); //=> [10, 20, 30] (넘치므로 전체 반환)
R.take(0, [10, 20]);     //=> []

R.take(3, 'Ramda');      //=> 'Ram'

takeLast

R.takeLast는 리스트나 문자열의 마지막 n개를 취하여 반환합니다. take와 반대로, 끝에서부터 최대 n개 요소를 얻습니다.

사용 예시:

R.takeLast(2, [10, 20, 30]); //=> [20, 30]
R.takeLast(5, [10, 20]);     //=> [10, 20] (전체 반환)
R.takeLast(0, ['a', 'b']);   //=> []

R.takeLast(3, 'Ramda');      //=> 'mda'

takeLastWhile

R.takeLastWhile는 리스트의 끝부분에서 주어진 조건을 만족하는 연속된 요소들을 모두 취하여 반환합니다. 판정 함수 predicate와 리스트를 받아, 리스트를 오른쪽에서 왼쪽으로 검사하며 predicate가 true인 동안의 요소들을 모두 반환합니다. 중간에 처음 false가 나오면 그 앞쪽(왼쪽)의 요소들은 포함하지 않습니다.

사용 예시:

const isNeg = x => x < 0;
R.takeLastWhile(isNeg, [1, -1, -2, -3, 4, -4]);
//=> [-1, -2, -3] 
// (끝에서부터: -4 false, 그 앞 4 false, 그 앞 -3 -2 -1 true연속 -> -1,-2,-3 반환)

R.takeLastWhile(ch => ch !== ' ', 'Hello World');
//=> 'World'  (문자열 끝에서 공백이 나올 때까지 취함)

takeWhile

R.takeWhile는 리스트의 앞부분에서 주어진 조건이 true인 연속된 요소들을 모두 취하여 반환합니다. 판정 함수 predicate와 리스트를 받아, 왼쪽부터 순회하면서 true인 동안 요소를 포함하고, 처음 false가 나오면 그 이후는 버립니다.

사용 예시:

const isPositive = x => x > 0;
R.takeWhile(isPositive, [3, 2, 1, -1, -2]);
//=> [3, 2, 1]  (처음 -1 만날 때 멈춤)

R.takeWhile(ch => ch !== ' ', 'Hello World');
//=> 'Hello'  (처음 공백 전까지 취함)

tap

R.tap는 주어진 값을 받아 특정 함수를 실행한 후 원래 값을 그대로 반환합니다. 첫 번째 인자로 실행할 함수 fn (부수효과 용도로, 인자를 받아 어떤 동작을 함), 두 번째 인자로 값 x를 받아, fn(x)를 수행하고 나서 x를 반환합니다.

주로 함수 합성 중간에 디버깅 (console.log 등)이나 side effect 삽입에 사용됩니다.

사용 예시:

R.pipe(
  R.tap(x => console.log('input:', x)),
  R.map(R.add(1)),
  R.tap(result => console.log('result:', result))
)([1, 2, 3]);
// 콘솔 출력: "input: [1,2,3]"  "result: [2,3,4]"
// 최종 반환: [2, 3, 4]

test

R.test는 정규표현식으로 문자열을 테스트합니다. 첫 번째 인자로 정규식 regex, 두 번째 인자로 문자열 str을 받아, regex.test(str)의 결과 (true/false)를 반환합니다.

사용 예시:

R.test(/^ram/, 'ramda');   //=> true  ('ram'으로 시작함)
R.test(/Ram/i, 'ramda');   //=> true  (대소문자 무시 플래그, 'Ram' 매치)
R.test(/z$/, 'ramda');     //=> false (z로 끝나지 않음)

thunkify

R.thunkify는 임의의 함수를 즉시 실행하지 않고 인자를 기억했다가 호출 시 실행하는 “뭉크(thunk)” 함수로 변환합니다. 쉽게 말해, fn(...args)를 나중에 호출하기 위해 무인자 함수로 감싸줍니다. 첫 번째 인자로 함수 fn을 받고, 반환된 함수는 ...args를 받아 또 다른 함수(인자 없음)를 반환하며, 그를 호출하면 fn(...args) 결과를 얻습니다.

이 함수는 주로 비동기 제어 (과거 Nodeback 패턴 등)에서 lazy evaluation 또는 thunk 패턴에 쓰입니다.

사용 예시:

const add = (a, b) => a + b;
const thunkAdd = R.thunkify(add);
const lazyAdd = thunkAdd(2, 3);  // 아직 실행 안 됨
typeof lazyAdd; //=> 'function'
lazyAdd();      //=> 5  (호출 시 add(2,3) 실행)

times

R.times는 0부터 n-1까지의 인덱스를 함수에 적용한 결과를 배열로 생성합니다. 첫 번째 인자로 함수 fn(index) -> value, 두 번째 인자로 횟수 n을 받아, [ fn(0), fn(1), ..., fn(n-1) ] 배열을 반환합니다.

예를 들어, 5번 반복하면서 i의 2배 값을 채운 배열을 만들 수 있습니다.

사용 예시:

R.times(R.identity, 5);   //=> [0, 1, 2, 3, 4]
R.times(x => x * 2, 4);   //=> [0, 2, 4, 6]

const randoms = R.times(Math.random, 3); //=> [0.324..., 0.715..., 0.128...] (예시)

toLower

R.toLower는 문자열을 모두 소문자로 변환합니다. (String.prototype.toLowerCase와 동일)

사용 예시:

R.toLower('RAMDA'); //=> 'ramda'
R.toLower('Hello World!'); //=> 'hello world!'

toPairs

R.toPairs는 객체를 [키, 값] 쌍의 배열로 변환합니다. 객체의 own property들을 열거하여 각 [key, value] 배열을 요소로 갖는 배열을 반환합니다. 객체의 순서 보장은 JS 사양에 준합니다.

사용 예시:

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

R.toPairs({}); //=> []

toPairsIn

R.toPairsIn은 toPairs와 유사하지만, 프로토타입 체인 상의 속성까지 포함하여 키-값 쌍을 생성합니다. for...in과 비슷하게 동작하지만 결과는 [key, value] 배열들입니다.

사용 예시:

function F() { this.x = 1; }
F.prototype.y = 2;
const f = new F();
R.toPairsIn(f); 
//=> [['x', 1], ['y', 2]]  (x는 own property, y는 prototype property 포함)

toString

R.toString은 주어진 값을 문자열 표현으로 변환합니다. 기본적으로 JavaScript의 String()이나 value의 toString() 결과와 비슷하지만, 배열이나 객체는 JSON 비슷하게 표현하고, 함수는 함수 자체의 문자열, undefined/null은 ‘undefined’/‘null’로 처리합니다.

이 함수는 주로 디버깅이나 데이터 로깅에 쓰입니다.

사용 예시:

R.toString(42);           //=> '42'
R.toString([1, 2, 3]);    //=> '[1, 2, 3]'
R.toString({ a: 1, b: 2 }); //=> '{"a": 1, "b": 2}' (정확한 포맷은 Ramda 사양)
R.toString(null);         //=> 'null'

toUpper

R.toUpper는 문자열을 모두 대문자로 변환합니다. (String.prototype.toUpperCase와 동일)

사용 예시:

R.toUpper('ramda');    //=> 'RAMDA'
R.toUpper('Hello!');   //=> 'HELLO!'

transduce

R.transduce는 트랜스듀서(transducer)를 사용하여 데이터 시퀀스를 누적 처리합니다. 인자: (1) 트랜스듀서 xf (즉, transformer 함수를 반환하는 함수), (2) 축약(누적) 함수 reducer(acc, val), (3) 초기 누적값 accInit, (4) 입력 시퀀스 list. 내부적으로 입력을 xf로 변환하면서 reducer로 누적합니다. 결과는 최종 누적값입니다.

Transducer란 map/filter 등의 조합을 하나의 변환으로 추상화한 개념으로, 효율적으로 데이터 처리를 가능케 합니다 (중간 구조 생성 없이). transduce는 이 추상화된 변환 xf를 실제 reduce과정에 적용하는 함수입니다.

간단히, R.transduce(xf, reducer, accInit, list)는 R.reduce(xf(reducer), accInit, list)와 개념적으로 유사합니다. xf는 transformer를 만들어 reducer를 개선하고, reduce가 이를 사용합니다.

사용 예시:

const numbers = [1, 2, 3, 4];
const xf = R.compose(R.map(R.add(1)), R.filter(x => x % 2 === 0));
// xf는: 모든 요소에 +1 하고, 짝수만 남기는 트랜스듀서
R.transduce(xf, R.flip(R.append), [], numbers); 
//=> [2, 4] 
// 설명: numbers를 처리: +1 => [2,3,4,5], filter even => [2,4], 그들을 []에 append 축적

// 동일 작업을 일반적으로 하면:
R.pipe(R.map(R.add(1)), R.filter(x => x % 2 === 0))(numbers);
// 결과 [2,4]지만 중간 [2,3,4,5] 배열이 생성됨, transduce는 내부적으로 스트림처리

transpose

R.transpose는 2차원 배열의 행과 열을 교환합니다. 즉, 행렬을 전치(transpose)한 배열을 반환합니다. 입력은 [ [c1r1, c2r1, ...], [c1r2, c2r2, ...], ... ] 형식이고, 출력은 [ [c1r1, c1r2, ...], [c2r1, c2r2, ...], ... ] 형식입니다. 만약 내부 배열 길이가 다르면, 짧은 열은 누락됩니다.

사용 예시:

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

R.transpose([[10, 11], [20], [], [30, 31, 32]]);
//=> [[10, 20, undefined, 30], [11, undefined, undefined, 31]]

traverse

R.traverse는 Applicative Functor 컨텍스트에서 Traversable 구조를 순회하여 그 내부 값을 Applicative로 변환합니다. 쉽게 말해, R.traverse(Applicative, fn, traversable)은 traversable의 각 요소에 fn을 적용해서 Applicative들을 얻고, 그걸 한꺼번에 Applicative of traversable로 바꿉니다.

예를 들어, 배열 내 각 값에 비동기 함수(Promise)를 적용하여 Promise<배열>로 바꾸거나, Maybe 내 여러 값 구조를 Maybe<구조>로 바꾸는 등에 쓰입니다.

Ramda traverse는 판타지랜드 규약의 Traversable을 따르는 구조와 Applicative(fantasy-land of, ap) 인터페이스가 필요한데, JavaScript 기본엔 Array, Object가 Traversable, Promise(bluebird etc)나 Ramda-fantasy Maybe/Either 등이 Applicative로 쓰입니다.

사용 예시:

// 예: 배열의 각 숫자를 Promise로 바꾸고 전체를 Promise<배열>로
const toPromise = x => Promise.resolve(x * 2);
R.traverse(Promise, toPromise, [1, 2, 3])
  .then(result => console.log(result)); // Logs: [2, 4, 6]

// 예: Maybe 구조 (Ramda-fantasy Maybe)
const justNumbers = [Maybe.Just(1), Maybe.Just(2)];
R.traverse(Maybe.of, Maybe.Just, justNumbers);
//=> Maybe.Just([Just 1, Just 2])

trim

R.trim은 문자열의 앞뒤 공백을 제거합니다. (String.prototype.trim과 동일)

사용 예시:

R.trim('  ramda  '); //=> 'ramda'
R.trim('\tHello\r\n'); //=> 'Hello'

tryCatch

R.tryCatch는 함수를 실행하고 예외 발생 시 대체 함수를 실행하는 고계함수입니다. 첫 번째 인자로 본 함수 fn, 두 번째 인자로 예외 처리 함수 fallbackFn, 나머지 인자로 optional context를 받아, 반환 함수는 fn을 호출하여 try, 에러가 throw되면 catch하여 fallbackFn을 호출한 결과를 반환합니다. fallbackFn은 error 객체와 동일 인자들을 받아 처리할 수 있습니다.

예를 들어, JSON.parse에 try-catch를 적용하여 파싱 오류 시 기본값 반환 등을 구현할 수 있습니다.

사용 예시:

const safeParse = R.tryCatch(JSON.parse, R.always({}));
safeParse('{"valid": true}'); //=> { valid: true }
safeParse('invalid JSON');    //=> {}  (파싱 실패 시 fallback 함수 always({}) 실행)

const safeDivide = R.tryCatch(
  (x, y) => x / y,
  (err, x, y) => (err instanceof Error ? Infinity : NaN)
);
safeDivide(1, 0); //=> Infinity (divide by zero -> Error -> fallback returns Infinity)

type

R.type은 값의 타입명을 문자열로 반환합니다. JavaScript의 typeof와 Object.prototype.toString.call을 조합한 듯한 함수로:

  • null -> ‘Null’

  • undefined -> ‘Undefined’

  • Object -> ‘Object’

  • Array -> ‘Array’

  • arguments -> ‘Arguments’

  • Number, String, Boolean, Function, RegExp, Map, Set, etc -> 각자 ‘Number’, ‘String’, …

  • user-defined class -> class name or ‘Object’ depending on function name.

사용 예시:

R.type({});              //=> "Object"
R.type(3);               //=> "Number"
R.type('str');           //=> "String"
R.type(null);            //=> "Null"
R.type(undefined);       //=> "Undefined"
R.type([1, 2, 3]);       //=> "Array"
R.type(() => {});        //=> "Function"
R.type(new Map());       //=> "Map"
R.type(new Date());      //=> "Date"

unapply

R.unapply는 함수가 배열 형태의 인자만 받도록 변환합니다. 첫 번째 인자로 함수 fn(...args)를 받고, 반환된 함수는 어떠한 개수의 인자를 받으면 그 인자들을 배열로 묶어 fn에 전달하여 호출합니다.

예를 들어, R.unapply(JSON.stringify)는 가변 인자를 받아 JSON으로 직렬화된 배열을 반환합니다.

이 함수는 주로, 가변 인자를 한데 모아 처리해야 하는 상황에서, 함수 합성 호환성을 높이기 위해 사용합니다.

사용 예시:

const toArray = R.unapply(R.identity);
toArray(1, 2, 3); 
//=> [1, 2, 3]  (전달된 인자들을 배열로 묶어 반환)

const sumAll = R.unapply(R.sum);
sumAll(1, 2, 3, 4); 
//=> 10  (모든 인자를 배열로 받아 R.sum 수행)

unary

R.unary는 함수를 받아 딱 1개의 인자만 전달하는 새 함수로 변환합니다. R.nAry(1, fn)과 동일합니다. 즉, 반환된 함수는 첫 번째 인자만 fn에 전달하며, 추가 인자는 무시합니다.

이 함수는 parseInt와 같이, map에 사용될 때 원치 않는 추가 인자(index 등)를 받는 경우 제대로 동작하도록 할 때 사용됩니다.

사용 예시:

['1', '2', '3'].map(parseInt);    
//=> [1, NaN, NaN]  (parseInt('2','1') 등 불필요한 인자 영향)

['1', '2', '3'].map(R.unary(parseInt));
//=> [1, 2, 3]  (unary로 parseInt를 인자 1개만 받도록)

uncurryN

R.uncurryN는 커리 함수(누적 적용)를 일반 다인수 함수로 변환합니다. 첫 번째 인자로 아리티(총 인자 수) n, 두 번째 인자로 함수 fn을 받아, 반환된 함수는 한번에 최대 n개의 인자를 받아 fn에 전달합니다 (커리를 풀어냅니다).

예를 들어, 3-커리 함수 f(a)(b)(c)를 uncurryN(3, f) 하면 f(a, b, c)로 호출 가능합니다.

사용 예시:

const addThree = a => b => c => a + b + c;
const uncurriedAdd3 = R.uncurryN(3, addThree);
uncurriedAdd3(1, 2, 3); //=> 6

uncurriedAdd3(1)(2, 3);  //=> 6 (초과된 인자 무시 없이 잘 적용)

unfold

R.unfold는 전개 함수와 초기 시드를 사용하여 리스트를 생성합니다. 첫 번째 인자로 함수 fn(seed)를 받고, 두 번째 인자로 초기 시드값 seed를 받습니다. fn은 seed를 입력받아 [value, newSeed]를 반환하거나, 종료 조건 시 falsey 값을 반환합니다. unfold는 이 [value]들을 누적하여 리스트를 만들고, newSeed를 다음 시드로 다시 fn에 넣어 반복합니다. fn이 false를 반환하면 중단합니다.

이 함수는 명령형 루프를 함수적으로 대체하여, 계산된 연속적 시퀀스를 만들어내는 데 유용합니다.

사용 예시:

// 5까지 수들을 생성
const f = n => n > 5 ? false : [n, n + 1];
R.unfold(f, 1); 
//=> [1, 2, 3, 4, 5]

const generateBinary = n => n === 0 ? false : [n % 2, Math.floor(n / 2)];
R.unfold(generateBinary, 13); 
//=> [1, 0, 1, 1]  (13의 2진 표현을 뒤집어서 생성)

union

R.union는 두 배열의 합집합(중복 제거)을 반환합니다. 첫 번째 리스트와 두 번째 리스트의 모든 요소를 포함하되, 각 요소는 한 번씩만 나오는 배열을 돌려줍니다. 요소 비교에는 R.equals를 사용하며, 순서는 첫 번째 리스트의 요소를 유지하고, 두 번째 리스트에서 첫 리스트에 없는 새로운 요소들을 추가합니다.

사용 예시:

R.union([1, 2, 3], [3, 4, 5]); 
//=> [1, 2, 3, 4, 5]

R.union([{x:1}], [{x:2}, {x:1}]);
//=> [ {x:1}, {x:2} ]  (객체 {x:1}은 중복이라 한 번만)

unionWith

R.unionWith는 사용자 정의 동등 비교 함수를 사용하여 두 리스트의 합집합을 구합니다. 첫 번째 인자로 비교 함수 pred(a, b) -> Boolean (true이면 동일 간주), 이후 두 리스트를 받아, pred 기준으로 중복을 제거한 합집합을 반환합니다.

사용 예시:

const eqId = (a, b) => a.id === b.id;
const l1 = [{id:1}, {id:2}, {id:3}];
const l2 = [{id:3}, {id:4}];
R.unionWith(eqId, l1, l2);
//=> [{id:1}, {id:2}, {id:3}, {id:4}]

uniq

R.uniq는 리스트에서 중복 요소를 제거한 새 리스트를 반환합니다. R.equals로 비교하며, 첫 번째로 등장한 요소만 남기고 이후 같은 것은 무시합니다. 즉, 배열의 원소의 집합을 반환합니다 (순서 유지).

사용 예시:

R.uniq([1, 2, 1, 3, 1]); 
//=> [1, 2, 3]  (1은 한 번만)

R.uniq([{x:1}, {x:2}, {x:1}]);
//=> [ {x:1}, {x:2} ]  (객체 값 {x:1} 중복 제거)

uniqBy

R.uniqBy는 요소를 특정 함수에 적용한 결과값을 기준으로 중복 제거합니다. 첫 번째 인자로 키 생성 함수 fn, 두 번째 인자로 리스트를 받습니다. fn에 대한 결과가 같은 요소들은 중복으로 간주하여, 가장 먼저 나온 요소만 유지하고 나머지는 제거합니다.

예를 들어, 문자열 리스트를 대소문자 무시하여 중복 제거하거나, 객체 리스트를 특정 속성값 기준으로 유일하게 만들 수 있습니다.

사용 예시:

R.uniqBy(Math.abs, [1, -1, 2, -2, 3]);
//=> [1, 2, 3]  (절댓값 기준 중복 제거)

const users = [{id:1, name:'a'}, {id:2, name:'b'}, {id:1, name:'c'}];
R.uniqBy(R.prop('id'), users);
//=> [ {id:1, name:'a'}, {id:2, name:'b'} ] (id 1은 첫번째만)

uniqWith

R.uniqWith는 커스텀 동등 비교 함수로 리스트 중복을 제거합니다. 첫 번째 인자로 비교 함수 pred(a, b) (true이면 같은), 두 번째 인자로 리스트를 받아, pred 기준 첫 등장 외 모두 제거한 리스트를 반환합니다.

사용 예시:

const eqName = (a, b) => a.name.toLowerCase() === b.name.toLowerCase();
const persons = [{name:'ALICE'}, {name:'Alice'}, {name:'Bob'}];
R.uniqWith(eqName, persons);
//=> [ {name:'ALICE'}, {name:'Bob'} ]  ('ALICE'와 'Alice'를 동일로 인식해 하나만 남김)

unless

R.unless는 조건을 만족하지 않으면 지정된 함수를 실행하는 conditional 함수입니다. 첫 번째 인자로 조건 함수 pred, 두 번째 인자로 변환 함수 fn, 이후 하나의 값을 받아, pred(value)가 false면 fn(value) 결과를, true면 value를 그대로 반환합니다.

쉽게 말해 if-not: “만약 조건이 거짓이면 ~하라, 아니면 그대로”.

사용 예시:

const safeInc = R.unless(x => x === undefined, R.inc);
safeInc(42);       //=> 43 (undefined가 아니므로 inc 적용)
safeInc(undefined); //=> undefined (조건 true, 값 그대로 반환)

const nonNeg = R.unless(x => x >= 0, Math.abs);
nonNeg(-5); //=> 5  (음수이면 절댓값으로)
nonNeg(3);  //=> 3  (양수이면 그대로)

unnest

R.unnest는 R.flatten의 alias (동일 기능)으로, 배열의 한 단계 중첩을 제거합니다. 즉, 배열 내부의 배열 요소들을 풀어내 같은 레벨로 합칩니다 (depth 1 flatten).

사용 예시:

R.unnest([1, [2, 3], [[4]], 5]); 
//=> [1, 2, 3, [4], 5]  (한 단계 풀어서 [4]는 남음)

R.unnest([[1,2], [3,4]]); 
//=> [1, 2, 3, 4]

until

R.until는 조건을 만족할 때까지 반복적으로 함수를 적용합니다. 첫 번째 인자로 종료 조건 함수 pred, 두 번째 인자로 반복 적용 함수 fn, 세 번째 인자로 초기 값 initial을 받습니다. pred가 true를 반환하면 현재 값을 반환하고, false면 fn(current)를 계산하여 다시 pred 검사하는 식으로 반복합니다.

예를 들어, 숫자가 특정 임계값 이상이 될 때까지 두 배로 키우는 등의 작업에 사용할 수 있습니다.

사용 예시:

const doubleUntil100 = R.until(x => x >= 100, x => x * 2);
doubleUntil100(7); //=> 112
// 7 -> 14 -> 28 -> 56 -> 112 (다음 224이지만 112>=100 조건 만족되어 멈춤)

const incUntilLength5 = R.until(arr => arr.length === 5, arr => [...arr, 0]);
incUntilLength5([1, 2]);
//=> [1, 2, 0, 0, 0]  (길이 5 될 때까지 0 추가)

update

R.update는 배열의 특정 인덱스 위치의 요소를 새 값으로 교체한 배열을 반환합니다. 첫 번째 인자로 인덱스 index, 두 번째 인자로 새 값 val, 세 번째 인자로 배열을 받아, 해당 index 위치에 val을 놓고 나머지는 동일한 배열을 반환합니다. 원본은 변하지 않습니다.

만약 index가 범위 밖이면 원본 배열을 그대로 반환합니다.

사용 예시:

R.update(2, 99, [0, 1, 2, 3, 4]);
//=> [0, 1, 99, 3, 4]

R.update(-1, 'z', ['a', 'b', 'c']); 
//=> ['a', 'b', 'z'] (-1 인덱스 = 마지막 요소 교체)

R.update(5, 42, [1, 2, 3]); 
//=> [1, 2, 3] (인덱스 5 없음, 원본 그대로)

useWith

R.useWith는 함수의 각 인자를 다른 함수로 변환하여 처리합니다. 첫 번째 인자로 메인 함수 fn, 두 번째 인자로 함수 배열 [f1, f2, ...]를 받고, 반환된 함수는 주어진 인자들을 각각 f1, f2, ...로 변환한 후 fn에 적용한 결과를 반환합니다. 만약 인자 개수가 변환 함수 배열 길이보다 많으면 나머지 인자들은 그대로 fn에 전달됩니다.

이 함수는 여러 입력이 서로 다른 전처리가 필요할 때 유용합니다.

사용 예시:

const multiplyAndAdd = (x, y, z) => x * y + z;
const f = R.useWith(multiplyAndAdd, [Math.abs, Math.abs, R.identity]);

f(-3, -4, 5); 
//=> 17  (abs(-3)=3, abs(-4)=4, 5 그대로, multiplyAndAdd(3,4,5)=12+5=17)

const mergeThree = (a, b, c) => [...a, ...b, ...c];
const g = R.useWith(mergeThree, [R.identity, R.tail, R.init]);
g([1,2,3], ['x','y','z'], [true,false]);
//=> [1,2,3, 'y','z', true] (첫 배열 그대로, 두번째 tail(['y','z']), 세번째 init([true]))

values

R.values는 객체의 own property 값들을 배열로 반환합니다. 순서는 R.keys 순서에 대응합니다.

사용 예시:

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

R.values({}); 
//=> []

valuesIn

R.valuesIn은 객체의 own property와 prototype property 값들을 모두 배열로 반환합니다. for...in으로 값을 수집한다고 보면 됩니다 (순서는 JS 엔진 정의에 따름).

사용 예시:

function F() { this.x = 1; }
F.prototype.y = 2;
const f = new F();
R.valuesIn(f);
//=> [1, 2] (x 값과 y 값 모두 포함)

view

R.view는 렌즈를 사용하여 구조 내의 값을 반환합니다. 첫 번째 인자로 렌즈 lens, 두 번째 인자로 대상 target을 받아, lens가 가리키는 target의 값을 돌려줍니다. (R.lensProp('x')와 {x:1} -> 1)

Lens는 R.lens, R.lensProp, R.lensPath, R.lensIndex 등을 통해 생성할 수 있습니다.

사용 예시:

const nameLens = R.lensProp('name');
R.view(nameLens, { name: 'Alice', age: 30 }); //=> 'Alice'

const indexLens = R.lensIndex(1);
R.view(indexLens, ['a', 'b', 'c']); //=> 'b'

when

R.when은 조건을 만족하면 지정한 함수를 적용하고, 아니면 원래 값을 그대로 반환하는 함수입니다. 첫 번째 인자로 조건 함수 pred, 두 번째 인자로 변환 함수 fn을 받고, 하나의 인자를 받아 pred(value)가 true이면 fn(value) 결과를, false이면 value를 돌려줍니다.

when은 ifElse의 축약형으로, else의 동작이 identity인 경우 사용합니다.

사용 예시:

const absIfNegative = R.when(x => x < 0, Math.abs);
absIfNegative(-5); //=> 5  (음수라 Math.abs 적용)
absIfNegative(3);  //=> 3  (양수라 그대로)

const greet = R.when(person => person.lang === 'en', person => 'Hello ' + person.name);
greet({ name: 'Moe', lang: 'en' }); //=> 'Hello Moe'
greet({ name: 'Moe', lang: 'es' }); //=> { name: 'Moe', lang: 'es' } (객체 그대로 반환)

where

R.where는 객체가 여러 속성 조건을 모두 만족하는지 검사합니다. 첫 번째 인자로 조건 객체 spec, 두 번째 인자로 대상 객체 obj를 받아 boolean을 반환합니다. spec의 각 키에 대응하는 값은 판정 함수 또는 값입니다:

  • 값이 일반 값이면 obj[key] === value인지 검사.

  • 값이 함수이면 value(obj[key])가 true인지 검사.

모든 키 조건이 만족(true)해야 결과가 true입니다. 조건 키 중 obj에 없는 키가 있으면 false입니다.

사용 예시:

const spec = { x: R.gte(R.__, 10), y: y => y === 20 };
R.where(spec, { x: 10, y: 20, z: 30 }); //=> true  (x >= 10 true, y===20 true)
R.where(spec, { x: 5, y: 20 });        //=> false (x 조건 불만족)
R.where({ a: 1, b: 2 }, { a: 1, b: 2, c: 3 }); //=> true
R.where({ a: 1, b: 2 }, { a: 1, b: 1 });       //=> false

whereAny

R.whereAny는 객체가 지정된 여러 조건 중 하나라도 만족하면 true를 반환합니다. spec 객체 내 조건들 중 최소 하나를 통과하면 true입니다. (where는 모두 통과해야 true였음)

즉, R.where의 OR 버전입니다.

사용 예시:

const spec = { x: R.equals(10), y: R.equals(20) };
R.whereAny(spec, { x: 5, y: 20 });  //=> true  (y 조건 만족)
R.whereAny(spec, { x: 10, y: 30 }); //=> true  (x 조건 만족)
R.whereAny(spec, { x: 5, y: 30 });  //=> false (둘 다 불만족)

whereEq

R.whereEq는 객체가 특정 패턴 객체와 동일한 속성값들을 가지고 있는지 검사합니다. 첫 번째 인자로 패턴 객체 spec, 두 번째 인자로 대상 객체 obj를 받아 boolean 반환합니다. 이는 R.where(R.map(R.equals, spec), obj)와 같습니다: spec의 각 속성 값과 obj의 대응 속성이 R.equals로 같으면 true, 하나라도 다르면 false. spec에 없는 속성은 무시됩니다 (즉, obj는 spec 패턴을 포함하기만 하면 됨).

사용 예시:

const spec = { x: 1, y: 2 };
R.whereEq(spec, { x: 1, y: 2, z: 3 }); //=> true  (x,y 일치)
R.whereEq(spec, { x: 1, y: 1 });      //=> false (y 다름)

without

R.without는 두 리스트의 차집합과 유사하게, 첫 번째 리스트에서 두 번째 리스트에 존재하는 모든 요소를 제거한 리스트를 반환합니다. 두 번째 리스트가 제거할 값들의 집합 역할을 합니다. 비교는 R.equals로 수행합니다.

예를 들어, 블랙리스트 요소들을 제거하거나, A 리스트에서 B 리스트의 원소를 빼고 싶을 때 사용합니다.

사용 예시:

R.without([1, 2], [1, 2, 3, 4]); //=> [3, 4]  (1,2 제거)
R.without(['a', 'c'], ['a', 'b', 'c', 'd']); //=> ['b', 'd']

const initial = [{x:1}, {x:2}, {x:3}];
const toRemove = [{x:2}];
R.without(toRemove, initial);
//=> [ {x:1}, {x:3} ]  (값 {x:2}인 객체 제거)

xor

R.xor는 논리 배타적 OR (exclusive OR) 연산을 합니다. 두 값 중 하나만 truthy일 때 true를, 그렇지 않으면 false를 반환합니다. (둘 다 truthy이거나 둘 다 falsy면 false)

사용 예시:

R.xor(true, false);   //=> true  (하나만 true)
R.xor(false, true);   //=> true  (하나만 true)
R.xor(true, true);    //=> false (둘 다 true)
R.xor(false, false);  //=> false (둘 다 false)

R.xor(0, 1);          //=> true  (0 falsy, 1 truthy)
R.xor(1, 2);          //=> false (둘 다 truthy 값)

xprod

R.xprod는 두 리스트의 데카르트 곱(cartesian product)을 구합니다. 첫 번째 리스트의 모든 요소와 두 번째 리스트의 모든 요소의 조합을 쌍으로 만들어 이차원 배열을 반환합니다. 결과 배열의 크기는 length1 * length2 입니다.

사용 예시:

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

R.xprod([1, 2], []); 
//=> [] (한 쪽이 빈 배열이면 곱 결과도 빈)

zip

R.zip는 두 리스트를 동일 인덱스 요소끼리 쌍으로 묶어 새로운 리스트를 반환합니다. 결과 리스트의 길이는 더 짧은 입력 리스트의 길이에 맞춰집니다.

예를 들어, [1,2,3]과 ['a','b','c']를 zip하면 [[1,'a'], [2,'b'], [3,'c']]가 됩니다.

사용 예시:

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

R.zip([1, 2], ['a', 'b', 'c']);
//=> [[1, 'a'], [2, 'b']]  (초과 'c'는 버려짐)

zipObj

R.zipObj는 두 병렬 리스트를 키 리스트와 값 리스트로 사용하여 객체를 생성합니다. 첫 번째 리스트를 키들, 두 번째 리스트를 값들로 하여, 대응되는 키:값으로 구성된 객체를 반환합니다. 짧은 쪽 리스트 기준으로 생성됩니다.

사용 예시:

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

R.zipObj(['x', 'y'], [10, 20, 30]);
//=> { x: 10, y: 20 } ('30'은 무시됨)

zipWith

R.zipWith는 두 리스트의 요소들을 주어진 함수로 결합하면서 쌍을 이룹니다. 첫 번째 인자로 이항 결합 함수 fn(a, b) -> result, 두 번째로 리스트1, 세 번째로 리스트2를 받아, 인덱스별로 fn(list1[i], list2[i]) 결과의 리스트를 반환합니다. 길이는 짧은 쪽에 맞춥니다.

예를 들어, 두 배열 같은 인덱스 값끼리 더하거나, 문자열 합치는 등 가능합니다.

사용 예시:

const sum = (x, y) => x + y;
R.zipWith(sum, [1, 2, 3], [4, 5, 6]); 
//=> [5, 7, 9]  (1+4, 2+5, 3+6)

const concat = (x, y) => x + ':' + y;
R.zipWith(concat, ['a','b'], ['A','B','C']); 
//=> ['a:A', 'b:B']  (C는 짝 없이 버려짐)
728x90
728x90

🍛 Ramda 세 번째 스텝: R.curry – 함수를 쪼개서 더 유연하게 쓰자

함수형 프로그래밍의 핵심 철학 중 하나는 작고, 순수하며, 조합 가능한 함수를 만드는 것입니다.
이때 중요한 역할을 하는 것이 바로 curry(커리)입니다.

Ramda에서는 R.curry를 통해 여러 인자를 받는 함수를, 하나씩 인자를 줄 수 있는 함수로 바꿔
함수 조합이나 재사용성을 극대화할 수 있습니다.


🧠 Topic Summary

R.curry: 다중 인자 함수를 하나씩 나눠서 받을 수 있게 만들어주는 도구

const add = (a, b) => a + b;
const curriedAdd = R.curry(add);

curriedAdd(1)(2); // 3
  • add(1, 2)와 같은 함수를 curriedAdd(1)(2)로 나눠서 호출 가능

  • 함수 조합에서 일부 인자만 미리 넣고 고정시킬 수 있음 (partial application)


🛠 Usage Examples

예제 1: 커리된 함수로 부분 적용(partial application) 하기

import * as R from 'ramda';

const multiply = (a, b) => a * b;
const curriedMultiply = R.curry(multiply);

const double = curriedMultiply(2);

double(5); // 10
double(10); // 20

설명: multiply를 커리화하여 double이라는 함수로 재사용하고 있습니다.

어떤 값이든 2배로 만드는 함수가 된 거죠.


예제 2: 리스트에서 특정 조건으로 필터링

const hasMinLength = R.curry((min, str) => str.length >= min);
const atLeast5Chars = hasMinLength(5);

R.filter(atLeast5Chars, ['hi', 'hello', 'world', 'js']); 
// ['hello', 'world']

설명: 커리된 함수에 min값을 먼저 고정하여, 나중에 문자열을 넣기만 하면 되는 새로운 함수를 만들었습니다.


예제 3: pipe와 함께 활용 (실전 조합)

const greet = R.curry((greeting, name) => `${greeting}, ${name}!`);

const excitedGreet = R.pipe(
  greet('Hello'),
  R.toUpper
);

excitedGreet('ramda'); // 'HELLO, RAMDA!'

설명: R.curry를 이용해 greeting만 먼저 설정하고, 이후에 이름만 넣는 방식으로 함수 조합을 깔끔하게 구성했습니다.


⚠️ Common Pitfalls

1. R.curry 는 함수만 커리화할 수 있다

R.curry(3); // ❌ 에러

2. 커리된 함수는 항상 인자를 다 받기 전까진 실행되지 않는다

const add3 = R.curry((a, b, c) => a + b + c);

add3(1)(2); // 아직 실행 안 됨
add3(1)(2)(3); // 실행됨: 6

3. this 를 사용하는 함수는 커리화에 주의해야 함

함수 내부에서 this를 사용하는 경우 R.curry와 호환되지 않을 수 있습니다. 대신 bind 또는 화살표 함수로 대체하거나 외부에서 context를 주입하는 방식이 필요합니다.


🔎 Advanced Tip:

R.partial vs R.curry

  • R.partial: 일부 인자를 고정한 새 함수 생성

  • R.curry: 여러 인자를 순차적으로 나눠 받을 수 있도록 만든 함수

const greet = (greeting, name) => `${greeting}, ${name}`;
const hello = R.partial(greet, ['Hello']); // Hello 고정
const hello2 = R.curry(greet)('Hello');    // 동일한 효과

둘 다 목적은 비슷하지만, R.curry는 pipe나 compose 같은 함수 조합에 더 잘 어울립니다.


✅ Call to Action

함수의 일부 인자만 먼저 넣어둘 수 있다면, 얼마나 다양한 곳에 재사용할 수 있을까요?

오늘은 R.curry를 통해 재사용성과 조합 가능한 함수를 만드는 연습을 해보세요.

실습 아이디어:

  • hasRole(role, user) → isAdmin = hasRole('admin')

  • formatDate(locale, date) → formatKoreanDate = formatDate('ko-KR')

728x90
728x90

🔁 Ramda 기초 다지기: R.compose — 오른쪽에서 왼쪽으로 흐르는 함수 조합

함수형 프로그래밍의 강력한 매력 중 하나는 작은 함수를 조합해 큰 동작을 만드는 능력입니다.
Ramda에서는 이 함수 조합을 위해 R.pipeR.compose라는 두 가지 툴을 제공합니다.

이전 포스트에서는 R.pipe로 왼쪽에서 오른쪽으로 함수들을 이어 붙였죠.
이번에는 R.compose를 통해 오른쪽에서 왼쪽으로 데이터를 흐르게 하는 방법을 배워봅니다.


🧠 Topic Summary

R.compose란?

R.compose는 여러 함수를 오른쪽에서 왼쪽으로 차례차례 실행해서, 결과값을 만들어내는 함수입니다.

즉, 아래처럼 f3(f2(f1(value)))의 형태로 실행됩니다:

const composedFn = R.compose(f3, f2, f1);
composedFn('data'); // 실행 순서: f1 → f2 → f3

이것은 수학에서 함수 합성 f(g(h(x)))의 형태와 정확히 같으며, 코드 흐름을 수학적 추론처럼 표현하고 싶을 때 유용합니다.


🔍

pipe vs compose

특징 R.pipe R.compose
실행 방향 왼쪽 → 오른쪽 오른쪽 → 왼쪽
읽기 방식 일반 문장 흐름과 유사 수학 함수 합성과 유사
예시 pipe(f1, f2, f3)(x) compose(f3, f2, f1)(x)

실제로는 어떤 걸 써도 무방하지만, 읽는 순서나 팀 스타일에 따라 선택하면 됩니다.


🛠 Usage Examples

예제 1: 점수 계산 + 설명 붙이기

import * as R from 'ramda';

const describeScore = R.compose(
  (score) => `점수는 ${score}점입니다.`,
  R.multiply(10),     // 10배
  R.add(5)            // 5 더하기
);

describeScore(7); // "점수는 120점입니다."
// 흐름: 7 → 12 → 120 → 텍스트로 변환

설명: 7이라는 숫자에 먼저 5를 더하고(R.add(5)), 그 결과에 10을 곱한 다음, 최종 숫자에 설명 문구를 붙였습니다.


예제 2: URL 정리하기

const cleanUrl = R.compose(
  R.toLower,                     // 모두 소문자로
  R.replace(/^https?:\/\//, ''), // http:// 또는 https:// 제거
  R.trim                         // 앞뒤 공백 제거
);

cleanUrl('  https://Example.COM/page  '); // 'example.com/page'

설명: 사용자가 입력한 URL에서 프로토콜(http://, https://)과 공백을 제거하고, 전부 소문자로 변환해 클린한 URL을 만들었습니다.


예제 3: 사용자 이름 처리 (실전 응용)

const formatUsername = R.compose(
  R.toLower,
  R.replace(/\s+/g, '_'),
  R.prop('name')
);

formatUsername({ id: 1, name: 'John Doe' }); // 'john_doe'

설명: 객체에서 name만 추출한 후, 띄어쓰기를 언더스코어로 바꾸고 소문자로 만드는 흐름입니다.


⚠️ Common Pitfalls

1. 순서 헷갈림

compose(f3, f2, f1)은 f1 → f2 → f3 순서로 실행되므로, 초보자에게는 pipe가 더 직관적으로 느껴질 수 있습니다.

2. 여러 인자 함수와 함께 사용 시 에러

compose는 기본적으로 단일 인자 함수 조합을 염두에 둡니다.

두 개 이상의 인자가 필요한 함수는 R.curry, R.partial 등을 함께 사용해야 안정적입니다.

const add = (a, b) => a + b;
const double = x => x * 2;

// 잘못된 예 (add는 두 개의 인자를 받음)
R.compose(double, add)(2); // NaN

// 해결: curry 처리
const curriedAdd = R.curry(add);
R.compose(double, curriedAdd(2))(3); // 10

3. 디버깅이 어려울 수 있음

실행 순서가 반대이기 때문에, 버그가 발생했을 때 pipe보다 디버깅이 까다로울 수 있습니다.


🧩 Tip: compose는 “마지막 작업부터 생각할 때” 유용하다

예를 들어, “데이터를 보여주기 전에 정리하고, 정리하기 전에 뭔가 계산해야 한다” 같은 상황에서는,

출력 방향부터 생각하는 compose가 더 자연스러운 흐름이 됩니다.


✅ Call to Action

728x90

+ Recent posts