커리와 함수합성 그리고 point-free 관점

Felix Yi·2020년 3월 24일
2

리액트 함수형 컴포넌트를 하나 이상 합성하지 말라는.. 그 리덕스 만든 사람이 하는 말도 있지만 그냥 내가 이해를 못하는 걸 만나서 너무 화가난다. 그래서 커리 함수와 합성에 대해 알아본다. 당장 일 하는데 전혀 필요 없는 내용인데. 막히는 게 화가 나고 답답해.

소유하면 좋아하게 된다. 글을 작성하면서 변화되는 심경.
금방 끝나겠지?

=> 아 내가 리액트를 괜히 건들였구나. 
   뭔 함수형이여 옜날 거 써도 잘 되는구만. 
   아 글이 끝이 안나네. 이렇게까지 돌아가야 할 판이야?
=> 아 그렇구먼. 좋은데?

부분(적용)함수

부분함수 : 기대하는 것보다 적은 인자를 제공하는 함수가 부분 적용함수 = 인자가 부분적으로 이미 적용(제공)된 함수.

이거 아래처럼 하게 된다.

add(a,b)
increment(b) // a 가 부분적용된 함수
  1. 정의가 더 명확해짐 : 더하기 -> 증가
  2. 덜 반복적임 : 인자 하나만 넣어줌

그런데 부분 적용만 되면, 인자 개수가 예측 어렵고 불필요한 null 이 들어감.

add(a,b,c,d,e)
const increment = add.bind(null, 1,2)
increment(c,d,e) 

무슨 일이 일어나는 지 알려면, 클로저bind 를 살펴보자. 다 알면 바로 커리로 넘어가기.

클로저 탐구

function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
//myFunc변수에 displayName을 리턴함
//유효범위의 어휘적 환경을 유지
myFunc();
//리턴된 displayName 함수를 실행(name 변수에 접근)

자바스크립트 함수는 객체다. 함수는 실행하면 해당 스콥이 사라져야 하지만.. 위처럼 함수를 리턴해 버리면 해당 함수가 참조하는 렉시컬(어휘적, 상위 함수 (외부함수)) 스콥이 사라지지 않고 리턴된 함수와 함께 번들(묶인)된다. 또한 묶인 스콥은 그 함수밖에는 접근할 수 없고 외부에 닫혀있다. 이렇게 사라져야하지만 외부에 닫혀서 함수에 묶인 스콥을 가진 함수를 클로저라고 한다.

이것을 표현해 보자면 disPlayName 함수만 접근할 수 있는 스콥은 다음과 같을 것이다. makeFunc를 외부에서 접근할 수 없지만 disPlayName 로 향하는 연결을 가지고 있다면 그것을 통해 접근할 수 있다. (조직 내부 끈)

가상의 makeFunc 함수 객체 스콥 나라.

const makeFunc = {
  name: mozilla
  get displayName(){
  	alert(makeFunc.name)
  }
}

