Blog

Read

Functional Programming

Jul 25, 2022

Functional Programming

함수는 어디에서나 실행가능 하다. 각 언어마다 특성이 존재하나 함수형 프로그래밍을 지원하는 언어는 higher-order function · closure · currying 등의 기능을 포함하고 있다. 함수형 프로그래밍 자체가 리스트(LISP) 같은 데이터를 조작하는 방법에서 왔다고 하니 여러 자료형을 다루는 법을 아는것도 필요하다.



Imperative vs Declarative

개발자는 코드를 작성하는 시간보다 읽는 시간이 더 길다고 한다. 이를 다시 말하면 코드를 읽는 시간을 줄일 필요가 있다는 것이다. 명령형 코드는 분기처리 · 상태 · nullable 등으로 인해 가독성을 해치게 되고 읽기에 많은 시간을 소비하게 된다. 이에 반해 선언형 프로그래밍은 내부적으로 코드 구현을 숨기고 연산 · 작업을 표현한다. 이를 내부 메커니즘의 추상화라고 한다. 이러한 추상화를 이루기 위해서는 따라야 할 몇가지 규칙이 존재한다.


너무 많은 추상화는 오히려 코드 이해를 해친다.


Pure

먼저 사용하는 코드가 신뢰성이 높아야 한다. 비즈니스는 항상 변화하고 개발자는 이러한 변화에 대응해야 한다. 수천 · 수만줄의 코드에서 요구사항에 따라서 기능을 변경했을 때 문제가 발생하지 않을거라는 건 말도 안되는 믿음이다. 이를 예방하기 위해 TDD 등의 테스트를 도입함으로써 어느정도 예방을 하긴하지만 근본적으로 가장 좋은 대응은 코드(함수)를 예측 · 커버 가능한 범위에서 사용하는 것이다. 그리고 여기서 순수함수가 나온다.


순수함수는 동일한 입력이 주어졌을 때 항상 동일한 출력을 반환하고 관찰할 수 있는 부작용이 없는 프로시저다.

웹은 다양한 라이브러리와 API를 통해 작동하므로 사이드 이펙트가 발생할 수 밖에 없는 구조다. 따라서 Haskeller 처럼 완벽한 순수함수를 추구 하기보다 순수 · 사이드 이펙트를 일으키는 함수를 구분 및 관리를 목표로 한다.



Perspective ¹

OOP 에서는 캡슐화를 통해 데이터 접근을 제어 할 수 있으며 이를 통해 응집력을 키울 수 있다. 그에 반해 함수는 데이터와 연산을 분리하므로 응집된 코드를 작성하기 위한 방법이 필요하다.

데이터와 데이터에 대한 작업을 분리하는 과정에는 타입스크립트 세계에서 타입을 통해 구현할 수 있다. 데이터를 표현하는 타입과 작업을 표현하는 모듈을 구성한다.

모듈은 잘 정의된 인터페이스와 숨겨진 구현을 가진 "작은 프로그램 조각" 이다.


이러한 구성의 최대 장점은 모듈이 개별적으로 작동하기 때문에 복잡성 처리에 용이하다는 것이다. 또한 A기능과 B기능에 의존성없이 기능 구현이 가능하다는 점도 있다. 이러한 모듈화를 목표로 하는 기술은 몇 가지 필수요소를 제공해야 한다.

  1. 인터페이스와 구현의 좋은 분리를 제공하는 모듈 구조가 있어야 한다.
  2. 교체된 모듈에 의존하는 모듈을 변경하거나 다시 컴파일하지 않고 하나의 모듈을 동일한 인터페이스를 가진 다른 모듈로 교체하는 방법이 있어야 한다.
  3. 모듈을 함께 연결할 수 있는 방법이 있어야 한다.


Communication

FP에 많은 장점이 존재한다고 하여도 다른 개발자가 이해하지 못하는 코드(방식)는 결국 독이된다. OOP 같은 다른 패러다임과의 적절한 조화가 필요하며 러닝커브가 적어야 한다.



Structure ²

크고 복잡한 애플리케이션(웹 서비스 등)을 구조화 할때의 FP와 OOP 패러다임의 차이는 없으며 하위 원칙에 의존한다.

  • Modularity : 소프트웨어는 개별적이고 재사용 가능한 컴포넌트로 구성되어야 한다.
  • Sepration of concerns : 각 컴포넌트는 한가지 일만 해야 한다.
  • Layering : 상위 수준 컴포넌트는 하위 수준에 의존할 수 있지만 반대는 불가능하다.
  • Loose coupling : 컴포넌트는 의존하는 컴포넌트의 내부 세부 정보를 알 수 없다. 따라서 컴포넌트에 대한 변경사항은 해당 컴포넌트에 종속된 컴포넌트에 영향을 주어서는 안된다.


Signature and Types

함수는 함수형 프로그래밍의 빌딩 블록이며 종종 문제에 접근할때 가장 먼저 하는 일이다. 따라서 함수 시그니처를 올바르게 구성하는 것이 가장 중요하다.


함수의 시그니처는 인풋과 아웃풋의 타입을 알려준다. 시그니처를 통해 어느정도 동작을 추론할 수 있으며 함수의 의도를 표현하는데 좋은 이름을 사용하면 명확한 API를 작성할 수 있다.

1 fullname :: (string, string) -> string

OOP에서는 TDD에서 테스트 명이나 인터페이스 를 통해 구현하던 API를 함수형 프로그래밍에서는 함수 시그니처로 표현한다.


함수 시그니처를 파악한 다음 데이터 타입을 정의해야 한다.

  • 로직은 함수로 인코딩 된다
  • 데이터는 함수의 입력 및 출력으로 사용되는 데이터 객체로 캡처된다.





  1. Functional Programming, Simplified: (Scala edition)

  2. Functional Programming in C#