Ramda __ (placeholder)

드디어 첫 번째 함수 시작!

첫 번째 함수는 Documentation 첫 번째에 위치한 __ 함수이다.

처음 보면 이게 뭔가 싶기도 한데, underscore 달랑 2개가 함수 이름이다. 허 그것 참…

함수 설명

A special placeholder value used to specify “gaps” within curried functions, allowing partial application of any combination of arguments, regardless of their positions.

발번역: 커리된 함수 내에서 위치에 상관 없이 인수의 조합을 부분적으로 적용할 수 있는 특별한 플레이스홀더

내맘대로 번역: 커리된 함수에서 인수의 순서를 맘대로 바꿔 쓸 수 있도록 해주는 친구 되시겠다.

인수의 순서를 맘대로 바꿔 쓴다?

Ramda는 중요한 인수를 맨 마지막에 받는 패턴을 이용해서 함수의 합성을 수행하는데

이때 __ 를 사용하면 함수 합성시 원하는 인수 위치로 이전 함수의 결과를 합성의 인수로 사용할 수 있다.

__ docs에도 예제가 친절하게 나와있지만 예제는 아래 항목에서 확인해 보겠다.

코드 분석

먼저 Ramda는 github 에 코드가 있고, 소스코드 디렉토리를 보면 export 를 통해 노출되는 함수들은 source 디렉토리에 위치하고 있고, 노출되지 않고 private 으로 사용되는 함수들은 source/internal 에 위치하고 있다.

자, 그럼 우리가 원하는 __ 함수는 export를 통해 노출되는 함수이므로 source/__.js 를 보면 된다.

주석은 다 건너 뛰고… 이게 다다.

1
export default {'@@functional/placeholder': true};

그냥 @@functional/placeholder 속성의 값을 true 로 갖는 Object 를 반환한다. 음? 함수가 아니네?

이걸 어디다 어떻게 쓰나??? 하고 코드들을 잘 뒤져보면

source/internal/_isPlaceholder.js 에 Placeholder 인지 여부를 판단하는 함수가 있다.

1
2
3
4
5
export default function _isPlaceholder(a) {
return a != null &&
typeof a === 'object' &&
a['@@functional/placeholder'] === true;
}

인자로 전달받은 값에 @@functional/placeholder 속성이 true 인지 판단하는 함수이다.

이건 어디서 쓰나??? 하고 잘 찾아보기 전에… __ 의 docs에 어떨 때 쓴다고 나와있었다. 바로

within curried functions

라고 말이다.

그럼 curry 안에 있겠지??

오늘은 curry 함수를 설명하는 시간이 아니니 어떻게 쓰이는지 확인할 수 있는 링크들을 남겨 두겠다.

  1. source/internal/_curry1.js
  2. source/internal/_curry2.js
  3. source/internal/_curry3.js
  4. source/internal/_curryN.js

source/internal 에서만 사용되니 참고하도록.

실제 사용 예제

요런 로직을 짜야한다고 가정하자.

Database 에서 사용자 이름을 조회하여 사용자 Object에 userName 속성으로 저장

Database에서 사용자 이름을 조회하는 함수 getUserNameFromDbid 를 통해 사용자 정보를 가져오고 async 로 동작한다고 하자. 그리고 사용자 Object는 간단하게 user 라고 하자.

그리고 나중에 분석할 함수지만 assoc 을 통해 useruserName 을 추가하는 방법을 코드로 작성해보자.

참고로 assoc 함수는 인수로 (속성명, 속성값, Object) 를 받으며, Object속성값 에 해당하는 속성명 을 추가하여 clone 된 새로운 Object 를 반환한다. 보통 함수 합성과 assoc 함수를 같이 사용하면 Object 위치에서 합성에 필요한 인수를 받게 되는데, __ 를 이용하면 그 위치를 바꿀 수 있다.

더욱 자세한건 나중에 함수를 분석할 때 알아보도록 하고 코드를 보자.

1
2
3
4
5
6
7
8
import R from "ramda";

const id = 12345;

getUserNameFromDb(id)
.then(R.assoc("userName", R.__, user))
.then(console.log)
.catch(console.error);

getUserNameFromDb 의 수행 결과를 then 을 통해 Promise 를 벗겨내고, assoc 함수의 두 번째 인수로 넘기고 있다.

함수 합성은 Ramda의 pipe, compose 함수 외에 Promise의 경우에는 then 만 사용하여 얼마든지 합성이 가능하다.

마치며…

오늘은 __ 에 두서없이 분석(?) 이라기도 뭐한 걸 해 봤다.

문뜩 생각이 든건데 바로 위 코드를 보면 user Object에다가 그냥 userName 속성 하나 추가하는건데 assoc 까지 써야하나? 귀찮게? 라고 생각 할 수 있다.

실제로 async/await 구문을 사용하면 한줄에 끝난다.

1
user.userName = await getUserNameFromDb(id);

그럼에도 왜 힘들게 Ramda 를 써가면서 불필요한 코드들을 쓰는가? 라고 생각 할 수 있겠지만

코드가 점점 커져가고 유지보수 할 내용들이 많아질 경우

함수형으로 작성한 코드는 함수를 하나의 모듈로 해서 끼웠다 뺐다 별 짓을 다 하기가 굉장히 쉬워진다. 왜냐면 함수형 프로그래밍에서

함수는 항상 똑같은 입력에 대해 똑같은 결과를 반환해야 한다

라는 pure function 정의에 의해 함수 하나 뺀다고 결과가 막 어그러지거나 프로그램이 뻑나거나 하지 않는다.

실제로 실무에서 신나게 기능을 구현해 놨었는데 기획 단계에서 구현된 기능이 스펙아웃 되면서 제거된 경우가 종종 있었다.

옜날 코흘리개 시절 (지금도 마찬가지지만) 작성한 코드는 이럴 경우 상당히 많은 코드들을 뜯어 고치고 수정을 했었는데 요즘은 그냥 해당 함수 하나 호출하는 부분을 지워버리는 일로 끝낸다.