Swift로 함수형 프로그래밍 시작하기

이 글은 Swift로 함수형 프로그래밍 시작하기의 내용을 공부하고, 개인적으로 정리해놓은 글입니다.

1. 프로그래밍 패러다임의 변화

패러다임

  • 한 시대의 사람들의 견해나 사고를 인식하는 체계 또는 흐름이랄까 그런 것을 패러다임이라고 하죠.

  • Low Memory

    • 지금과는 달리 메모리가 제한적이던 옛날에 프로그래머들이 선택할 수 있는 패러다임은 최적화였습니다. 데이터를 분리하고 메모리에 직접 접근하고 중복되는 데이터를 없앴죠. 그래서 취급하는 데이터가 변경되면 프로그래밍도 변경되어야 했죠.

  • Mass Production

    • 컴퓨터가 대중화되면서 대량 생산의 필요성이 증가했어요. 그래서 기존의 매번 코드를 재작성해야하는 패러다임은 맞지 않았습니다. 코드의 재사용의 필요성이 대두된거죠. 여기서 객체지향 프로그래밍(OOP)가 등장했습니다.

  • Concurrency

    • 요즘 시대를 생각해보죠. CPU에는 여러 개의 코어가 들어가고, 하나의 PC에 여러 개의 CPU가 장착됩니다. 하나의 프로세스로 돌아가는 프로그램도 여러 개의 쓰레드로 나눠져서 동시에 실행되죠.

    • 따라서, 이런 시대에 맞는 패러다임이 필요했습니다. 하나의 반복된 작업을 병렬로 나눠서 처리하고, 여러 개의 작업을 동시에 수행하고, 각 작업이 끝날 때까지 멈춰서 기다리지도 않고, 각기 작업에 실행된 결과들이 수집되서 새로운 작업에 사용됩니다.

    • 패러다임이 재사용의 관점에서 동시성의 관점으로 옮겨진겁니다.

FP가 재조명 받는 이유

  • 결국엔 동시성(Concurrency)이 문제인거죠. 여기서 발생할 수 있는 제일 큰 문제는 이런 겁니다. 사과가 10개있고, A에서 사과 10개를 가져다가 3개를 팔고 7개를 반환합니다. 그런데 B에서 7개가 반환되기 전에 사과를 요청한거죠. 그러면 데이터의 무결성이 깨져버립니다.

  • 이런 동시성의 문제를 해결할 수 있는 방법이 OOP에도 있긴 합니다. 세마포어를 통해서 자원의 동시 접근을 제한하는거죠. 자바에서는 Synchronized 라는 키워드를 통해 자원의 동시 접근을 제한합니다.

  • 하지만 많이 불편합니다. 그리고 이것을 해결하는 간단한 방법도 있습니다. 바로 한 번 만들어진 데이터는 변경하지 않는겁니다. 하나의 데이터를 참조하지않고 데이터가 필요하면 그냥 복사하는거죠. 기존의 데이터가 변경되지 않으니 동시에 수행되는 다른 프로그램에 영향을 주지 않습니다.

  • 이렇게 프로그래밍하면 side-effect가 없고, 이런 관점이 현재 시대의 동시성 문제를 해결하는데 도움이 됩니다. 그리고 이것은 옛날부터 존재했지만 함수형 프로그래밍(FP)가 다시 주목받는 이유입니다.

FP에 대한 오해

  • 고차함수, 커링, 맵, 핉터, 리듀스는 프로그래밍 기법입니다. 내 프로그램에서 map, filter를 사용한다고 FP는 아니라는거죠.

  • FP의 가장 특징은 side-effect가 없다는 겁니다. 함수를 중심으로 side-effect가 없도록 하는게 FP입니다.

  • 또 FP <-> OOP는 아닙니다. OOP도 FP도 각자 당면한 문제에 따라 대두된 패러다임이죠. 그냥 지금 내가 맞이한 문제에 맞는 패러다임을 사용하면 됩니다. 둘 다 공존할 수 있는 것이죠.

  • 마지막으로 함수를 일급객체로 취급하는 언어들에서는 더 쉽겠지만, 다른 언어에서도 FP를 사용할 수 있습니다. FP는 기술이 아니라 패러다임이기 때문이지요.

2. 함수를 다루는 기술들

순수함수(Pure Function)

  • 함수형 프로그램은 함수를 이용해서 프로그래밍을 하죠. 여기서 말하는 함수는 순수 함수입니다.

  • 순수함수란, 어떤 input에 대해서 항상 동일한 output을 반환하는 함수에요. output은 오직 input에 의해서만 결정됩니다.

  • 그래서 side-effect가 없고 외부에 영향을 주지도, 받지도 않죠.

고차함수(Higher-Order Function)

  • 함수형 프로그래밍에서는 함수를 1급객체로 취급합니다. 1급객체란 함수의 파라미터로 전달되거나 리턴값으로 사용될 수 있는 객체를 말하죠.

  • 우리가 매번 사용하는 swift의 고차함수인 map, filter, reduce와 같은 것들입니다.

