10분 안에 함수형 프로그래밍 맛보기

BSK·2022년 3월 14일
0
post-thumbnail

Functional Programming in 40 minutes, Russ Olsen, GOTO2018 를 각색한 제 프레젠테이션(private link)을 각색한 글입니다.

본 문서는 데스크탑/랩탑에 최적화되어 있습니다. 모바일에서는 레이아웃이 예쁘지 않을 수 있습니다.

서론

본문을 설명하기에 앞서, 우리가 개발이라는 것을 할 때에 볼 수 있는 가상의 시나리오를 하나 가져왔습니다.

이것이 개발자다! (희망편)

완벽히 갖춰진 테스트 코드!(모두 PASS)
요구사항 분석 & UML 설계와
설계대로 작성하는 코드!
우아하게 해결된 git conflict!
이대로만 된다면 완벽하진 않아도
그럭저럭 행복하게 잘 돌아감!

물론 이런 일들은 꿈과 같습니다. 조금 더 우리의 삶에 익숙한 풍경을 가져와 봤습니다.

이것이 개발자다! (절망편)

애자일한 요구사항
테스트코드가 있어요?
배가고프면 코드를 짜요 스파게티 한그릇 뚝딱
git-ar hero
머지된 깃 그래프처럼 꼬인 허리와 목
완벽도 행복도 멀고 잘 돌아가지도 않음

요구사항은 변하고
새 모듈을 기워맞추고
설계는 뒤집어지고
의존관계는 꼬여가고...

이러다 보면 점점 mess🤮 를 만들게 됩니다. 예를 들면 ~할때 빼고는 잘 돌아가요 같은 코드요.

이 feature 정말 잘 동작해요. 근데 눈오는 화요일에는 안되요
( + 왜 안되는지도 모름 )

어쨌든 이런 상황들을 상대하는 것이 우리의 일이니 처리를 하려 하지만, 버그🐛는 점점 깊숙히 들어가고, 우리는 breakpoint⛔와 stdout 출력을 남발하며 이 버그를 찾아야 합니다( console this , console that).

사실 이는 자연스러운 상황입니다. 왜냐면 우리가 접하는 언어들의 스펙조차 이런 것들이 있거든요.