좀 더 이걸 구체화 시킨 예제가 있다.

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  // 함수가 아닌 객체를 제공 하지만
  return {
    // 객체 내부의 함수는 상위 function(외부함수) 객체의 어휘적 스콥이 번들됨
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var counter1 = makeCounter();
var counter2 = makeCounter();
alert(counter1.value()); /* 0 */
// 객체 내부 함수는 생성당시 상위 function 의 어휘적(외부함수) 스콥이 번들되었으므로 여전히 접근 가능.
counter1.increment();
counter1.increment();
alert(counter1.value()); /* 2 */
counter1.decrement();
alert(counter1.value()); /* 1 */
alert(counter2.value()); /* 0 */

여기 필요 없는 내용인데 본 김에.. 클로저에서 스콥 체인. 전역, 외부함수, 지역 스콥 다 접근(체인)할 수 있음.

프로토타입 체이닝은 상속모델, 객체의 상속으로 간주하면 편함. 객체 내부의 호출이 일어나면 자기에게 없으면 자기 부모를 거슬러 올라가며 찾아내서 리턴한다.

클로저 함수에서 체인이 된다는 말은 결국 함수도 prototype을 가진 객체라는 거고. 클로저 함수는 자기만 아는 비밀스런 상속을 받은 객체라는 거.

// 전역 범위 (global scope)
var e = 10;
function sum(a){
  return function(b){
    return function(c){
      // 외부 함수 범위 (outer functions scope)
      return function(d){
        // 지역 범위 (local scope)
        return a + b + c + d + e;
      }
    }
  }
}

bind 함수 탐구

call 도 함수를 받아서 함수를 반환하지만 부분 함수는 아님. 왜냐면 리턴하는 함수의 인자의 개수가 원본 함수랑 동일하니까.

function list() {
  // 배열 slice 함수에 인자를 넘겨주는 함수를 리턴.
  return Array.prototype.slice.call(arguments);
}

// 게다가 이렇게 매번 호출을 해야 적용됨.
var list1 = list(1, 2, 3); // [1, 2, 3]
var list1 = list(3, 4, 5); // [3, 4, 5]

arguments 객체는 매개변수(parameter)에 정의하지 않아도 함수 호출 시 인자(argument)로 넘겨주면 전달되는 객체다. 정의하지도 않는데 왜 받바고, 받은 걸 접근할 수 있으면 뒤통수 맞은 느낌인데 여튼 자바스크립트는 그그렇게 돌아간다.

bind 는 함수를 받아서 함수를 반환하고, 반환되는 함수가 받을 인자의 개수가 원본보다 적다. 즉 부분함수.

// bind 함
var leadingThirtysevenList = list.bind(null, 37);

// 요렇게 한번 뿅하면 클로저에 들어가 있어서 재사용 가능!
var list2 = leadingThirtysevenList();  // [37]

var list3 = leadingThirtysevenList(1, 2, 3);  // [37, 1, 2, 3]
  1. bind 가 원본 함수와 인자를 받음.
  2. bind 함수의 렉시컬(원본 입장에선 외부함수) 스콥 내부에 인자를 설정
  3. 원본 함수는 클로저가 됨. 즉 외부함수(bind) 스콥과 번들됨.
  4. 반환된 함수를 호출할 때는 bind 로 제공한 인자가 외부함수에서 제공되므로 나머지 인자만 제공하면 됨.

커리된 함수

커리된 함수 = 인자를 한번에 하나씩 여러개 받을 수 있는 함수

커링: 수학자 하스켈 커리(Haskell Curry)에서 유래함. 함수르 변형하는 과정

 const 세수합 = a=>b=>c=> a+b+c
 세수합(1)(2)(3) // 6
  1. a 받고, b => c=> => a+b+c 반환
  2. b 받고, c=> => a+b+c 반환
  3. c 받고, a+b+c 반환

부분적용 함수랑 뭐가 다른가?
1. 커리된 함수는 부분적용된 함수를 리턴.
2. 하지만, 부분적용 함수는 인자의 개수가 변동, 커리된 함수는 오직 하나(unary function)

이게 뭐가 좋은거야 대체?

point-free style(tactic style)에서 커리와 함수합성

택틱 프로그래밍, 점벗어난 스타일. 그들이 수행할 점(매개변수or인자)을 식별하지 않고 함수를 정의하는 프로그래밍 패러다임. 또 다른 말로, 불필요한 매개변수-인자 매핑으로 발생하는 어수선한 겉모습을 제거하는 것. 커리, 부분적용, 함수 합성이 잘 맞음.

커리의 좋은 점이 하나의 인자만 받아서 어수선한(인자가 여러개)인 겉모습 제거에 도움을 준다.

불필요한 매개변수-인자 매핑으로 발생하는 어수선한 겉모습(=>)을 제거해보자.

let newBooks = books.filter(point => isTechnology(point))

let newBooks = books.filter(isTechnology)

함수가 수행할 매개변수or인자를 식별하지 않게 해보자.

// map(배열, 처리함수) 에서 배열이 필요하다.
[‘Hello’,’Hello world’,’Hi’].map(processHello)

// map은 오직 하나의 인자만 받아서 함수를 리턴한다.
// 커리 덕분에 데이터에 종속적이지 않은 unary 함수를 만들 수 있게 됐다.
map(processHello)(‘Hello’,’Hello world’,’Hi’)

커리 말고 함수 합성도 좋음.

// word 가 언급되기 때문에 낫 포인트 프리
const snakeCase = word => word.toLowerCase().replace(/\s+/ig, '_');

// 포인트 프리
const snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase);

compose 는 여러 개의 함수를 받아서 우측 함수의 결과를 좌측 함수의 인자로 합성해주는 함수.

function compose(...fns) {
  const n = fns.length;

  return function $compose(...args) {
    let $args = args;

    for (let i = n - 1; i >= 0; i -= 1) {
      // 맨 우측 함수에다가 나머지 함수를 인자로 call 걸음.
      // 이런식으로 모든 함수의 인자로 이전 함수의 결과를 넘김
      $args = [fns[i].call(null, ...$args)];
    }
    
    // 가장 좌측에 있는 함수를 반환
    return $args[0];
  };
}

예시 2

// not pointfree because we mention the data: name
const initials = name => name.split(' ').map(compose(toUpperCase, head)).join('. ');

// pointfree
const initials = compose(join('. '), map(compose(toUpperCase, head)), split(' '));
initials('hunter stockton thompson'); // 'H. S. T'

혹은 간단하게 reduceRight로 맨 우측 부터 처리하도록...

// 여러 함수를 넣어주면
const compose = (...fns) => 
      // 추후 데이터를 받아서, 함수 좌측부터 인자를 적용한 결과를
      // 반복해서 누적한 결과를 반환함
      x => fns.reduceRight((y, f) => f(y), x);
const g = n => n + 1;
const f = n => n * 2;
// replace `x => f(g(x))` with `compose(f, g)`
const h = compose(f, g);
h(20); //=> 42

디버깅이 좀 거시기 함. 왜냐면 순식간에 실행=>결과=>인자 가 연쇄되어버림. 그럼 trace() 를 쓰자

const trace = label => value => {
  console.log(`${ label }: ${ value }`);
  return value;
};
const g = n => n + 1;
const f = n => n * 2;
/*
Note: function application order is
bottom-to-top:
*/
const h = compose(
  trace('after f'),
  f,
  trace('after g'),
  g
);
h(20);
/*
after g: 21
after f: 42
*/

참조

클로저
https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures

프로토타입 체인
https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain

bind
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

부분적용함수
https://hackernoon.com/partial-application-of-functions-dbe7d9b80760

커리된함수 함수합성
https://medium.com/javascript-scene/curry-and-function-composition-2c208d774983

arguments 객체
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/arguments

point free
https://medium.com/tech-tajawal/function-composition-point-free-style-54a209946e6
https://www.freecodecamp.org/news/how-point-free-composition-will-make-you-a-better-functional-programmer-33dcb910303a/

profile
다른 누구와도 같은 시장 육체 노동자

0개의 댓글