JS로 함수형 프로그래밍 배우기

jhj46456·2020년 8월 21일
1

📌 https://www.youtube.com/watch?v=e-5obm1G_FY

이 글은 위의 JSConf 2016의 Anjana Vakil 영상을 참조하여 작성하였습니다.

함수형 프로그래밍이 뭐에요?

정답은 여러가지가 있습니다.

  1. 프로그래밍 패러다임
    다른 패러다임의 예로 명령형 프로그래밍이 있습니다.
    객체지향 프로그래밍객체가 있고 거기에 메소드가 있어서 그것을 변경하는 방법입니다.
    함수형 프로그래밍함수가 제일이에요. 나중에 설명할게요.
  2. 코딩 스타일
    코드를 정리하고 작성하며 프로젝트에 접근하는 방식으로 일종의 사고방식이라고 볼 수 있어요.
    문제를 고민하고 과제를 풀이하는 방식이죠. 최근들어 섹시한 트렌드기도 해요.
    주목을 많이 받고있죠.

JS에서 함수형 프로그래밍의 역할은 뭐에요?

객체지향 Javascript를 처음 배우면 너무 어렵습니다. 프로토타입과 상속이 대체 뭐냔 말이죠.

그리고 객체지향 프로그래밍에서 버그나 예상치 못한 문제를 겪어보신 분들이 많습니다.

너무 복잡하고 어려워서 JS를 배운다면 객체지향은 미뤄놓고 함수형을 배우는 편이 어쩌면 쉬울지도 모릅니다.

함수형 JS는 더 안전하고, 덜 복잡하며, 디버그가 조금 쉽고, 프로젝트 작업에서 유지 관리가 더 쉽습니다.

지금까지 글을 읽고 함수형 프로그래밍에 관심이 생기셨다면 JS를 어떻게 활용할지 궁금하시겠죠?

JS로 어떻게 구현해요?

어떻게 할까요? 어떤 의미일까요?

당연하게 들리겠지만 기본적으로 함수형 프로그래밍은 그 이름대로 모든 걸 함수로 실행해요.

프로그램의 전부를 함수로 표현하죠. 함수는 말할 것도 없이 INPUT을 받아 OUTPUT을 내는 거에요.

그러니 프로그램 내에서 INPUT과 OUTPUT의 데이터 흐름을 고려해야 해요.

명령형처럼 객체가 어떻게 상호작용하고 조작하는지 등 단계별로 생각하면 안됩니다.

함수형으로 표현하는 법을 고민해야 해요.

# Not functional
var name = "Larry";
var message = "Hi, I'm ";
console.log(message + name);
=> "Hi, I'm Larry"

이건 비함수형으로 화면에 메시지를 띄우는 방법입니다.
일종의 명령형 프로그래밍입니다.
먼저 이거, 다음엔 이거 순서가 있어요. 함수는 사용되지 않았죠.
INPUT과 OUTPUT 개념으로 표현하지 않았어요.

똑같은 내용을 함수형으로 풀어볼게요. 아주 간단한 예시지만 보세요.

# Functional
function message(name) {
  return "Hi, I'm " + name;
}
greet("Larry");
=> "Hi, I'm Larry"

함수는 매개변수 name을 취하고 name 앞에 "Hi, I'm"이라는 문자열을 반환합니다.
그래서 message를 입력하면 문자열 "Larry"가 그걸 INPUT으로 받아서 "Hi, I'm Larry"라는 OUTPUT이 나옵니다.

순수 함수

또 함수형 프로그래밍의 또 다른 특징 중 하나는 Side effects(부작용)을 피해서 pure function(순수 함수)만 사용한다는 것이에요.

부작용이란 함수가 주어진 INPUT에서 OUTPUT을 계산하여 반환하지 않는 상황을 의미합니다.

예를들면 Console에 무언가를 인쇄하는 것은 return OUTPUT이 아니죠. 다른 작업입니다. 함수와 관련된 다른 작업입니다.

아니면 OUTPUT 계산에 전역 변수를 사용하는 함수라서 INPUT에 의존하지 않아도 된다면 순수 함수가 아니에요.

함수 외부의 것을 가지고 함수의 결과에 영향을 주는 거니까요.

side effects가 없는 순수함수란 쉽게 말해 이런 것들이에요.

기본적으로 함수가 INPUT만을 받아 OUTPUT을 계산하고 return해야 해요. 그래야 순수해요.

예시를 볼게요.

# Not pure
var name = "Larry";
function message() {
  console.log("Hi, I'm " + name);
}

전역 변수 name이 있어요. 함수에 사용되고 있죠. 변수가 사용되고 INPUT은 없어요.
name은 함수의 인자가 아니고 전역 상태에서 뭔가를 읽어오고 있어요. 순수하지 않죠.
그리고 여기서 원하는 것은 함수의 반환 값이 아니라 순수하지 않아요.
여기서 원하는 것은 반환값 대신에 뭔가를 바꾸는 것입니다. 이 경우엔 Console에 인쇄하는 것이죠.

