[함수형 프로그래밍] - 함수형 프로그래밍 개요 / 함수형 프로그래밍 정의, 순수함수

이 강의는 유인동 님의 함수형 프로그래밍 강의를 듣고 이해한 부분과 다른 자료를 통해 더 알게 된 내용을 함께 정리한 글입니다.

프로그래밍 패러다임(Programming Paradigm)은 프로그래머에게 프로그래밍의 관점을 갖게 하고 코드를 어떻게 작성할 지 결정하는 역할을 한다. 새로운 프로그래밍 패러다임을 통해서는 새로운 방식으로 생각하는 법을 배우게 되고, 이를 바탕으로 코드를 작성하게 된다.

최근의 프로그래밍 패러다임은 크게 아래와 같이 구분할 수 있다.

명령형 프로그래밍

무엇(What)을 할 것인지 나타내기보다 어떻게(How) 할 건지를 설명하는 방식

  • 절차지향 프로그래밍: 수행되어야 할 순차적인 처리 과정을 포함하는 방식 (C, C++)
  • 객체지향 프로그래밍: 객체들의 집합으로 프로그램의 상호작용을 표현 (C++, Java, C#)

선언형 프로그래밍

어떻게 할건지(How)를 나타내기보다 무엇(What)을 할 건지를 설명하는 방식

  • 함수형 프로그래밍: 부수 효과를 멀리하고 조합성을 강조하는 프로그래밍 패러다임.
    - 부수 효과를 멀리한다: 순수 함수를 만든다.
    • 조합성을 강조한다: 모듈화 수준을 높인다.

순수 함수의 조건 1: 동일한 인자에 대해서는 항상 같은 값을 반환해야 한다.

function add(a, b) {
	return a + b;
}

/* 동일한 인자(10, 5)를 주면 항상 15를 반환하는 걸 알 수 있다. */
console.log(add(10, 5)) // 15
console.log(add(10, 5)) // 15
console.log(add(10, 5)) // 15

여기서 add는 순수 함수다. 매개변수로 같은 값이 들어온다면 항상 같은 값을 반환하기 때문이다.

let c = 10;

function add2(a,b) {
	return a + b + c; 
}

add2(10, 2) // 22
add2(10, 3) // 23

c = 20;

add2(10, 2) // 32
add2(10, 3) // 32

만일 맨 위의 c라는 값이 상수로 존재한다면, add2는 순수 함수가 될 수 있다. 그러나 c의 값이 바뀌면, add2는 순수 함수가 아니게 된다.

순수 함수의 조건 2: 부수효과를 일으키지 않는 함수

부수효과: 외부의 상태를 변경 혹은 들어온 인자의 상태를 직접 변경하는 함수

let c = 20;

function add3(a, b) {
  c = b;
  return a + b;
}

console.log(c) // 20
add3(20, 30) 
console.log(c) // 30
  • add3 함수는 c 변수, 즉 외부의 상태를 직접 변경하는 함수다. 따라서 이 함수는 순수함수가 아니다.
let obj = { val: 10 };

function add4(obj, b) {
  obj.val += b; 
}

console.log(obj.val); // 10
add4(obj, 20);
console.log(obj.val) // 30
  • add4() 함수는 넘어온 객체(인자)의 value를 직접 변경하는 함수다. add4() 함수는 직접 값을 변경하기 때문에 순수함수가 아니다.

  • 함수형 프로그래밍에서 객체의 값을 변경할 때는, 객체를 복사한 뒤 값을 변경하고 원본 객체는 그대로 둔다. 이를 불변성을 지킨다 라고 한다.

  • 예시를 하나 더 보자.

var obj1 = { val: 10 };
function add5(obj, b) {
	return {
     val: obj.val + b 
    }
}

console.log(obj1.val); // 10
var obj2 = add5(obj1, 20);
console.log(obj1.val); // 10

console.log(obj2.val); // 20
  • 함수 add5 를 보면, obj1을 받아서 key val을 참조만 할 뿐 값을 변경하지는 않는다.

  • add5 함수는 인자로 받은 값을 변경하지도 않고, 그 외의 외부 상태도 변경하지 않는다.

  • 따라서 obj1 객체에는 아무런 변화가 없다.

  • add5 함수가 return 한 값을 obj2 객체에 담아주고, obj2.val을 콘솔에 찍어 확인해보면 obj2.val은 20이 더해진 30이 된 것을 확인할 수 있다.

순수함수

함수형 프로그래밍에서 순수함수란, 초기화 되어 있는 값을 외부 변수의 값을 직접 변경 시키지 않고, 인자로 들어온 값을 직접 변경 시키지 않는 함수를 의미한다.

함수의 평가 시점

함수형 프로그래밍에서는 함수의 평가 시점이 중요하지 않다고 한다. 이게 무슨 뜻인지 알아보기 위해서 다음 코드를 보자.

let c = 10;

function add2(a,b) {
	return a + b + c; 
}

add2(10, 2) // 22
add2(10, 3) // 23

c = 20;

add2(10, 2) // 32
add2(10, 3) // 32
  • add2 함수는 어쩔 때는 22를 return하기도 하고, 32를 return하기도 한다.

  • add2 함수는 외부 변수 c에 의존하고 있기 때문에, 평가 시점이 중요해진다.

소스코드의 평가: 실행 컨텍스트 생성 - 변수, 함수 등의 선언문 먼저 실행 - 생성된 변수, 함수 식별자를 키로 실행 컨텍스트가 관리하는 스코프에 등록 (런타임 이전)

함수코드의 평가 : 함수가 호출되어 전역 코드 실행을 일시 정지하고 함수 코드를 평가하기 시작함

  • 함수 실행 컨텍스트 생성
  • 함수 렉시컬 환경 완성후 실행 컨텍스트 스택에 push (실행 중인 실행 컨텍스트가 됨)
  • 함수 렉시컬 환경 생성 : 생성후 함수 실행 컨텍스트에 바인딩 됨
  • 함수 환경 레코드 생성 : 매개변수, arguments객체, 함수 내부에서 선언한 지역 변수와 중첩 함수를 등록하고 관리
  • this 바인딩 : 함수 환경 레코드의 [[ThisValue]] 내부슬롯에 this가 바인딩 됨 -> 함수 호출 방식에 따라 달라짐
  • 외부 렉시컬 환경에 대한 참조 결정 : 함수 정의가 평가된 시점에 실행중인 실행 컨텍스트의 렉시컬 환경의 참조가 할당됨 (상위 스코프는 어디에서 정의했는가가 결정함, 어디서 호출이 아니라)
  • 함수 정의 평가하여 함수객체 생성시 함수의 상위 스코프를 함수 객체 내부 슬롯 [[Environment]]에 저장, 이것을 외부 렉시컬 환경에 대한 참조가 가리키게 됨

위의 함수코드의 평가에 대한 설명은 Javascript Deep Dive를 참고하였다. 함수형 프로그래밍에서 '함수의 평가 시점이 중요하지 않다' 라는 의미는, 결국 함수가 호출되어 전역 코드 실행을 일시 정지하고 해당 함수를 평가하는 시점이 중요하지 않다는 의미로 이해하였음.

함수코드의 실행

  • 런타임이 시작되어 함수의 소스코드가 순차적으로 실행.
  • 매개변수에 인수가 할당되고, 변수 할당문이 실행되어 지역 변수에 값이 할당
  • 할당문, 함수 호출문 실행 단계
  • 실행중인 실행 컨텍스트에서 식별자 검색 - (찾지 못하면 상위스코프에서 검색)
  • 식별자 결정 -> 검색된 식별자에 값 바인딩

c의 값이 변경됨에 따라 return하는 값이 달라지기 때문에, 순수함수가 아닌 add2 같은 함수는 평가 시점에 따라서 로직이 정해지게 된다.

그러나 순수함수는 언제 실행해도 동일한 결과를 return한다. (=평가 시점이 중요하지 않은 이유)

일급 함수

Javascript에서 함수는 일급함수다. 일급함수란, 함수를 값으로 다룰 수 있다는 뜻이다. 더 자세히 풀어보면

  • 함수를 변수에 담을 수도 있고
const foo = function() {
   console.log("foobar");
}
// 변수를 사용해 호출
foo();
  • 함수가 인자로 넘겨질 수도 있다는 뜻이다.
function sayHello() {
   return "Hello, ";
}
function greeting(helloMessage, name) {
  console.log(helloMessage() + name);
}
// `sayHello`를 `greeting` 함수에 인자로 전달
greeting(sayHello, "JavaScript!");
  • 함수를 반환할 수도 있다.
function sayHello() {
   return function() {
      console.log("Hello!");
   }
}

// 반환한 함수도 호출하기
sayHello()(); // Hello!

add_maker

지금까지 공부했던 순수함수+일급함수의 개념을 조합해 add_maker 라는 함수를 만들어보자.

/* add_maker */
function add_maker(a) {
  	
  	// add_maker 함수가 return하는 이 함수는 클로저다.
	return function(b) {
    	return a + b;
    }
}
  • add_makera를 인자로 받고 함수를 return 하는 함수다.

  • 그 return하는 함수 또한 b를 인자로 받아서 a + b를 return한다.

// add10은 add_maker의 인자로 넘어간 a, 즉 10을 기억하는 클로저다.
var add10 = add_maker(10);

console.log(add10(20)); // 30

var add5 = add_maker(5);
var add15 = add_maker(15);

console.log(add10(20)); // 25
console.log(add10(20)); // 35

지금까지 살펴본 add_maker 함수는 클로저와 일 함수의 개념이 함께 사용되었다.

/* add_maker */
function add_maker(a) {
  	
  // add_maker 함수가 return하는 이 함수는 클로저임과 동시에 순수함수다.
  
  // 그 이유는 a를 단순히 참조만 하고 있기 때문이다. a를 직접 변경하지 않는다. 
	return function(b) {
    	return a + b;
    }
}

함수로 함수 실행하기

예시로 나온 f4 함수의 정의, 호출을 보자.

function f4(f1, f2, f3) {
	return f3(f1() + f2()); 
}


// f1, f2, f3은 모두 순수함수다.
// f4는 순수함수를 조합한 함수다.
f4(
 function() { return 2; }, 
 function() { return 1; },
 function() { return a * a; }
); // 9

함수형 프로그래밍은 지금까지 봤던 add_maker, f4처럼 프로그래밍하는 패러다임이다. 함수형 프로그래밍의 개념을 따라서 코드를 작성하다 보면 위의 함수들처럼 코드를 작성하게 된다. (물론 실제로 코드를 작성한다면f4처럼은 아님, 단순히 형태만 보자.)

요즘 개발 이야기

요즘 개발되는 어플리케이션들은 다음과 같은 특징들을 가지고 있다고 한다.

  • 재미 / 실시간성: 라이브 방송, 실시간 댓글, 협업, 메신저

  • 독창성 / 완성도: 애니메이션, 무한스크롤, 벽돌

    • 벽돌(Masonry 레이아웃): 'masonry'는 '벽돌 쌓기'라는 의미다. 벽돌을 쌓아 올린 모양처럼 동일한 너비를 가진 이미지가 엇갈려 배열되는 레이아웃이 Masonry 레이아웃이다. 카드 형식의 아이템들이 꼬리에 꼬리를 물고 배치되는 Masonry 레이아웃은 Pinterest와 Facebook 등 여러 웹 사이트의 콘텐츠 페이지에 사용된다.

  • 더 많아져야하는 동시성: 비동기 I/O, CSP, Actor, STM 등

    • 게임이나 채팅을 예로 들면, 예전에는 서버 당 들어갈 수 있는 인원을 정해놓았다.

    • 그러나 요즘의 카카오톡, 페이스북 같은 메신저들을 보면 전 세계의 모든 사람들이 동시에 대화를 할 수 있는 기능을 제공한다. 서로 다른 서버에 있는 것이 아니라, 마치 물리적으로 하나의 서버에서 동시에 대화를 진행하게 된다.

    • 따라서, 이러한 동시성에 관한 솔루션들이 크게 늘어나게 되었다.

  • 더 빨라야하는 반응성 / 고가용성

    • 고가용성: 서비스가 죽으면 안 된다. 이게 무슨 말이냐면, 예전에는 서비스가 새벽에 점검 시간 등을 가지며 일시적으로 서비스를 중단 시켰지만, (특히 게임하셨던 분들은 아실 거에요) Facebook같은 서비스는 새로운 기능이 배포되는 와중에도 죽으면 안된다.
  • 대용량 / 정확성 / 반응성: MapReduce, Clojure Reducers 등

  • 복잡도 / MSA: 많아지고 세밀해지는 도구들

    • 이전에는 프레임워크 하나로 거의 모든 기능을 대체했다면, 요즘에는 여러 프레임워크로 여러 기능을 세밀하게 조작하는 방식을 사용한다.

예전에 비해 이런 많은 것들을 고려해야 하기 때문에, 예전보다 개발하기 어려워진 것은 사실이다. 그럼에도 불구하고 생산성을 올려야 했기 때문에, 함수형 프로그래밍이 등장하게 되었다.

저자가 생각하는 함수형 프로그래밍 정의

함수형 프로그래밍은 어플리케이션, 함수의 구성 요소, 더 나아가서 언어 자체를 함수처럼 여기도록 만들고, 이러한 함수 개념을 가장 우선순위에 놓는다.

함수형 사고방식은 문제의 해결 방법을 동사(함수)들로 구성(조합)하는 것

  • 마이클 포거스 [클로저 프로그래밍의 즐거움] 중

함수 개념을 가장 우선순위에 놓는다고? 이게 뭔지 확인해보자.

/* 객체(데이터) 기준 */
duck.moveLeft();
duck.moveRight();
duck.moveLeft();
duck.moveRight();

// 상속을 통해 오리의 이동 방향을 정한다.


/* 함수 기준 */
moveLeft(dog);
moveRight(dog);
moveLeft({ x: 5, y: 2});
moveRight(dog);

// 오리의 이동 방향을 미리 정의하고 오리를 매개변수로 넘겨준다.
  • 객체지향에서는 데이터를 먼저 디자인하고, 해당 데이터에 맞는 메서드를 만드는 식으로 프로그래밍한다. (내가 지금까지 해왔던 방식)

  • 함수형 프로그래밍에서는 함수를 먼저 만들고, 해당 함수에 맞게 데이터셋을 구성하는 식으로 프로그래밍을 하게 된다.

참고 링크

좋은 웹페이지 즐겨찾기