[함수형 프로그래밍] 함수형 프로그래밍

Heechul Yoon·2022년 8월 12일
0

함수형 프로그래밍

  • 함수는 input을 받으면 일정한 output을 뱉는 코드 묶음
  • 외부 변수가 없으면 일정한 input을 넣으면 “반드시” 일정한 output이 나온다

첫번째로, 함수형 프로그래밍에서는 외부 변수를 사용하지 않는다. 반드시 함수 스코프 범위 안에 있는 변수만 사용해야 한다.

두번째로, 상태를 변경하지 않는다. 매개변수로 배열이나 객체를 받으면 그 배열과 객체 안에 있는 값을 추가하거나 변경하는게 아니라 매개변수로 받은 객체나 배열을 기반으로 해서 새로운 객체나 배열을 만든다

const func1 = (someArr: Array, someObj: Object) => {
     /* 처음부터 생성할 때 부터 값을 넣어서 생성한다. 
     생성과 동시에 유효하다. 다만 상태변경은 안된다 */
     newArr = new Array(1);
     newObj = new Object('key', 'value');
 }

참조형으로 받은 변수안에 있는 상태를 조작하는 위험을 원천 봉쇄 하는것을 통해서 "예외상황"을 막을 수 있다. javascript에서는 객체 안에 있는 상태를 바꾸지 못하게 Object.freeze를 사용하기도 한다.

세번째로, 클로저를 지원한다. 사실 지원하지 않아도 함수형 프로그래밍이 가능하지만, 함수안에서 함수를 선언해서 반환하기 위해서는 클로저가 필요하다.
클로저는 함수안에서 함수가 정의될 때, 함수가 정의되는 스코프 안에있는 매개변수를 전부 정의되는 함수 안에 있는 스코프로 복사해준다. (아마 함수의 메타데이터에 상위스코프의 매개변수들이 들어가는곳이 있을것이다)

const func1 = (a, b) => {
	function innerfunc(c) {
    	return a + b + c;
    }
  
	return innerfunc;
}

inner = func1(1, 2); // [1]
inner(3) // 6

func1에서 내부함수를 선언하고 리턴했다. 내부함수 안에서는 func1 스코프에서의 변수 a와 b를 사용했다.
[1]의 시점에서 inner이라는 변수에 담긴 내부함수가 리턴될 시점에서는 이미 func1의 함수 스택이 pop 된 상태이다. 하지만 inner(3) 을 실행하면 a와 b 변수에 접근이 가능하다.
이걸 가능하게 하는게 스코프다.
innerfunc 내부 함수가 선언되는 시점에서 이미 func1 스코프에 있는 변수 a, b를 innerfunc 내부에 복사를 해주었을 것으로 추정된다.(더 찾아봐야됨)

마지막으로 함수가 일급객체로 취급받는다는 전제조건이 있어야 된다. 객체는 전부 포인터라서 함수도 포인터로 전달될 수 있기 때문에 이런 맥락에서 함수 또한 일급객체로 인식된다.

일급객체가 되기 위한 조건

변수나 데이터 구조 안에 담을 수 있다.

  • 함수포인터를 변수, 구조체, 객체의 멤버변수 등으로 넣을 수 있다

파라미터로 전달할수 있다.

  • 함수포인터를 argument로 전달하면 parameter로 받는 입장에서 함수포인터(값의 주소)를 스코프 안에서 복붙해서 사용함.
  • 결국 가리키고 있는 값(함수포인터면 함수, 객체 포인터면 객체)은 그대로 들고있기 때문에 주소 통해서 접근가능

반환값으로 사용할수 있다.

  • 그냥 어떤 값의 주소를 반환하는 개념으로 보면 함수 구현체의 주소를 반환한다고 보면됨

할당에 사용된 이름과 관계없이 고유한 구별이 가능하다.

  • 이것도 그냥 스택에 올라와있는 주소값에 이름붙인거니깐(아마 js같은건 함수포인터만 있는게 아니라 그냥 함수 포인터를 wrapping하는 함수의 메타데이터가 들어가는 객체가 있을거같음) 이 주소를 다른 변수에 대입해도 가리키고 있는 힙메모리에있는 객체는 변하는게 아니니깐 포인터 참조하는거처럼 값을 가져올수 있음

동적으로 프로퍼티 할당 가능

js같은 언어에서는 익명함수를 선언하면 함수포인터만 들고있는게 아니라 함수의 메타데이터까지 들어가있는 메타데이터 객체안에 함수포인터를 멤버로 들고있는 형태로 만들어질걸로 예상되기 때문에, 함수의 메타데이터 객체에 어떤 프로퍼티도 할당가능할걸로 추정됨(자세한건 es6 문서같은거 봐야될듯)

전역상태가 없다는것의 의미

보통 전역상태가 있으면 race condition을 막기 위해서 변수에 lock을 걸어주거나, 아니면 다른 actor model 을 만들어서 전역변수를 건드리는데 함수형 프로그래밍에서는 이런일을 할 필요가 없다. 처음부터 전역변수가 없고, 매개변수로 받은 값을 함수스코프 안에서 조작하기 때문이다.

함수형 프로그래밍이 속도가 느릴수도 있고 빠를수도 있다.

함수형 프로그래밍이 속도가 빠를수 있는 이유는 전역변수에 lock을 걸어줘서 대기했다가 lock이 풀리면 다시 접근해서 하는 atomic 연산을 위한 과정이 없기 때문이다.

  • 세마포어 방식이면 기다리는 스레드가 대기큐로 들어가기 때문에 컨텍스트 스위칭이 발생해서 느려질 것이고,
  • 뮤텍스 방식이면 스핀락이 걸리기 때문에 컨텍스트 스위칭 비용은 없지만 기다리는동안 cpu를 물고있어서 다른 스레드가 cpu를 할당받을 확률을 낮춤

이런 과점에서 함수형 프로그래밍은 멀티스레드 환경에서 개별적으로 cpu를 물고 돌수 있어서 더 빠를 가능성이 있따

함수형이 속도가 더 느릴 수 있다

함수형이 속도가 더 느릴 수 있는 이유는 매개변수로 받은 객체의 상태를 변경할 수 없기 때문에 계속 새로운 객체를 만들어야 한다.
새로운 객체를 계속 생성하면 gc가 더 빠른 주기로 돌게 된다. gc가 도는 동안은 gc 스레드 빼고는 다 멈추기 때문에 자주 stop the world 상태가 돌아온다

FP 적용 전 생각해볼 것들

일반 플랫폼 서비스의 경우 빈번한 요구사항 변경과 변화에 대응하는게 유리하다. 이런 이유때문에 oop를 통해서 거대한 서비스의 기능을 모듈화 해서 관리하는게 유지보수에 유리하다.

oop가 오히려 도메인 로직을 녹여내는데 더 유리하다. 객체가 자기자신의 상태를 책임지는 등, 책임의 범위가 분명하기 때문
책임의 범위가 분명하다. 이말은 즉, 역할이 분명하게 나뉘어 진다는 뜻이다.

함수형 프로그래밍은 데이터중심의 아키텍처를 전제로 하고, 데이터를 가공할일이 많을 때 사용하는게 좋을 것 같다. 결국은 두개는 다른영역이다. 쓰이는 곳이 다르다는 뜻이다.

profile
Quit talking, Begin doing

0개의 댓글