순수함수는 이런 식이에요.

# Pure 
function message(name) {
  return "Hi, I'm " + name;
}

이 함수의 OUTPUT에 영향을 주는 것은 INPUT 뿐이에요.
우리가 주는 매개변수는 OUTPUT만을 반환하게 합니다.

함수형 프로그래밍특징 중 하나가 최대한 순수하게 생각해야 한다는 거에요.

고차 함수

함수형 프로그래밍의 다른 특징으로는 **고차 함수(higher-order functions)**가 있어요.

다른 함수를 INPUT으로 받거나 함수를 OUTPUT으로 반환해야 하는 함수에요.

함수를 일종의 객체로 취급하는 거죠. 함수를 다른 함수에 전달할 수 있어요.

함수에 함수가 있는 구조를 만들 수 있어요. 그게 바로 고차 함수에요. 함수형 프로그래밍에서 자주 쓰입니다.

예시를 하나 보여드릴게요.

function makeAdjectifier(adjective) {
  return function (string) {
    return adjective + " " + string;
  }
}
var coolifier = makeAdjectifier("cool");
coolifier("conference");
=> "cool conference"

형용사를 주면 함수를 반환합니다. 문자열이나 숫자가 아니라 함수를 반환합니다.
주어진 문자열에 형용사를 붙이는 함수 말이죠.

이런 특징을 염두해야 합니다. 함수형 코드 작성에서 중요한 부분이거든요.

다른 패러다임에서 쓰는 몇 가지 요령을 안쓰려면 고차 함수가 필요합니다.

피해야할 버릇

  1. 반복
    for이나 while같은 반복을 피하셔야 합니다.
    리스트에 있는 모든 항목을 이용하는데 익숙하잖아요.
    함수형에선 그러진 않고 map, reduce, filter같은 고차 함수를 써요.
    INPUT으로 원하는 방식의 리스트만이 아니라 함수도 받는 함수에요.

이 고차함수를 잘 설명한 이미지가 있습니다.

📌 출처 : https://www.datasciencecentral.com/forum/topics/what-is-map-reduce

데이터를 자르고 싶다면 썰기나 자르기, 다지기 같은 함수를 씁니다.
목록의 각 항목에 함수를 적용할 거에요.

  for vegetable in sandwich ingredients
  

이 방법은 하나씩 전부 자릅니다.

하지만 함수형에서는 map을 써서 재료 목록과 '다지기' 함수를 줍니다.
그러면 모든게 다져진 새 목록이 나오죠.

여기서 Reduce 함수로 목록의 모든 항목을 일정 방식으로 결합합니다. 맛있는 샌드위치를 위해서요.

filter는 오이가 싫다면 오이가 아닌 것만 통과시키는 함수에요.

이렇게 고차 함수를 사용해서 'for'이나 'while'의 반복을 피할 수 있어요.

물론 map, reduce, filter에 대한 명확한 설명은 아닙니다. 온라인에 정보가 많이 있습니다.

for 대신에 사용한다는 것만 기억하세요.

  1. 데이터의 변형

여기서 변형이란 객체 위치 변경을 뜻합니다. 불변 데이터라고 하면 위치를 변경할 수 없는 데이터를 뜻해요.

일단 두면 고정돼죠. 절대 변하지 않아요.

  # Mutation (나쁨)
  var rooms = [1, 2, 3];
  rooms[2] = 4;
  rooms;
  => [1, 2, 4];

index[2]를 임의로 변경했더니 rooms가 실제로 변경되었습니다. 여전히 변수 rooms에 저장되어 있습니다.
하지만 있던 것을 바꿨어요. 문제 소지가 있기 때문에 함수형에서는 피하는 방법입니다.

이렇게 객체 지향적으로 접근할 때 문제가 생기는 이유는 의도치 않게 무언가를 바꿀 수 있기 때문이에요. 헷갈릴 수 있죠.

rooms가 초기 상태라고 생각하고 다른 코드 어딘가에서 rooms 배열이 변경된 것을 몰랐다면 문제가 생길지 모릅니다.
코드에 버그가 생겨도 찾기가 정말 힘들 수 있죠.

여기 rooms는 올바른데 저기 rooms는 틀리니까요.

더 나은 방법은 모든 데이터를 불변 데이터로 생각하는 거에요.

   # No mutation (좋음)
   var rooms = [1, 2, 3];
   var newRooms = rooms.map(function (room) {
     if(room === 3) { 
       return 4; 
     } else {
       return room;
     }
   });
   newRooms => [1, 2, 4];
   rooms => [1, 2, 3];
 > 변형 없이 rooms에 여전히 1, 2, 3이 있습니다. 그걸 대체하는 대신에 map 함수를 써서
 > 새로운 rooms 배열을 만듭니다.
 

