함수형 프로그래밍 101: 카레 패턴

함수형 프로그래밍의 핵심 개념.



OOP와 함수형 프로그래밍에 대한 나의 이야기



내가 배운 첫 번째 프로그래밍 언어는 Java 였으므로 분명히 오늘날 Java에서 일부 기능 프로그래밍 개념도 허용하지만 객체 지향 프로그래밍(지금부터 OOP라고 함)도 배워야 했습니다.

OOP의 핵심 개념을 배울 때 데스크탑에 앉아서 캡슐화, 예, 상속, 오 예, 그리고 갑자기 "야, 다형성이 도대체 뭐야?"와 같은 것을 읽고 있었습니다. 개념은 처음에는 고통스러웠지만 적용하는 것은 생각보다 쉬웠습니다. 시간이 지나면 POO(이상한 결과)를 사용하여 Python을 배우고 C#, C++, Ruby를 약간 사용합니다. 즉, OOP를 사용하여 탐색했습니다. 그리고 마지막으로 JavaScript를 배웠고 네, OOP를 다시 사용합니다. 어떤 이유로 든 JavaScript에서 OOP는 나를 전혀 확신시키지 못했습니다 (나도 그것을 사용하는 것이 지루했습니다). OOP를 사용하면 JavaScript의 다재다능함이 사라진다고 생각합니다. 그런 다음 ES6이 내 삶에 나타나고 모든 것이 바뀝니다. 나는 ES6이 함수형 프로그래밍을 허용한다는 것을 알았고, 그래서 나는 함수형 프로그래밍 패러다임과 함께 JavaScript(TypeScript와 함께)를 사용하는 것에 대해 배우기로 결정했습니다. 함수형 프로그래밍의 핵심 개념을 배울 때 책상에 앉아 순수 함수, 예, 고차 함수 같은 것을 읽고 있었는데 갑자기 "야, 커링 함수가 대체 뭐야?"다시 말하지만, 처음에는 개념이 고통 스러웠지만 생각보다 적용하기가 쉬웠습니다.

오늘은 "Functional Programming 101"이라는 섹션에서 TypeScript를 사용하여 커링 함수가 무엇인지 설명하겠습니다.

카레 기능의 핵심 개념



커링 함수는 한 번에 하나의 매개변수만 사용하는 다른 함수를 반환하는 함수입니다.

Currying is a transformation of functions that translates a function from callable as f(a, b, c) into callable as f(a)(b)(c). [1]



function currying(a) {
    return function(b) {
        // do somethig with the assigned 'a' var
        // by using another function that pass 'b' var.
        return a + b; // for example
    }
}


이것은 웹에서 검색할 수 있는 매우 간단한 예입니다.
따라서 다음과 같이 하면:

console.log(currying(1)); // function currying(b)


결과적으로 함수를 얻습니다. 여기에서 모든 것이 정상입니다. 따라서 다음과 같이 하면 의미가 있습니다.

console.log(currying(1)(1)) // 2


커링 개념은 JS 클로저 덕분에 작동합니다.

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time. [2]



장점



이 간단한 개념은 사용해야 할 때 매우 강력하고 코드가 더 깔끔합니다. 일부 라이브러리의 경우 내보낸 커링 함수를 사용하는 것이 좋은 아이디어(가능한 경우)이거나 일부 시나리오에서는 더 많은 유연성을 허용합니다.

단점



문제를 풀 때 카레를 먹는 것은 일반적이지 않습니다. 글쎄요, 제 경우에는 특히 Factory에서 몇 번 사용했습니다.

JavaScript를 사용한 첫 번째 Currying 함수




// No curried function
const sumThreeNumbers = (a, b, c) => (a + b + c);

// You designed a function that always will suon only three numbers.
// But what if I need a sum four numbers? or 'n' numbers?
console.log(sumThreeNumbers(1, 2, 3)); // 6


// Curried
const add = (a) => (function(b) { return a + b} );

// I can sum as I want without depend of the number of arguments.
console.log(add(add(1)(2))(3)); // 6
console.log(add(add(add(1)(2))(3))(4)); // 10


하지만 이 코드는 약간 혼란스러워 보입니다. 그래서 개선하겠습니다만 이번에는 TypeScript를 사용합니다.

TypeScript를 사용하여 첫 번째 Currying 함수 개선



첫 번째 커링 기능을 개선하기 위한 두 가지 제안이 있습니다. 첫 번째는 멋지지만 두 번째는 내가 가장 좋아하는 것입니다.

상태를 저장하여



이 예제는 핵심 개념과 매우 유사해 보이며 정확히 'n'번의 합을 계산하기 위해 제한된 카레 함수를 반환하는 카레 함수를 설계할 필요가 없습니다.

const add = (...a: number[]): Function => {

  function curried(...b: number[]) {
    return add(...a, ...b)
  }

  // Just saving the 'state' to the returned value.
  // Remeber that Functions are objects too in JS.
  curried.done = a.reduce((result: number, value: number) => result + value;

  return curried;
}

// I designed a nice currying sum by saving the state.
console.log(add(1)(2)(3)(4)(5)(6).done); // 21


잘 작동하지만 한 가지 문제가 있습니다. 개체를 사용하고 있고 기능만 사용하고 싶습니다. 그래서 여기 우리의 커링 기능을 개선하기 위한 두 번째 제안이 있습니다.

재귀를 사용하여



이 경우는 더 이상 인수가 제공되지 않음을 감지할 때까지 전달된 함수를 사용하도록 설계되었습니다.

const curryUntilHasNoArguments = (functionToCurry: Function): Function => {
  const next = (...args: any[]) => {
  // I tried to avoid use any[] without spread the var with no success.
    return (_args: any[]) => {
      if (!(_args !== undefined && _args !== null)) {
        return args.reduce((acc, a) => {
          return functionToCurry.call(functionToCurry, acc, a)
        }, 0);
      }
      return next(...args, _args);
    };
  };
  return next();
};


const add = curryUntilHasNoArguments((a: number, b: number) => a + b);
// Don't forget end with '()' to tell that there's no more arguments.
console.log(add(1)(3)(4)(2)());


실제 사례



마지막으로 '실제' 문제(일종의)를 해결하는 이 기사를 끝내고 싶습니다. sum 카레링 예제는 사소하고 나는 단지 시연 목적으로 그것을 사용했습니다.

나무꾼




enum Method {
    WARN = "warn",
    ERROR = "error",
    LOG = "log",
    DEBUG = "debug",
    INFO = "info"
}

function createLogger(name: string, ): Function {
  return function(action: Method){
    return function print(message: string): void {
        console[action](`[${new Date()}] [${name}] ${message}`);
    }
  }
}

const logger = createLogger("Curry");
logger(Method.DEBUG)("This is a debug"); // [Dummy Date][Curry] This is a debug


이러한 종류의 로거 구현을 사용하면 많은 'if'를 피할 수 있습니다.

// Dummy scenario
const response = await api.call();
const {metadata, message} = response;

createLogger(api.name)(getMethod(metadata))(message);

function getMethod(metadata: ApiMetadata): Method {
 // do something with the metadata to return a valid Method.
 switch (metadata){
     case metadata.fail: return Method.error; 
 }
}



자원.


  • https://javascript.info/currying-partials

  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures .
  • 좋은 웹페이지 즐겨찾기