본문 바로가기

Web/JavaScript

함수형 자바스크립트 9. 지연평가와 함수형 프로그래밍 요약

지금까지 강의를 들으면서 평가 시점이란 단어가 무슨 의미인지 생각하지 않고 넘어갔다.

그냥 단순히 값을 대입하는 시점? 함수를 수행하는 시점? 이라는 느낌으로 넘어가도 이상함이 없었기 때문이다.

그런데, 문득 지연 평가에 대한 내용을 듣고 여기서 평가가 무슨 의미인지 궁금해졌다.


평가란 무엇인가?

evaluation이란 단어는 수학에서 값을 구함이라는 명사이다.

함수를 평가한다 = 함수를 수행해서 결과를 구한다 정도로 이해해도 될 것 같다.

확실하지 않으니 what is evaluation of function or variable 라는 내용으로 검색해보았다.

해당 문서에서 함수의 평가란 변수를 주어진 숫자 또는 표현식으로 대체하는 것을 의미한다고 한다.

f(x) = 2x + 4 함수를 , x = 5로 평가한다면, x에 주어진 5를 대입한다.

f(5) = 2*5 + 4 = 14

f(5) = 14

결국, 함수의 평가 시점이란 함수에 파라미터를 넘기고, 수행하는 시점이라고 봐도 무방하다.


엄격한 평가



val1

val2

val3

val4

val5

val6

map

->

->

->

->

->

->

filter

->

->

X

->

X

->

map

->

->


->


->

reject

->

->


X


->

take(2)

O (1개)

O (2개)




->


엄격한 평가는, 말 그대로 순서에 맞게 엄격하게 평가한다.

map에서 각각을 모두 평가하여 val ~ val6을 평가한다.

다음 filter, map, reject에서도 마찬가지로 모두 평가한 뒤, take(2)에서 2개를 평가하고 끝낸다.



지연 평가



val1

val2

val3

val4

val5

val6

map



filter

X



map




reject


X



take(2)


O (1개)


O (2개)




지연 평가는 첫 번째 map에서 val1 ~ val6 모두를 평가하지 않는다.

우선 val1을 평가한 뒤, filter로 내려와 다시 평가한다.

val1은 filter에서 제거되므로 다시 map으로 돌아와 val2를 평가한다.

val2는 map, filter, map, reject를 모두 거쳐 take(2)에서 평가되며, take에는 1개의 평가가 축적된다.

val3은 마찬가지로 reject에서 제거되어 val4가 take(2)에서 평가되며, take에는 2개의 평가가 축적되어

평가가 종료되므로 val5, val6은 평가할 필요가 없어진다.


지연 평가 예제


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

/* 엄격한 평가 */

var mi = 0;

var fi = 0;

_.go(

   _.range(100),

   _.map(function(val) {

       ++mi; // 100번 루프

       return val * val;

   }),

   _.filter(function(val) {

       ++fi; // 100번 루프

       return val % 2;

   }),

   _.take(5), // 5개 꺼내기

   console.log

);

console.log(mi, fi); // 100 100

/* 지연 평가로 성능 높이기 */

/* take(5) 에서 5개를 가져오는데 필요한만큼만 루프를 돈다 */

/* 하나를 제곱하면 바로 다음 필터에 넣어보고, true로 평가되면 take에 넣어서 하나를 축적,

  5개가 축적될 때 까지 반복한다 */

var mi = 0;

var fi = 0;

_.go(

   _.range(100),

   L.map(function(val) {

       ++mi; // 10번 루프

       return val * val;

   }),

   L.filter(function(val) {

       ++fi; // 10번 루프

       return val % 2;

   }),

   L.take(5),

   console.log

);

console.log(mi, fi); // 10 10

cs


partial.js에서 네임스페이스를 _대신 L로 변하면 지연평가가 되며, 나머지 연산은 실행되지 않도록 알아서 최적화를 해준다. 이는 map, filter 등이 순수함수이기 때문에 가능하다.

평가 시점이나 평가 순서와 상관없이 동일한 결과를 가져올 수 있기 때문이다.