mapping한다는 것은 map에 함수를 줍니다. 리스트의 각 room을 보고 3이 있으면 3 대신 4를 반환합니다.

rooms를 불변 데이터로 취급해서 변경하지 않았어요. 함수형 프로그래밍에서 중요한 부분이에요.

말씀드렸듯이 버그가 생기는 걸 막을 수 있어요.

  1. 영속 데이터 구조

불변성의 문제는 배열 같은 것을 불변한다고 가정했을 때, 계속 사본이 생긴다는 것이죠.

리스트에서 room 하나를 변경하려 해도 배열 전체를 새로 만들어야 하죠.

객체가 크고 복잡할 수록 효율이 떨어지게 됩니다. 하나만 바꾸려 해도 변경되지 않은 걸 포함해 전체를 복사하니까요.

함수 세계에서 이런 효율 문제 해결법으로 인기있는 방법이 영속 데이터 구조에요.

만약 1, 2, 3이 있는 배열이 있고 변형 가능하다면 3을 없애고 1, 2, 4로 대체할 순 있지만, 그건 피할거에요.

그래서 새 배열에 복사해요. 1을 복사하고 2를 복사하고 3은 복사하는 대신 4를 넣어요 1, 2, 4

하지만 이 방법도 시간이 오래 걸려요.

그렇다면 이 배열을 트리로 표현하면 어떨까요? 트리의 각 리프 노드가 지금 만드려고 하는 모양이 되도록이요.

뭔가를 변경하고 싶다면 배열 전체를 복사하지 않고 새로운 노드를 만들어서 rooms 트리의 특정 노드에 연결하면 끝이에요.

이제 1, 2, 4를 포함하는 새로운 데이터 구조가 생겼습니다. 새로 만들지 않고도요.

필요한 것만 새로 추가했죠. 리스트의 기존 구조를 재사용했어요.

이런 방식을 구조 공유라고 합니다.
기존 버전과 새 버전을 부분적으로 공유해 벡터에서 뭔가를 추가, 변경, 이동하는 작업이 훨씬 효율적이게 됩니다.

함수형 프로그래밍에서 중요한 불변 데이터를 업데이트하는 데 오랜 시간과 공간이 들지않게 해줍니다.

현재는 Immer.js , Immutable.js 라이브러리 등으로 관리를 합니다.

나열한 라이브러리 모두 불변 데이터 구조를 사용하는 방법 중 일부에요.

질문

Q. 저희 학교 교수님들은 항상 객체 지향 프로그래밍이 함수형 프로그래밍보다 낫다고 하셔서 전 그게 낫다고 생각하거든요. JS에서만 함수형 프로그래밍이 좋다는 건가요? 아니면 C같은 다른 언어에서도요?

A. 좋은 질문입니다. 프로그래밍 패러다임의 철학으로 들어가는 문제네요.
왜 어떤 사람들은 객체 지향이 나쁘고 함수형이 최고라는데, 어떤 사람은 함수형이 나쁘고 객체 지향이 좋다고 외치는지 궁금했어요.
제가 내린 결론은 다른 것보다 뛰어나거나 모자란 패러다임은 없다는 거에요. 서로 장점과 단점이 다르고 상황에 따라서 어떤 문제를 해결할 때 최고의 도구가 될 수 있죠.
예를들어 컴퓨터에서 한 번 실행되고 말 스크립트를 빨리 짜야 한다면 그냥 명령형으로 짜는게 편해요. 유지할 필요 없이 당장 실행하면 되니까요. 그 경우 명령형 프로그래밍이 적절한 패러다임이에요.
실세계 객체를 모델링해서 객체의 상호작용이나 작업을 정해야 한다면 객체 지향 사고 방식이 알맞을 거에요.
하지만 앞서 설명한 그런 문제를 피해야 하는 상황이라고 할게요. 그냥 필요해서 객체를 쓰니까 각종 부작용과 찾기 힘든 버그가 발생하는 경우요.
그렇다면 데이터 함수와 여러 함수를 쓰는 파이프 라인을 고려해야 하죠. 여러 변형을 거치지만 데이터 자체가 변형되지 않고 기존 INPUT에 따라 새로운 OUTPUT이 나오게요.
함수형 프로그래밍을 그럴 때 사용하면 장점이 되게 많아요. 한 가지 작업만 하는 작은 함수로 쪼갤 수 있죠. 뭔가를 바꾸고 싶으면 Class 구조 전체를 바꾸지 않고 바꾸고 싶은 작업을 하는 함수 하나만 바꾸면 돼요.
장점만큼 단점도 있죠. 항상 객체 지향보다 뛰어나진 않아요.
객체 지향도 마찬가지고요. 상황에 따라 달라요.

0개의 댓글