본문 바로가기
Unity/UniRx & UniTask

함수형 프로그래밍 (FP)

by Pretty Garbage 2022. 11. 7.

함수형 프로그래밍 : 순수 함수 작성과 공유 상태, 변경 가능한 데이터와 사이드 이펙트 피하기로 소프트웨어 구축하는

프로세스이다.

 

명령적이라기보다는 정의 혹은 선언에 의해 반응적이며 어플리케이션의 상태의 흐름이 순수 함수를 통해 흐르게 된다.

이는 어플리케이션 상태가 공유되고, 객체의 메소드와 함께 배치되는 객체 지향 패러다임과는 대조적이다.

 

함수형 코드는 객체 지향 코드에 비해 더 간결하고, 더 예측 가능하고, 더 테스트하기 쉬운 경향이 있다.

 

함수형 프로그래밍 패러다임을 이해하기 위해서는 아래 5가지 아이디어가 중요하다.

 

  • 1. 순수 함수
  • 2.합성 함수
  • 3.공유 상태 피하기
  • 4.불변성
  • 5.사이드 이펙트 피하기

 

위 핵심 개념을 알아야 비로소 함수형 프로그래밍의 패러다임을 이해하게 된다.

 

1. 순수 함수

  • 같은 입력 값이라면, 항상 같은 결과 값을 반환한다.
  • 사이드 이펙트를 가지지 않는다.
private int ReturnNum(int num)
{
    return num * 2;
}

ReturnNum(10); // 20으로 대체 가능


//잘못된 예
private int ReturnNum2(int num)
{
	user.setItem("Item Equipped", num * 2); //참조 투명성 해침
    return num * 2;
}

ReturnNum2(10) // 20으로 대체 불가능!! user.setItem 부분이 동작이 누락되기 때문

 

순수 함수는 외부 변수를 수정할 수 없다.

 

//순수 함수는 외부 변수를 수정할 수 없다. 고로 아래 함수들은 순수 함수가 아니다.

writeFile(fileName);
updateDbTable(sqlCmd);
openSocket(ipAddress);

//위의 함수들은 모~두 사이드 이펙트가 있을 수 있는 함수들이다.

 

함수형 언어들로 사이드 이펙트를 완전히 없앨 수는 없다. 단지 억제를 할 뿐! 프로그램은 사용자와 밀접한 관계가 있기 때문에,

순수 함수로만 프로그램을 만들 수는 없다. 

 

우리의 목표는 비순수 함수를 최소화 하고, 우리의 프로그램의 순수 함수와 분리해서 작성하는 것이다.

 

// 함수형 프로그래밍에서는 변수가 없다! //

 

지역 변수들이 사실상 상수로서 작동한다. (생명주기가 짧기 때문)

 

2. 합성 함수

 

이해하기로는 그냥 수학시간 때 배운 합성 함수 그 자체 였다.

 

private int ReturnSum10(int num)
{
    return num + 10;
}

private int ReturnMultiply5(int num)
{
    return num * 5;
}

private int Mult5AfterAdd10(int num)
{
    return 5 * (num + 10);
}

private int Mult5AfterAdd10Modified(int num)
{
    return ReturnMultiply5(ReturnSum10(num));
}

 

예시로 보여주기 위해 3번 4번 함수가 있는 것인데 1번 2번만 만들어놓으면 4번의 return대로 가져다 사용하면 된다.

합성함수를 통해서 분리된 함수들을 조립해서 동일한 결과를 뽑아낼 수 있도록 한다.

 

 

3. 상태 공유

 

상태 공유는 공유되는 영역안에서 존재하는 모든 변수, 객체, 메모리 공간이거나 영역 간에 전달되는 객체의 속성이다.

예를 들자면 캐릭터, 게임 아이템 등이 속성으로서 저장된 마스터 게임 객체를 가지고 있을 수 있다. 함수형 프로그래밍은

상태 공유를 피하고, 대신에 변하지 않는 데이터 구조와 순수 계산을 이용하여 새로운 데이터를 기존의 데이터로 부터 뽑아낸다.

 

게임을 예로 들면 

- 유저가 아바타를 변경하고 이를 서버에 접근해서 저장한다. 라는 요구 사항에

 

SaveUser() 라는 함수는 서버 API에 현재 유저의 상태 값 저장을 전달할 것이다. 

이 요청이 일어나는 동안 유저가 어떠한 상태를 변경을 했을 시에 UpdateUser()라는 함수가 실행 될 것이고 이에 또다시

SaveUser()라는 함수를 연쇄적으로 호출할 것이다.

그런데 여기서 첫번째 호출 보다 두번째 호출이 더 빠르게 반응이 왔다고 했을 때에 마지막 상태값이 아닌 처음 SaveUser()를 호출할 때

값으로 교체 된다. 이 것이 경쟁 조건의 한 예이고 상태 공유와 연관되어 자주 발생하는 버그이다.

 

또 다른 예로는

public void Test()
{
    var x = 2;

//6번째 줄과 7번째 줄의 순서에 따라 결과가 달라진다.
    x = Mul(x);
    x = Sum(x);

    Console.WriteLine(x);
}

public int Sum(int num)
{
    return num += 1;
}

public int Mul(int num)
{
    return num *= 2;
}

해결법은??

 

각 수식에 대한 함수를 만들고 합성함수를 이용해서 해결한다.

 

4. 불변성

 

반복문도 for, while, do 등을 사용하지 않고 재귀함수로서... 사용한다고 한다. for 반복문이 훨씬 이해하기 쉽다고 생각할 것이고

친숙함의 문제가 있지만 비 재귀함수의 반복문은 불변하지 않기 때문에 좋지 않다.

불변성의 장점은 프로그램의 변수에 접근할 경우 오직 읽기만 가능하고 아무도 그 값을 바꿀 수 없다.

 

// 함수형 프로그래밍은 반복을 하기 위해 재귀 함수를 이용한다 ! //

//일반적으로 우리가 생각하는 반복문
        private void Sum()
        {
            var sum = 0;

            for (var i = 0; i <= 10; i++)
            {
                sum += i;
            }
            Console.WriteLine(sum);
        }

        private int Sum2(int start, int end, int result)
        {
            return start > end ? result : 
                Sum2(start + 1, end, result + start);
        }

 

 

5. 사이드 이펙트

 

가장 크게 메리트를 느끼는 부분이고 함수형 프로그래밍에서 대부분 피할 수 있고, 이는 프로그램을 더 쉽게 이해하고 더 쉽게 테스트 할 수 있도록 한다. (단위 테스트 적인 부분에서 봐도)