[번역] OO vs FP (객체 지향 vs 함수형)
date
May 11, 2023
slug
oo-vs-fp
status
Public
category
아키텍처
tags
Architecture
Backend
summary
type
Post
Created Time
May 11, 2023 09:01 AM
파일과 미디어
들어가기 앞서
원문 작성일: 2014년 11월 24일
OO = Object-Oriented (객체 지향)
FP = Functional Programming (함수형 프로그래밍)
![notion image](https://www.notion.so/image/https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHSskP%2FbtseScZaHSX%2FBUYBUzFdGVwVOzHTuZGWL1%2Fimg.jpg?table=block&id=9077d734-62cf-49fc-a553-75b3368c135e&cache=v2)
내 친구가 이런 사진을 페이스북에 올렸습니다. 그는 이 사진을 보고 트롤이라고 얘기했고, 이 사진은 저를 짜증나게 하는 데에 성공했습니다.
객체 지향과 함수형 프로그래밍을 상호 배타적인 형태라고 얘기하는 프로그래머들이 수년 동안 많이 있었습니다. FP 슈퍼 존재들은 종종 구름 위의 상아 탑에서 불쌍하고 순진한 OO 프로그래머들을 내려다보며 혀를 끌끌 차기도 합니다. 반대로, 그들의 상아 탑의 OO 슈퍼 존재들은 함수형 언어의 괄호 낭비와 공해에 대해 의아한 표정을 지으며 혀를 끌끌 찹니다.
이런 견해는 객체 지향과 함수형 프로그래밍이 실제로 무엇인지에 대한 깊은 무지를 바탕으로 합니다. 몇 가지 이야기를 해보겠습니다.
객체 지향은 상태(state)에 관한 것이 아닙니다.
객체(Objects)는 자료 구조가 아닙니다. 객체는 자료구조처럼 쓰일 수 있지만, 이런 자료 구조가 사용되는 방식이나 포함하는 데이터들은 숨겨져있습니다. 이것이 바로 데이터 필드가
private
인 이유입니다. 외부에서 보았을 때 어떤 상태도 볼 수 없습니다. 볼 수 있는 것은 함수 뿐입니다. 따라서 객체는 상태에 관한 것이 아니라 함수에 관한 것입니다.함수형 프로그래밍은 객체 지향처럼, 데이터에서 작동하는 함수들로 구성되어 있습니다.
지금까지의 모든 함수형 프로그램은 데이터에서 작동하는 함수들의 집합으로 구성되어 있습니다. 지금까지의 모든 객체 지향 프로그램도 데이터에서 작동하는 함수들의 집합으로 구성되어 있습니다.
일반적으로 객체 지향 프로그래머들은 객체 지향을 '함수와 데이터가 함께 결합된 것'으로 정의합니다. 이는 물론 사실이긴 하지만, 객체 지향이 아니더라도 사실인 이야기입니다. 객체 지향 뿐 아니라 모든 프로그램은 데이터에 바인딩된 함수들이기 때문입니다.
바인딩하는 방식이 중요한 것이 아니냐며 항의할 수 있겠습니다. 하지만 이는 어리석은 생각입니다.
f(o)
, o.f()
, (f o)
사이에 그렇게 큰 차이가 있을까요? 정말 이 차이가 함수 호출의 구문에 불과하다고 얘기할 수 있을까요?차이
그렇다면 객체 지향과 함수형 프로그래밍 사이의 차이가 무엇일까요? 함수형에 있는데 객체 지향에는 없는 것은 무엇이고, 객체 지향에 있는데 함수형에는 없는 것은 무엇일까요?
함수형은 할당(assignment)을 할 때 일종의 규칙이 정해져 있습니다
진정한 함수형 프로그래밍 언어에는 할당 연산자 ( = )가 없습니다. 변수의 상태를 변경할 수 없다는 의미입니다. 실제로 변경할 수 없기 때문에 '변수'라는 이름은 함수형 프로그래밍에서는 잘못된 이름입니다.
함수형 프로그래머들은 종종 "함수는 함수형 언어에서 일급 객체다"라는 고상한 말을 하곤 합니다. 사실일 수도 있지만,
SmallTalk
라는 언어에서는 함수는 일급 객체로 취급되지만 SmallTalk
은 객체 지향 언어입니다.함수형과 비함수형 언어의 가장 중요한 차이점은 함수형 언어에는 대입문이 없다는 것입니다.
함수형 언어에서는 어떤 것의 상태를 절대 변경할 수 없다는 뜻은 아닙니다. 함수형 언어는 일반적으로 어떤 대상의 상태를 변경하기 위해 수행할 수 있는 선언문을 제공합니다. 예를 들어서
F#
라는 언어에서는 "가변 변수 (mutable variables)"를 선언할 수 있습니다. Clojure
를 사용하면 다양한 마법 주문을 사용해서 값을 변경할 수 있는 특별한 객체를 만들 수 있습니다.요점은 함수형 언어는 상태를 변경할 때 일종의 의식이나 규칙을 부과한다는 것이고, 따라서 상태를 변경하려면 적절한 절차를 거쳐야 합니다.
그래서 대부분은 상태를 변경하지 않습니다.
객체 지향은 함수 포인터에 일종의 규칙이 정해져 있습니다
"뭐?" 라고 말하겠죠. 하지만 사실 이것이 객체 지향의 핵심입니다.
"객체 지향", "현실 세계의 객체 (real-world objects)", "우리가 생각하는 방식에 더 가까운 프로그래밍" 이라는 모든 거창한 표현에도 불구하고, 객체 지향 프로그래밍이 실제로 의미하는 바는 함수 포인터를 편리한 다형성으로 대체한다는 점입니다.
다형성을 어떻게 구현할까요? 함수 포인터로 구현합니다. 객체 지향 언어는 이런 구현을 대신 해주고 함수 포인터를 우리로부터 숨겨줍니다. 함수 포인터는 잘 관리하는 것이 매우 어렵기 때문에 좋은 점입니다. 함수 포인터로 다형성 코드를 직접 작성하려고 하면 (C언어에서처럼) 모든 사람들이 모든 상황에서 따라야 하는 복잡하고 불변한 규칙들에 의존하게 됩니다. 이는 일반적으로 비현실적인 일입니다.
Java
에서는 호출하는 모든 함수가 다형성입니다. 다형성이 아닌 함수를 호추할 수 있는 방법은 없습니다. 즉, 모든 자바 함수는 함수에 대한 포인터를 통해 간접적으로 호출됩니다.C언어에서 다형성을 원한다면 포인터를 직접 관리해야 하는데, 이는 어렵습니다.
Lisp
에서도 다형성을 원한다면 포인터를 직접 관리해야 합니다. (포인터를 상위 수준 알고리즘에 인수로 전달해야 합니다 (전략 패턴)) 하지만 객체지향 언어에서는 이런 포인터가 자동으로 관리됩니다. 언어가 포인터를 초기화하고, 마샬링해서 이를 통해 모든 함수를 호출합니다.상호 배타적?
이 두 분야는 상호 배타적일까요? 함수에 대한 할당과 포인터 모두에 규율을 적용하는 언어를 사용할 수 있을까요? 물론 가능합니다. 객체 지향과 함수형 프로그래밍은 서로 아무런 관련이 없기 때문에, 전혀 상호 배타적이지 않습니다. 다시 말해, 객체 지향-함수형 프로그램을 작성할 수 있다는 이야기입니다.
또한 함수형 프로그래머가 함수에 대한 포인터를 부과하는 규율들을 받아들인다면 함수형 프로그래머도 객체 지향 프로그래머가 사용하는 모든 디자인 원칙과 디자인 패턴을 사용할 수 있다는 뜻이기도 합니다.
하지만 함수형 프로그래머가 왜 그런 일을 해야 할까요? 다형성이 일반적인 함수형 프로그램에는 없는 어떤 이점이 있을까요? 같은 맥락에서, 객체 지향 프로그래머가 할당에 규율을 적용함으로써 얻을 수 있는 이점이 뭐가 있을까요?
다형성의 이점
다형성의 이점은 단 하나 뿐이지만 매우 큰 이점입니다. 바로 소스 코드와 런타임 종속성의 반전 (inversion of source code and run time dependencies)입니다.
대부분의 소프트웨어 시스템에서 한 함수가 다른 함수를 호출할 때 런타임 종속성과 소스 코드 종속성은 같은 방향을 가리킵니다. 호출을 하는 모듈은 호출이 되는 모듈에 의존합니다. 그러나 둘 사이에 다형성이 주입된다면 소스 코드 종속성은 반전됩니다. 호출을 하는 모듈은 런타임에도 여전히 호출되는 모듈에 종속됩니다. 하지만 호출을 하는 모듈의 소스 코드는 호출되는 모듈의 소스 코드에 종속되지 않습니다. 오히려 두 모듈 모두 다형성 인터페이스에 의존합니다.
이러한 반전을 통해 호출된 모듈이 플러그인처럼 작동할 수 있습니다. 실제로 모든 플러그인이 이러한 방식으로 작동합니다.
플러그인 아키텍처는 사용자 인터페이스나 데이터베이스와 같이 변동성이 큰 '낮은 가치의 모듈'에 의존하지 않고, 안정적인 높은 가치의 '비즈니스 규칙'을 유지할 수 있기 때문에 매우 견고합니다.
결과적으로 시스템이 견고해지기 위해서는 중요한 아키텍처 경계에 걸쳐서 다형성을 사용해야 합니다.
불변의 이점
할당문을 사용하지 않을 때의 이점은 분명합니다. 아무것도 업데이트하지 않는다면 동시성 문제는 발생할 수가 없습니다.
함수형 프로그래밍 언어에는 대입문이 없기 때문에 이러한 언어로 작성된 프로그램은 많은 변수의 상태를 변경하지 않습니다.
Mutation
은 높은 수준의 의식을 견딜 수 있는 시스템의 매우 특정한 세션을 위해 예약되어 있습니다. 이러한 섹션은 본질적으로 다중 스레드 및 다중 코어 환경으로부터 훨씬 안전합니다.깊은 철학
물론 객체 지향을 고수하는 사람들, 함수형 프로그래밍을 고수하는 사람들 모두 저의 환원주의적 분석에 항의할 것입니다. 그들은 자신이 선호하는 스타일이 다른 스타일보다 더 나은 철학적, 심리적, 수학적 이유가 있다고 주장할 것입니다.
이에 대해 저는 말도 안된다고 생각합니다. 다들 자기 방식이 최고라고 생각하죠. 모두가 틀렸습니다.
디자인 원칙과 디자인 패턴은 어떨까요?
이 글의 시작 부분에서 저를 짜증나게 한 구절은 지난 수십 년동안 우리가 확인해낸 모든 디자인 원칙과 디자인 패턴들이 객체 지향 프로그래밍에만 적용되며 함수형 프로그래밍은 이 모든 것을 그저 '함수'로 축소한다는 것을 암시합니다.
와! 환원 주의에 대해 이야기해 보세요!
이 생각은 극단적으로 말도 안됩니다. 소프트웨어 설계의 원칙은 프로그래밍 스타일과 관계 없이 적용됩니다.
할당 연산자가 없는 언어를 사용하기로 했다고 해서 단일 책임 원칙을 무시할 수 있다던가 개방-폐쇄 원칙이 자동으로 적용된다던가 하는 의미가 아닙니다.
전략 패턴이 다형성을 사용한다고 해서 이 패턴이 좋은 함수형 언어에서는 쓸 수 없다는 것을 의미하지는 않습니다. (좋은 함수형 언어는 편리한 다형성을 허용하는 언어입니다.
Clojure
가 좋은 예시입니다)결론은 간단합니다. 객체 지향 프로그래밍은 객체 지향이 무엇인지 알고 쓸 때 좋은 프로그래밍입니다.
함수형 프로그래밍도 그렇습니다. 그리고 객체 지향-함수형 프로그래밍도 그렇습니다.