java (https://docs.oracle.com/javase/tutorial/java/generics/genTypes.html)
During the type erasure process the Java compiler erases all type parameters and replaces each with its first bound if the type parameter is bounded, or Object if the type parameter is unbounded

ruby(https://medium.com/@tjoye20/ruby-access-control-basics-public-vs-private-vs-protected-methods-7788b26e04a7)
Protected methods: a protected method is similar to a private method, with the addition that it can be called with, or without, an explicit receiver, but that receiver is always self(it’s defining class) or an object that inherits from self.

Cpp(https://www.tutorialspoint.com/cplusplus/cpp_friend_functions.html)
A friend function of a class is defined outside that class’ scope but it has the right to access all private and protected members of the class. Even though the prototypes for friend functions appear in the class definition, friends are not member functions.

좋아요. 함수형 뭐시기를 배우면 이런 문제를 벗어날 수 있나 보죠?
그래서 함수형 프로그래밍이 무엇입니까?

무엇이 함수형 프로그래밍인가?

구글링 1종 자격증이 있는 우리들이 새로운 정보를 접하는 주된 방법인, 구글링으로 함수형 프로그래밍 에 관련된 내용들을 보면, 우리들을 겁주는 글들이 많습니다.

FORGET EVERYTHING ABOUT PROGRAMMING
Charles Scalfani, 2016 ← (btw, 매우 좋은 글이에요. 읽기를 추천드립니다)

보자마자 강력한 워딩을 마주합니다. 그럼 N년차의 경력를 가지고 있는 우리 모두 함수형 뭐시기를 배우려면 완전한 신입으로 돌아가야 할까요..? 그 정도로 함수형 프로그래밍이 이득이 될까요? 우리가 알고 있던 객체지향의 세계에서 들고 갈 수 있는 건 없을까요..?

그러면 우리가 (객체지향 혹은 그 외에도 이미 알고 있는)프로그래밍에서 알고 있는 것들을 정리해 봅시다.

데이터
타입
불리언
문자열
인덴트
숫자
변수명
자료구조
버전관리
.... 등등등

함수형 프로그래밍도 변수명이나 인덴트는 쓰겠죠? 문자열이나 숫자나 변수/함수명 규칙도요. 자료구조도 있을 것 같아요. 그런데도 정말 우리가 알고 있는 모든 것들을 잊어버려야 할까요? 사실 우리가 해야 하는 것은 우리가 알고 있는 것을 리팩토링하기 아니었을까요?

수학에서의 함수

다시 눈 오는 화요일에만 안되는 우리의 비즈니스 코드 로 돌아와서, 사실 이러한 고민을 하고 있던 다른 사람들이 있었습니다. 바로 수학자👨‍🎓들이죠.


여기 알프레드 노스 화이트헤드와 버트런드 러셀이라는 두 수학자가 저술한 Principia Mathematica 라는 책이 있습니다.

이 책에서는 1 + 1 = 2 것을 증명하기 위해 무려 362 페이지동안 증명을 합니다. 이 정도로 견고한 체계가 있다면, 우리 프로그래머들이 저 수학자들로부터 빌려올(*훔쳐올) 만한 것들이 없을까요? 👀👀

본문의 주제가 함수형 프로그래밍이라니까 생각나건데, 수학의 시스템에서 함수 라는 것이 있습니다. 그런데 우리 프로그래머들도 함수라는 게 있잖아요! 그 왜 서브루틴이니, 메소드이니 하는 것들 말이에요.

그리고 이 음모를 듣던 지나가던 수학자가 속삭입니다.

중학교때 배웠던 함수 f(x) 기억나세요? 정의역, 공역, 치역.. 이런 것들이요

우리가 알던 개념을 리팩토링 하는 관점에서, 우선 이 함수라는 개념을 리팩토링 해봅시다. 수학에서의 함수는 집합 A 와 집합 B를 연관짓는 것이었습니다. A 가 들어가면 f(A) 가 항상 나오는 형태였죠.

반면 우리가 알고 있는 함수는 어떤가요? 그냥 코드 덩어리 아닌가요?

  • 파일을 지우기
  • 데이터베이스 업데이트하기
  • 인스턴스의 필드 갱신하기

이런 과정들은 반환값이 없는 경우가 있습니다. 우리는 그냥 함수명을 부르면 그 안에서는 무슨 일이든 마음대로 들어가고, 우리는 그런 코드 뭉치 보따리를 묶은 뒤에 함수명이라는 이쁜 이름을 붙일 뿐입니다.

다시 수학에서의 함수를 보면,

  • 함수라는 것은 늘 영원했습니다.
    f(x) = x * 2 라는 함수에 x = 5 를 넣으면 언제나 10 이라는 값을 반환했습니다.
    화요일 목요일, 윤년인 추수감사절에는 7 같은 걸 반환하는 게 아니라요.
  • 결과값이 10이 되더라도 입력값인 x는 5인 상태 그대로입니다.
  • 만약 다른 일을 하는 것이 있다면, 그건 다른 함수가 됩니다.

그렇게 일단 곁눈질로 훔쳐 온 함수의 개념을 다음과 같이 정의해 보겠습니다.

함수

  1. 뭔가 들어가요
  2. 뭔가 나와요
  3. 들어가는 것을 바꾸지 않아요(side effect 가 없어요, 오로지 effect 뿐!)
  4. 같은 입력에 대해서 같은 출력을 보장해요!

오케이! 곁눈질로 잘 베껴온 것 같군요.
사실, 이런 함수들을 따로 순수 함수 라고 불러요!

let x = ['a','b','c']
let y = a(x)
console.log(x)// ['a','b','c'] 입니다!! 

위의 코드를 실행했을 때에, 어떤 임의의 함수 a 에 인자로 x를 넣었을때, 함수를 호출한 라인 다음에서의 x 값은 함수에 적용되기 이전과 동일합니다.

a가 너무 간단해 보이나요? 사실 복잡한 정도는 상관이 없습니다.

let x = ['a','b','c']
let y = c(a(x)) + b(e(x)+d(x)) - t(x) * z(x)
console.log(x)// ['a','b','c'] 입니다!! 

이를 데이터가 불변 하다고 합니다. 한번 선언되었다면 영원히 변하지 않는 것이죠. 컬렉션과 같은 구조에서 하나의 멤버만 바꾸게 된다고 해도, 원본 컬렉션을 바꾸지 않은 채로 새 컬렉션을 반환합니다.

let x =       [‘a’,’b’,...’m’,...,’y’,’z’]
let y = a(x)//[‘a’,’b’,...’M’,...,’y’,’z’]

오잉? 근데 100만개의 요소가 있는 컬렉션에서 하나의 요소만 바꾸는 작업이 많다면 매번 어마어마한 메모리와 연산 낭비가 발생하지 않나요?! 할당과 값 복사하는 비용이 엄청 비쌀 것 같아요!

세상은 넓고, 똑똑한 사람들은 그만큼 많아서 이미 이 앞길을 걸었던 분들은 persistent data structure 라는 개념을 만들었습니다. javascript 에 익숙한 분들이라면, immutablejs 를 보세요! 이들은 이런 복사하는 과정을 매우 효율적이고, 영리하게 수행합니다. 마치 버전관리의 자료구조 버전과 같이 말이죠!

또한 순수함수들은 입력에 대한 결과값들이 항상 동일하기 때문에, 메모이제이션 을 통해서 매우 강력하게 최적화를 수행할 수 있습니다. 입력값에 대한 결과값을 기억하고 있다가, 한번 입력되었던 입력값이 있다면, 기억하고 있던 결괏값을 반환하는 것이죠. 이는 함수의 비용이 얼마던 간에, 두번째 연산은 O(1) 에 수행할 수 있는 것입니다.

함수형 프로그래밍이란, 이렇게 같은 입력에 대해서 같은 출력을 제공하는 순수함수들과, 불변 데이터들을 이용하여 프로그램을 작성하는 것입니다.

마무리

함수형 프로그래밍은 마법같은게 아닙니다. 세간에 너도 나도 함수형 프로그래밍을 써서 행복해졌다고는 하지만, 함수형 프로그래밍이라도 여전히 레거시 코드나 죽은 코드가 있을 수 있고, 나쁜 코드와 버그가 있을 수는 있습니다. 그리고 객체지향 프로그래밍으로도 충분히 근사한 코드를 쓸 수 있고, 상속을 통한 코드 재사용과 모델링은 여전히 많이 쓰이는 기법입니다.

그럼에도 불구하고 함수형 프로그래밍은 멀티스레딩에서의 스레드 안전성을 보장할 수 있고, 단위 함수를 기반으로 작성되기 때문에 단위 테스팅에 매우 용이하고, 순수 함수를 이용하기 때문에 메모이제이션을 통한 매우 강력한 최적화를 사용할 수 있으며, 발견한 버그의 재현과 추적에 용이합니다.

프로그래밍에서의 DoD(Data-oriented Design)과 TDD(Test-Driven Development), 선언형 프로그래밍과 같은 최근 대두되고 있는 패러다임의 경향의 기반에는 함수형 프로그래밍이 있습니다. 모던 프로그래밍 언어들은 하나둘씩 함수형 프로그래밍 언어들의 기능들을 도입하고 있는 추세이기도 합니다.

최근 주위에서 들려오는 함수형 프로그래밍의 도입 사례를 들으면서 망설이셨다면, 작성중인 함수를 하나 둘 씩 순수 함수로 리팩토링 해가면서 이번 기회에 한번 찍먹 해 보는 것은 어떨까요?
읽어주셔서 감사합니다.

profile
Biscuit Basket

0개의 댓글