해당 게시글은 mostly-adequate-guide 의 시리즈들을 번역했습니다.
해당게시글의 저작권은 mostly-adequate-guide 의 저작권인 Attribution-ShareAlike 4.0 International 을 준수합니다.
🤖 AI가 요약한 글이예요 !
이 글은 함수형 프로그래밍 기법인 커링(currying) 에 대해 설명합니다.
커링은 함수를 예상보다 적은 인수로 호출했을 때, 나머지 인수를 받는 새로운 함수를 반환하는 방식입니다.
이를 통해 함수를 단계적으로 적용하거나, 인수를 미리 채워 넣어 재사용 가능한 함수를 쉽게 만들 수 있습니다.
curry헬퍼 함수를 사용하여 커링된 함수를 정의하고 사용하는 예시를 보여주며, 데이터 인수를 마지막에 배치하는 패턴의 유용성을 강조합니다.
커링은 코드 중복을 줄이고, 함수를 더 유연하게 조합하며, 수학적 함수의 정의(단일 입력, 단일 출력)를 유지하는 데 도움이 됩니다.
챕터 04: 커링
당신 없이는 살 수 없다면 살 수 없어요
아버지께서 한번은 어떤 것들은 그것들을 얻기 전까지는 없이 살 수 있다고 설명해주신 적이 있습니다. 전자레인지가 그런 것 중 하나입니다. 스마트폰도 마찬가지죠. 우리 중 나이가 많은 분들은 인터넷 없이도 만족스러운 삶을 기억하실 겁니다. 저에게는 커링(currying) 이 이 목록에 있습니다.
커링(Currying): 여러 인수를 받는 함수를, 각각 단일 인수를 받는 함수들의 연속으로 변환하는 기법입니다. 예를 들어,
f(a, b, c)를f(a)(b)(c)형태로 바꾸는 것입니다.
개념은 간단합니다: 함수가 기대하는 것보다 적은 인수로 함수를 호출할 수 있습니다. 그러면 나머지 인수를 받는 함수를 반환합니다. 한 번에 모든 인수를 호출하거나 각 인수를 조금씩 넣어줄 수 있습니다.
const add = (x) => (y) => x + y;
const increment = add(1); // y => 1 + y
const addTen = add(10); // y => 10 + y
increment(2); // 3
addTen(2); // 12여기서 우리는 하나의 인수를 받아 함수를 반환하는 add 함수를 만들었습니다. 이 함수를 호출함으로써, 반환된 함수는 클로저를 통해 첫 번째 인수를 기억합니다. 하지만 두 인수를 한 번에 호출하는 것은 약간 번거롭기 때문에, curry라는 특별한 헬퍼 함수를 사용하여 이런 함수들을 더 쉽게 정의하고 호출할 수 있습니다.
즐거움을 위해 몇 가지 커링된 함수를 설정해 봅시다. 지금부터는 부록 A - 필수 함수 지원에 정의된 curry 함수를 사용할 것입니다.
// curry 함수는 Appendix A - Essential Function Support 에 정의되어 있다고 가정합니다.
// const curry = require('./support'); // 예시: 실제 경로에 맞게 수정 필요
const match = curry((what, s) => s.match(what));
const replace = curry((what, replacement, s) => s.replace(what, replacement));
const filter = curry((f, xs) => xs.filter(f));
const map = curry((f, xs) => xs.map(f));제가 따른 패턴은 간단하지만 중요합니다. 우리가 작업하는 데이터(문자열, 배열)를 전략적으로 마지막 인수로 배치했습니다. 사용하는 즉시 그 이유가 명확해질 것입니다.
(구문 /r/g는 모든 'r' 문자와 일치하는 정규 표현식입니다. 원한다면 정규 표현식에 대해 더 읽어보세요.)
match(/r/g, "hello world"); // [ 'r' ]
const hasLetterR = match(/r/g); // s => s.match(/r/g)
hasLetterR("hello world"); // [ 'r' ]
hasLetterR("just j and s and t etc"); // null
filter(hasLetterR, ["rock and roll", "smooth jazz"]); // ['rock and roll']
const removeStringsWithoutRs = filter(hasLetterR); // xs => xs.filter(s => s.match(/r/g))
removeStringsWithoutRs(["rock and roll", "smooth jazz", "drum circle"]); // ['rock and roll', 'drum circle']
const noVowels = replace(/[aeiou]/gi); // (replacement, s) => s.replace(/[aeiou]/ig, replacement)
const censored = noVowels("*"); // s => s.replace(/[aeiou]/ig, '*')
censored("Chocolate Rain"); // 'Ch*c*l*t* R**n'여기서 보여주는 것은 함수에 인수를 하나 또는 두 개 "미리 로드"하여 해당 인수를 기억하는 새 함수를 받는 능력입니다.
Mostly Adequate 저장소를 복제(git clone https://github.com/MostlyAdequate/mostly-adequate-guide.git)하고, 위의 코드를 복사하여 REPL에서 시도해 보시기를 권장합니다. curry 함수와 부록에 정의된 모든 것은 support/index.js 모듈에서 사용할 수 있습니다.
또는 npm에 게시된 버전을 살펴보세요:
npm install @mostly-adequate/support단순한 말장난 이상 / 특별한 소스
커링은 많은 것에 유용합니다. hasLetterR, removeStringsWithoutRs, censored에서 보았듯이 기본 함수에 몇 가지 인수를 제공하여 새 함수를 만들 수 있습니다.
또한 단일 요소에서 작동하는 모든 함수를 map으로 감싸서 배열에서 작동하는 함수로 변환할 수 있습니다:
const getChildren = (x) => x.childNodes;
const allTheChildren = map(getChildren); // xs => xs.map(x => x.childNodes)함수에 예상보다 적은 인수를 제공하는 것을 일반적으로 부분 적용(partial application) 이라고 합니다. 함수를 부분 적용하면 많은 상용구 코드를 제거할 수 있습니다. 위의 allTheChildren 함수가 lodash의 커링되지 않은 map (인수 순서가 다름에 유의) 으로 어떻게 될지 생각해 보세요:
부분 적용(Partial Application): 함수의 일부 인수를 미리 고정하여 더 적은 수의 인수를 받는 새로운 함수를 만드는 기법입니다. 커링과 유사하지만, 커링은 항상 단일 인수를 받는 함수들의 연속으로 변환하는 반면, 부분 적용은 여러 인수를 한 번에 고정할 수 있습니다.
// lodash의 map 함수 사용 예시 (인수 순서 다름)
// const _ = require('lodash');
// const allTheChildren = (elements) => _.map(elements, getChildren);우리는 일반적으로 배열에서 작동하는 함수를 정의하지 않습니다. 왜냐하면 map(getChildren)을 인라인으로 호출할 수 있기 때문입니다. sort, filter 및 기타 고차 함수(higher-order function) 도 마찬가지입니다.
고차 함수(Higher-Order Function): 함수를 인수로 받거나 함수를 결과로 반환하는 함수입니다.
map,filter,reduce등이 대표적인 예입니다.
순수 함수에 대해 이야기할 때, 우리는 그것들이 1개의 입력을 받아 1개의 출력을 낸다고 말했습니다. 커링은 정확히 이것을 합니다: 각 단일 인수는 나머지 인수를 기대하는 새 함수를 반환합니다. 그것이 바로, 친구여, 1개의 입력에서 1개의 출력입니다.
출력이 다른 함수이든 상관없이 순수 함수 자격을 갖습니다. 한 번에 둘 이상의 인수를 허용하지만, 이는 단순히 편의를 위해 추가 ()를 제거하는 것으로 간주됩니다.
요약하자면
커링은 편리하며 저는 매일 커링된 함수로 작업하는 것을 매우 즐깁니다. 이것은 함수형 프로그래밍을 덜 장황하고 지루하게 만드는 도구입니다. 몇 가지 인수를 전달하는 것만으로 즉시 새롭고 유용한 함수를 만들 수 있으며, 보너스로 여러 인수에도 불구하고 수학적 함수 정의를 유지했습니다.
또 다른 필수 도구인 compose를 습득해 봅시다.
연습 문제
연습 문제에 대한 참고 사항
책 전반에 걸쳐 이와 같은 '연습 문제' 섹션을 만날 수 있습니다. gitbook에서 읽고 있다면 (권장) 브라우저에서 직접 연습 문제를 수행할 수 있습니다.
책의 모든 연습 문제에 대해 항상 전역 범위에서 사용할 수 있는 몇 가지 헬퍼 함수가 있다는 점에 유의하세요. 따라서 부록 A, 부록 B, 부록 C에 정의된 모든 것을 사용할 수 있습니다! 그리고 그것만으로는 충분하지 않다는 듯이 일부 연습 문제에서는 제시된 문제에 특정한 함수도 정의합니다. 사실상 그것들도 사용할 수 있다고 생각하세요.
힌트: 내장 편집기에서 Ctrl + Enter를 눌러 솔루션을 제출할 수 있습니다!
자신의 컴퓨터에서 연습 문제 실행하기 (선택 사항)
자신의 편집기를 사용하여 파일에서 직접 연습 문제를 수행하려는 경우:
- 저장소를 복제합니다 (
git clone git@github.com:MostlyAdequate/mostly-adequate-guide.git). - 연습 문제 섹션으로 이동합니다 (
cd mostly-adequate-guide/exercises). - 권장 노드 버전
v10.22.1을 사용하고 있는지 확인합니다 (예:nvm install). 책의 readme에 이에 대한 자세한 내용이 있습니다. - npm을 사용하여 필요한 종속성을 설치합니다 (
npm install). - 해당 챕터 폴더의
exercise_*라는 이름의 파일을 수정하여 답을 완성합니다. - npm으로 수정을 실행합니다 (예:
npm run ch04).
단위 테스트가 답에 대해 실행되고 실수 시 힌트를 제공합니다. 참고로 연습 문제의 답은 solution_*이라는 이름의 파일에서 사용할 수 있습니다.
연습해 봅시다!
다음 함수를 고려하십시오:
const keepHighest = (x, y) => (x >= y ? x : y);