합성(Composition)

  • 함수의 반환값이 다른 함수의 입력값으로 사용되는 것을 함수의 합성이라고 합니다.

  • 약간, OOP에서 상속보단 구성(Composition)을... 같은 느낌의 유사한 의미를 가진 용어네요.

  • 아래의 코드처럼 f1의 리턴이 f2의 입력값으로 사용됐죠.

커링(Currying)

  • 여러 개의 파라미터를 받는 함수를 하나의 파라미터를 받는 여러 개의 함수로 쪼개는 것을 커링이라고 합니다.

  • a와 b를 받는 f를 f1과 f2로 쪼개면 cf처럼 될 수 있죠. 리턴값을 쪼갠 f2함수로 대체할 수 있으니까요.

  • 커링을 하는 이유는 합성을 원활하게 하기 위해서입니다. 함수의 Output이 다른 함수의 Input으로 연결되면서 합성(Composition)됩니다. 함수들이 서로 chain을 이루면서 연속적으로 연결이 되려면, Output과 Input의 타입과 개수가 같아야 합니다.

  • 함수의 Output은 하나밖에 없으니 Input도 모두 하나씩만 갖도록 한다면 합성하기가 쉬워질 것입니다.

비동기 함수(Async Result)

  • 함수는 input에 따라 연산을 하고 output을 리턴하지만, 시간이 오래 걸리는 경우라면 프로그램이 연산하는동안 멈추겠죠. 이런 것을 동기식(sync)이라고 합니다.

  • 반대로 비동기식(async)도 있습니다. 얘는 결과는 나중에 전달 받기로 하고 프로그램의 동작은 멈추지 않는 방식이죠. 네트워킹, 오랜 연산이 걸리는 작업, 딜레이가 포함된 작업은 비동기 방식으로 작업하는 것이 좋습니다.

  • 아래의 코드는 같은 작업의 sync, async 인데요. 중요한 부분은 결과값으로 전달하는 것이 아니라 result: @escaping (Int) -> Void) 라는 함수를 호출함으로써 전달하는 거에요.

  • 이게 무슨 뜻이냐면, sum 이라는 값이 나중에 들어오면 그때 그 결과가 발생하는 시점에 sum을 result에 매개변수로 호출하겠다라는 의미죠.

3. FizzBuzz 실습하기

  • 간단한 프로그램인 fizzbuzz로 일반적인 로직을 함수형 프로그래밍으로 바꿔보겠습니다.

  • 일단 fizz와 buzz를 클로저로 각각 정의합니다. 그리고 그것을 바탕으로 fizzbuzz 또한 새롭게 정의했죠.

  • 그리고 일반적인 로직의 가장 큰 문제점이 i 변수인데요. 외부 변수이기 때문에 side-effect가 발생합니다. 그래서 이 녀석을 loop 라는 함수로 만들어줄 거에요. input 값에만 output이 변동됩니다.

4. 자판기 예제 실습하기

  • 코드는 총 3단계로 나누어집니다. MODEL, UI, LOGIC 입니다. 먼저 MODEL에서 Product, Input, Output, State 값을 enum으로 정의합니다.

  • 그리고 다음은 UI를 구현하는데요. 여기에 예제 코드가 있으니 필요하시다면 참고하시면 될 것 같습니다. @IBOutlet, @IBAction 밖에는 없습니다.

  • 마지막으로 LOGIC 부분입니다. 여기서는 위에서 배웠던 커링이나 합성같은 것들이 대거 쓰였죠. 사실 이 부분이 가장 중요합니다.

  • 하나씩 살펴보면, 일단 버튼을 클릭하면 handleProcess가 동작하죠. 얘는 (String) -> Void 타입입니다.

  • 그리고 같은 타입으로 processHandler가 있죠. money 라는 상태값이 있기 때문에 state를 매개변수로 받고 있는데요. 함수형 프로그래밍에서 가장 중요시됐던게 side-effect였죠? 원래라면 이 상태값은 외부에 전역변수로 나와있었을 겁니다.

  • 그런데, 지금은 handleProcess("100"), handleProcess("cola") 처럼 입력되면 String 값이 processHandler 내의 command로 넘어오죠. 이 command를 바탕으로 uiInput에서는 Input enum을 리턴하고 uiOutput에서는 레이블이나 이미지를 보여준다는 식의 이벤트를 처리합니다.

  • 또, operation 메소드에서 핵심은 processHandler에서 state를 받아서 적절하게 계산을 해준뒤에 그것을 다시 리턴한다는 겁니다.

  • 이렇게 하면, state라는 값이 processHandler라는 함수내에 갇혀있게 됩니다. state는 함수내에 갇혀있지만, 함수가 수행됨에 따라서 계속 갱신되고 저장될 수 있죠. 이런 기법을 메모이제이션(Memoization)이라고 합니다. 메모이제이션을 통해서 side-effect를 없앨 수 있게 되었습니다.

MODEL
UI
LOGIC
TOTAL

Reference

https://www.inflearn.com/course/swift-fp

Last updated