함수형 프로그래밍이란?
- 함수형 프로그래밍은 자료 처리를 수학적 함수의 계산으로 취급하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임
- 순수 함수(pure function) 를 조합하고 공유 상태(shared state), 변경 가능한 데이터(mutable data) 및 부작용(side-effects) 을 피하여 소프트웨어를 만드는 프로세스
- 함수형 프로그래밍은 명령형(imperative) 이 아닌 선언형(declarative) 이며 애플리케이션의 상태는 순수 함수를 통해 전달
함수형 자바스크립트를 위한 기초
- 1급 함수
- 자바스크립트에서 함수는 일급 객체이자 일급 함수다. 여기서 ‘일급’은 값으로 다룰 수 있다는 의미로 다음과 같은 조건을 만족해야 한다.
- 변수에 담을 수 있다.
- 함수나 메서드의 인자로 넘길 수 있다.
- 함수나 메서드에서 리턴할 수 있다.
- 아무 때나(란타임에서도) 선언이 가능하다.
- 익명으로 선언할 수 있다.
- 익명으로 선언한 함수도 함수나 메서드의 인자로 넘길 수 있다.
- 자바스크립트에서 함수는 일급 객체이자 일급 함수다. 여기서 ‘일급’은 값으로 다룰 수 있다는 의미로 다음과 같은 조건을 만족해야 한다.
- 클로저
-
자신이 생성될 떄의 환경을 기억하는 함수
-
자신이 생성될 때의 스코프에서 알 수 있었던 변수를 기억하는 함수
-
이 책에선 클로저의 모든 조건을 만족하는 것만을 클로저로 취급하여 다음과 같이 정의한다.
자신이 생성될 때의 스코프에서 알 수 있었던 변수 중 언젠가 자신이 실행될 때 사용할 변수들만 기억하여 유지시키는 함수 - 클로저인 경우
var a = 10; var b = 20; function f1(){ return a+b; } f1();
f1에서 a와 b를 참조할 수 있다. Node.js에서 js 파일 하나의 스코프는 그로벌 스코프가 아니다. 즉 Node.js에서 특정 js파일에 위 코드가 작성되어있다면 이는 클로저다.
-
클로저가 아닌 경우
function f2() { var a = 10; var b = 20; function f3(c,d){ return c+d; } return f3; } var f4 = f2(); f4(5,7);
f3은 클로저가 아니다. f3는 f2안에서 생성되었고, f3 바로 위에는 a,b라는 지역 변수도 있다. 하지만 f3안에서 사용하는 있는 변수는 a,b가 아니라 c,d이고 두 변수는 모두 f3에서 정의되었다. 즉, f3가 기억해야 할 변수는 하나도 없는 것이다. 또한 f3 자신이 정의한 c,d도 실행되고나면 없어진다. f3는 기억하는 환경도 변수도 없다. 그러므로 클로저가 아니다.
-
클로저 사용 예시
<div class="user-list"></div> <script> var users = [ { id: 1, name: "HA", age: 25 }, { id: 2, name: "PJ", age: 28 }, { id: 3, name: "JE", age: 27 } ]; $('.user-list').append( _.map(users, function(user) { // (1) 이 함수는 클로저가 아니다. var button = $('<button>').text(user.name); // (2) button.click(function() { // (3) 계속 유지되는 클로저 (내부에서 user를 사용했다.) if (confirm(user.name + "님을 팔로잉 하시겠습니까?")) follow(user); // (4) }); return button; // (5) })); function follow(user) { $.post('/follow', { user_id: user.id }, function() { // (6) 클로저가 되었다가 없어지는 클로저 alert("이제 " + user.name + "님의 소식을 보실 수 있습니다."); }); } </script>
위 코드의 map부분을 보면 상태변화에따라 click 이벤트에 리스너를 등록하는 것이 처리되어있지 않다. 일반적인 for문을 돌며, click 이벤트에 리스너를 등록할 경우 i++ 때문에 지역 변수의 값이 먼저 변해서 원치않는 결과가 나온다. 하지만 위의 경우에서 에러가 나지 않는 이유는 map 함수가 동시성이 생길만한 상황이라도, 상태 변화로 인한 부수효과로부터 자유롭게 프로그래밍할 수 있도록 해주기 때문이다. 다음 코드는 위와 같은 내용을 설명한다.
<script> // 1. 흔한 클로저 실수 - 어떤 버튼을 클릭해도 JE var buttons = []; for (var i = 0; i < users.length; i++) { var user = users[i]; buttons.push($('<button>').text(user.name).click(function() { console.log(user.name); })); } $('.user-list').append(buttons); // 2. 절차지향적 해결 - 어차피 함수의 도움을 받아야 함, 각각 다른 이름이 잘 나옴 var buttons = []; for (var i = 0; i < users.length; i++) { (function(user) { buttons.push($('<button>').text(user.name).click(function() { console.log(user.name); })); })(users[i]); } $('.user-list').append(buttons); // 3. 함수적 해결 - 깔끔한 코드는 덤 $('.user-list').append( _.map(users, function(user) { return $('<button>').text(user.name).click(function() { console.log(user.name); }); })); </script>
-
-
고차 함수
-
함수를 인자로 받아 대신 실행하는 함수
function callWith10(val, func){ return func(10,val); } function add(a, b){ return a + b; } function sub(a, b){ return a - b; } callWith10(20,add); callWith10(5,sub);
-
함수를 리턴하는 함수
function constant(val){ return function(){ return val; } } var always10 = constant(10); always10();
-
함수를 인자로 받아서 또 다른 함수를 리턴하는 함수
function callWith(val1){ return function(val2, func){ return func(val1,val2); } } var callWith10 = callWith(10); callWith10(20,add); var callWith5 = callWith(5); callWith5(5,sub); callWith(30)(20,add); callWith(20)(20,sub);
-
-
함수를 리턴하는 함수와 부분 적용
-
불변성
- 함수형 프로그래밍의 핵심 개념
- 변경할 수 없는 객체란 객체를 생성한 후에 수정할 수 없는 객체
- 데이터 변경이 필요한 경우, 원본 데이터 구조를 변경하지 않고 그 데이터를 복사본을 만들어 그 일부를 변경하고, 변경한 복사본을 사용해 작업을 진행
- 선언형
- 명령형 프로그램은 원하는 결과를 얻기 위해 특정 단계를 설명하는 코드 라인을 사용 => 어떻게(How) 하는지?
- 선언적 프로그램은 흐름 제어를 추상화하고, 대신에 데이터 흐름 을 설명하는 코드 라인을 사용 => 무엇을(What) 하는지?
- 구문보다는 표현식을 사용하라