Engineering/Software Engineering

[FP] 함수형/선언형에 대한 고찰

MB Kyle K. 2025. 11. 19. 15:24

프롤로그

요즘 AI Transformation으로 IT 사업이 새로운 국면으로 넘어가는 것 같다. 그래서 최근에 이와 관련된 글도 작성했었다. 그런데 이번에는 함수형과 선언형에 대해서 얘기해보고 싶다. 이는 오래된 주제이기도 하고, 필자가 2016년부터 깊게 파고 있는 주제이다. 필자가 짠 코드가 의도된 대로만 동작하도록 제어하고 싶은 맘에서 학부 시절부터 디자인 패턴과 소프트웨어 공학에 심취했던 나였다. 그런 필자에게 새롭게 다가왔던 개념은 함수형이었다.
 
함수형은 내가 작성한 코드가 내가 의도한 대로 코드가 동작하도록 하는 강력한 무기가 되었다. 발생할 수 있는 예외 케이스 또한 내가 정의한 에러 코드를 통해서 예측 가능한 범주 내로 가이드해주었다. 진정한 함수형/선언형 코드를 작성했다면, 심지어 코드 내부적 이슈에 한해서 디버깅도 불필요하게 만들 수 있다. 이러한 함수형의 등장 배경과 필요성에 대해서 얘기해보고자 한다.
 

등장 배경

왜 등장했는지 혹은 왜 필요한지 등을 생각하다보면, 어떤 기술을 어떻게 써야 하는지가 분명해질 때가 있다. 그래서 어떤 기술 혹은 코드를 접근할 때는 왜 그런지에 대해서 자주 생각에 빠지곤 한다. 그럼 함수형 프로그래밍은 왜 등장한 것일까? 개인적인 의견이라서 사실과 다를 수도 있지만, 함수형 언어의 초기 등장에 대해서는 건너뛰고 그 이후부터 얘기해보자.
 
예전에는 CPU라는 것에 두뇌가 하나였다. 다시 말해 싱글코어였다. 하지만 자원을 효율적으로 쓰고 싶었던 사람들은 여러가지 일을 동시에 혹은 동시에 하는 것처럼 보이게 하고 싶었다. 이를 위해서 context switching이 가능하도록 instruction set을 설계했고, 더 나아가서 Thread라는 실행 흐름을 나누고 제어할 수 있도록 하였다. 하지만 이는 동시에 하는 것처럼 보이도록 짧은 time slot을 빠르게 번갈아가면서 사용하는 구조였다. 시간이 지나고 실제로 두뇌가 물리적으로 여러 개로 나누어지게 되었다. 멀티코어의 등장이다. 요즘 CPU들은 16 코어, 32 코어도 볼 수 있으며, 서버에 사용되는 친구들은 이보다 훨씬 더 많아졌다. 이제는 실제 물리적으로 실행의 흐름이 동시에 동작하게 된다. Multi processing / Multi thrading 등, 이로 인해 우리는 동시성 제어라는 난관에 봉착하게 된다.
 
우리가 운영체제 시간에 배웠던 Dead lock이나 Starvation, Race condition 등이 이에 관한 것들이었다. 하지만 우리가 운영체제 시간에 배웟던 것들은 아마 물리적인 동시 실행의 흐름들을 문제가 발생하지 않는 방향으로 유도하기 위한 방지책들이었을 것이다. 방지책이 아니라 실제로 동시성에 대한 side effect 없이 사용할 수 있는 구조적인고 원천적인 해결법이 나오면 더 좋지 않을까? 이를 위해서 기존부터 있었던 함수형 프로그래밍이 각광을 받기 시작했다고 생각한다.
 
함수형 프로그래밍을 사용해 본 분들은 아시겠지만, 각 실행의 흐름이 주어진 값에 대해서 완전 무결한 결과만을 반환하도록 하는 것이 목표이다. 이를 위해서 순수함수, 일급함수, Monad, Functor 등을 배우면서 이를 제대로 사용하는 방법들을 배우는 것이다.
 

실제로 유용한가?