함수형 프로그래밍 요약

함수형 프로그래밍은 언어 위에 있는 패러다임으로 특정 언어에 국한되지 않는다.

다음은 강의 내용을 요약한 것이다.

1. 함수를 되도록 작게 만들기

- 함수 단위로 프로그래밍을 하기 때문인 것 같다.


2. 다형성 높은 함수 만들기(고차함수 + 보조함수)

- 고차 함수로 로직을 선택하고, 보조 함수로 세부 내용을 구현한다.


3. 상태를 변경하지 않거나 정확히 다루어 부수 효과를 최소화 하기

- 부수효과가 없을 수는 없다.

- 웹 브라우저에서는 브라우저의 상태를 변경해야 하기 때문에, 특히 자바스크립트는 더욱 그러하다.

- 부수효과는 코드의 최종 목적이 된다.

- 함수형 프로그래밍이란 결국엔 부수효과까지 가기 전의 과정에서는 상태를 변경하지 않는

식으로 프로그래밍하다가 맨 마지막에 목적이 부수효과가 되도록 한다.


4. 동일한 인자를 받으면 항상 동일한 결과를 리턴하는 순수 함수 만들기

- 순수 함수의 중요성은 계속해서 공부한 내용이다.


5. 복잡한 객체 하나를 인자로 사용하기보다 되도록 일반적인 값 여러 개를 인자로 사용하기

- 복잡한 객체란 여러 속성을 가진 객체를 의미하는 것 같다.

- 그런데, 자바에서는 항상 VO와 같은 모델을 만들고 인자로 주고 받지 않나 ..?


6. 큰 로직을 고차 함수로 만들고, 세부 로직을 보조 함수로 완성하기


7. 어느 곳에서든 바로 혹은 미뤄서 실행할 수 있도록 일반 함수이자 순수 함수로 선언하기

- 평가시점과 상관없이 동일한 결과를 리턴할 수 있다.

- 병렬성, 동시성에 강하다.


8. 모델이나 컬렉션 등의 커스텀 객체보다는 기본 객체를 이용하기

- 앞서 5번에 대한 설명인 것 같다.

- 이유는 map, filter, reduce는 커스텀 객체와도 사용될 수 있지만, 기본적으로

기본 객체를 활용 할 때 다양한 조합이 가능하다.


9. 로직의 흐름을 최대한 단방향으로 흐르게 하기

- 위에서부터 아래로 쭉 흘러나가면서 프로그래밍 하라.

- 파이프라인에서 공부한 내용이다.


10. 작음 함수를 조합하여 큰 함수 만들기

- 수집하기에서 _map 대표함수를 이용해 _values, _plucks를 만들었다.

- 이러한 음 함수를 조합해서 큰 함수를 만들어 프로그램을 완성하는 것이 함수형 프로그래


병렬성, 동시성

순수 함수는 동시에 다른 쓰레드에서 평가를 해도 상관없고, 어느 곳이나 어느 시점에 평가해도 상관없기 때문에 동시성, 병렬성에 유리하다.



map

1

2

3

4

5

6

7

8

9

10

mapper

mapper

mapper

mapper

mapper

mapper

mapper

mapper

mapper

mapper

result


클로저

pmap - 서로 별도의 쓰레드로 병렬 동작

1

2

3

4

5

6

7

8

9

10

mapper

mapper

mapper

mapper

mapper

mapper

mapper

mapper

mapper

mapper

result



위에서 데이터 1,2,3...10은 서로 연관이 없다.

각각의 mapper에서 일어나는 결과는 순수 함수이기 때문에, 동일한 결과를 만들 수 있다.

map 함수에 넣는 배열의 양이 굉장히 크다면 클로저 언어에서는 pamp으로 바꾸기만 해도 성능의 큰 이점을 얻을 수 있다.

또, 강의에서 클로저의 reducer를 예제로 보았다.

map, remove 함수를 r/map, r/remove를 이용해 병렬로 변경해준 것 만으로 성능이 2배 증가했다.