2016년부터 함수형 관련 그룹 스터디와 개인적인 공부를 통해서 배우고 사용해왔다. 본격적으로 함수형 프로그래밍을 적용해서 개발한 것은 2022년부터이다. 유용한지에 대해서 결론부터 말하자면 유용하다. 필자가 만들었던 실행의 흐름이 발생하는 파이프라인은 주어진 파라미터에 따라서 거의 완전 무결한 결과값을 반환했다. 그리고 중간에 에러가 발생하면, 그 부분과 이유에 대해서 명확하게 알려줬다. 물론 기기 상의 환경적인 요인이나 필자가 짠 코드 외부에서 발생한 부분에 대해서는 unkownError를 반환하지만, 최소한 직접 작성한 코드 안에서의 에러는 이유를 명확하게 알려줬다. 이 덕분에 아까 말한 코드 외적인 부분의 에러도 코드 외적인 원인으로 발생했다는 것을 쉽게 추론이 가능하다. 단, 함수형을 적용하는 데에 있어서 제약이 좀 있다.
 


아마 함수형에 관해서 진행했던 발표들을 듣거나 실제로 적용해 본 분들은 이미 아시겠지만, 함수형으로 적용하다보면 공유되는 자원들에 대해서 문제가 발생한다. 함수형은 실행의 흐름이 각자의 데이터를 참조하여야만 무결한 결과값을 반환한다. 이를 위해서 우리는 함수형으로 작성될 영역과 그렇지 못할 영역을 명확하게 분리해줘야 할 필요가 있다. 이에 대해서는 지속적으로 써보면서 느껴야 하는 부분이다. 필자 또한 이를 경험하면서 영역을 구분해 나아갔다. 이제는 나름 이 영역들에 대해 구분하는 기준이 생겼다. 
 
최근에 진행했던 프로젝트에서는 이런 부분을 고려해서 진행해봤다. 공유될 자원들에 대해서는 객체지향적 개념을 사용해서 관리하도록 하고, 공유가 필요 없는 비지니스 로직들에 대해서는 명확하게 함수형을 적용하여 선언적이고 예측가능한 코드를 작성했다. 굳이 설명하자면 거시적으로 각 모듈은 객체지향을 이용한 다형성 및 캡슐화를 특징으로 가져갔다. 미시적으로는 비지니스 로직들을 분리하여 파이프라인으로 구성하고 이들이 서로 영향을 받지 않고 동작하게끔 설계하였다. 결과는 좋았다. 작성된 코드들이 의도된 대로 동작했다. 문제가 있다면 의도 자체가 잘못될 경우에 문제가 됐다. 요구사항을 똑바로 이해하지 못하였거나, 분명히 함수형으로 짠 비지니스 로직에서 에러를 반환했는데 이에 대해서 아무런 처리를 하지 않거나 등이다. 어떤 식으로 사고하고 작성하는지에 대해서는 예전에 발표했던 영상을 아래에 첨부할테니 시간 날 때에 봐두면 좋을 듯하다.
 

 

유용한데도 불구하고, 왜 시장에서 보기 어려운가?

실제로 각 회사에서 프로젝트에 참여하고 코드를 살펴보면 명확한 기준을 가지고 함수형으로 짜인 코드를 보기 드물다. 물론 필자가 아직 근무해보지 않은 회사는 논외이지만, 대체로 주변의 얘기를 들어보고 유추해 봐도 그렇게 많지는 않은 것 같다. 왜 유용하다는 프로그래밍 방법이 대중화되지는 못할까?
 
이유는 조직원의 지식 동기화이다. 적당한 표현을 몰라서, 지식 동기화라고 표현을 했다. 모두가 함수형 프로그래밍에 대한 배경 지식이 비슷해야 적용이 가능하다는 것이다. 우리가 어떤 아키텍처를 작성할 때에 추가적인 논의가 불필요한 이유는 해당 아키텍처들을 학부 혹은 기존에 근무하던 곳에서 보아왔기 때문이다. 우리가 유행하는 아키텍처를 사용하는 것도 굳이 배경 지식에 대해서 서로 맞춰볼 필요가 없는 것도 한몫을 할 것이다. 함수형 프로그래밍이 아무리 좋아도 이에 대해서 명확하게 이해하고 쓰지 않는다면, 제대로 된 효과를 보기도 어렵다. 그래서 함수형 프로그래밍이 유용함에도 불구하고 널리 사용이 안된다고 생각한다. 실제로 필자와 같이 프로젝트를 진행하면서 경험해 본 친구들의 의견은 놀라움이다. 명확하게 코드가 분리되고 결과를 반환하는 구조에 대해서 감탄한다. 하지만 이 코드들도 완벽하다고 자부할 수는 없다. 단지 여기서 말하고자 하는 것은 '우리가 선언적인 코드를 작성하려면, 이러한 코드들을 작성하고 공부해봐야 한다'는 것이다.
 

그럼 선언형은 뭔가?

함수형은 함수를 기반으로 코드를 작성하는 방법이라고 대충 갈무리를 하고 추가로 얘기를 이어나가보자. (물론 더 복잡한 개념들이 있지만....) 그럼 선언형은 뭔가? 선언형은 어떻게 동작하는지보다는 무엇을 할 것인지에 집중하는 코드를 지향하는 것이라고 생각하면 된다.
 

var result = [Int]()
for i in numbers {
    if i % 2 == 0 {
        result.append(i)
    }
}

 
위의 코드를 보자면 우리는 이것이 무엇을 위한 코드고 무엇을 하려는지에 대해서 코드를 분석해야 한다. 하지만 아래의 코드를 보자. 보면 명확하게 무엇을 원하는지 알 수 있다. 대표적으로 SwiftUI 혹은 SQL만 봐도 그렇다. SwiftUI는 무엇이 화면상에 배치되고 누구와 연관이 있는지 한눈에 들어온다. SQL은 작성자가 원하는 데이터가 무엇인지가 한눈에 들어온다. (물론 SQL을 더럽게 짜면 읽는데 시간이 걸린다) 
 

let result = numbers.filter { $0.isMultiple(of: 2) }

 
선언형은 이처럼 코드를 읽기 쉽고 의도된 대로 동작하도록 코드를 작성하는 방식이다. 함수형을 이용하면 선언적인 코드 작성이 쉽다. MVVM이나 SwiftUI를 이용한 코드를 작성할 때도 이러한 부분을 심각하게 고려하면서 코드를 작성해야한다. 코드의 역할이나 기능이 영역에 따라서 명확히 분리가 되었는지, 그리고 각 영역에서 선언적이고 의도된대로 동작하도록 작성하였는지 등이다. 
 
Swift가 처음에 등장했을 때에 유행처럼 사람들이 하던 말이 'Swift를 Swifty 하게 작성했는가'이다. 물론 흉내를 내는 코드를 작성하는 것이 첫 단계이다. 하지만 점점 나아갈수록 원래 표현하고자 했던 본질에 접근해 나아가지 않는다면, 원래 우리가 사용하고자 했던 이론이나 방법의 목적을 달성하기 어려울 것이다. 
 

Conclusion

이 글은 함수형과 선언형에 대한 내용을 알려주기 위해서 작성한 글은 아니다. 아까 말했듯이 이 내용에 대해서 발표한 영상도 있고 강의 내용도 있다. 그리고 발표 자료들도 많이 공유했었다. 그럼에도 불구하고 이 글을 통해서 다시금 내용을 언급하고자 하는 이유는 지속적인 노출을 통해서 많은 개발자들이 관심을 가지고 공부한다면, 위에서 얘기했던 조직 구성원 간의 배경 지식의 동기화에 드는 비용이 줄지 않을까 하는 차원에서이다. 함수형 프로그래밍과 이를 이용해서 작성하는 선언적인 코드 혹은 선언형 아키텍처를 제대로 쓰고, 조직 구성원들 간의 건설적인 토론이 가능하도록 공부하고 널리 알려졌으면 하는 작은 바람을 가지고 있다.