Skip to content

도서 리뷰 시리즈 - 함수형 사고

출처

『함수형 사고』 닐 포드 저 / 김재완 역 | 한빛미디어 | 2016년 07월

한 줄 리뷰

함수형 프로그래밍에 관심 있다면 필수로 읽어야 하는 도서

2장 전환

함수형 사고로의 전환은 어떤 경우에 세부적인 구현에 뛰어들지 않고 이런 고수준 추상 개념을 적용할지를 배우는 것이다.

고수준의 추상적 사고로 얻는 이점?

  1. 문제의 공통점을 고려하여 다른 방식으로 분류하기를 권장한다.
  2. 런타임이 최적화를 잘할 수 있도록 해준다는 것
    • 결과가 변하지 않는 한 작업 순서를 바꾸면 더 능률적이된다.
  3. 개발자가 엔진 세부사항에 깊이 파묻힐 경우 불가능한 해답을 가능하게 한다.
    • 단일 쓰레드로 작업했던 코드를 여러 쓰레스에서 처리가 필요하면 개발자가 저수준의 반복 과정을 제어해야 하기 때문에 쓰레드 관련 코드가 문제해결 코드에 섞이게 된다.

2.3.3 폴드/리듀스

  • foldLeft나 reduce는 캐터모피즘이라는 목록 조작 개념의 특별한 변형이다.
  • reduce는 초기값을 주어야 할 때 사용
  • fold는 누산기에 아무것도 없는 상태에서 시작

새로운 어휘를 배우는 것이 함수형 프로그래밍 같은 새로운 패러다임을 배우는 과정에서의 어려운점 중 하나이다.

3장 양도하라

3.1 반복 처리에서 고계함수로

반복처리 대신에 map과 같은 함수를 사용하여 제어를 포기하는 법을 보여주었다. 여기서 무엇이 양도되는 지는 확실하다. 고계함수 내에서 어떤 연산을 할 것인지를 표현하기만 하면, 언어가 그것을 능률적으로 처리할 것이다.

그렇다고 개발자가 저수준 추상 단계에서 코드가 어떻게 동작하는 지 이해하는 것까지 전부 떠 넘겨도 된다는 것은 아니다. 추상 개념을 사용할 때 함축된 의미를 반드시 알아야 한다.

3.3 커링과 부분적용

  • 커링(또는 부분적용)은 전통적인 객체지향 언어에서 팩토리 함수를 구현할 상황에서 사용하면 좋다.
  • Gof의 탬플릿 메서드 패턴을 대체할 수 있다.
    • 탬플릿 메서드: 구현의 유연성을 보장하기 위해 내부의 추상 메서드를 사용하는 패턴
  • 비슷한 인수 값들로 여러 함수를 연속적으로 불러올 때에는 커링을 사용하여 묵시적 인수값을 제공할 수가 있다.
    • 묵시적: 직접적으로 드러내지 않고 은연중에 드러냄

3.4 스트림과 작업 재정렬

함수형 언어에는 Stream이란 추상 개념이 정의되어 있다. Stream은 여러모로 컬렉션과 흡사하지만 바탕 값(backing value)이 없다. 대신 원천에서 목적지까지 값들이 흐르게끔 한다. 예에서 원천은 names 컬렉션이고 목적지는 collect() 함수이다. 이 두 작업 사이에서 map()과 filter()는 게으른 함수이다. 다시 말하자면 이들은 실행을 가능하면 미룬다. 이들은 목적지에서 요구하지 않으면 결과를 내려고 시도 하지도 않는다.

java
public String cleanNames(List<String> names) {
  if (names == null) return "";
  return names
    .stream()
    .map(e -> capitalize(e))
    .filter(n -> n.length() > 1)
    .collect(Collectors.joining(","));
}

4장 열심히 보다는 현명하게

4.1 메모이제이션

메모이제이션이란 단어는 영국의 인공지능 연구학자인 도널드 미치(Donald Michie)가 연속해서 사용되는 연산 값을 함수 레벨에서 캐시하는 것을 지칭하는 것으로 처음 사용하였다. 오늘날 메모이제이션은 함수형 프로그래밍 언어에서 흔히 내장된 기능이거나 쉽게 구현할 수 있는 기능이다.

함수 캐싱은 전형적인 컴퓨터과학의 트레이드오프이다. 이 방법은 좋은 성능을 위해서 메모리를 더 많이 사용한다.

4.1.1 캐싱

  • 메서드 레벨에서의 캐싱: 클래스 내부 캐싱(intraclass caching)
  • 합산 결과를 캐시하기: 이미 수행된 결과를 재사용하는 것

4.1.2 메모이제이션의 첨가

  • 함수를 메모아이즈하는 것은 메타함수를 적용하는 것이라고 할 수 있다. 즉 리턴값이 아니라 함수에 어떤 것을 적용하는 것이다. 커링도 하나의 메타함수기법이다.
  • 메모아이즈된 함수는 부수효과가 없어야 하고, 외부정보에 절대로 의존하지 말아야 한다.

4.2 게으름

표현의 평가를 가능한 최대로 늦추는 기법인 게으른 평가는 함수형 프로그래밍 언어에서 많이 볼 수 있는 기능이다. 게으른 컬렉션은 그 요소들을 한꺼번에 미리 연산하는 것이 아니라, 필요에 따라 하나씩 전달해준다.

이렇게 하면 몇 가지 이점이 있다.

  • 우선 시간이 많이 걸리는 연산을 반드시 필요할 때까지 미룰 수 있게 된다.
  • 둘째로, 요청이 계속되는 한 요소를 계속 전달하는 무한 컬렉션을 만들 수 있다.
  • 셋째로, 맵이나 필더 같은 함수형 개념을 게으르게 사용하면 효율이 높은 코드를 만들 수 있다.

프로그래밍 언어가 엄격한지 혹은 관대한지(게으른지)에 따라 결과가 다를 것이다. 엄격한 언어에서 이 코드를 실행하면 에러가 발생하고, 관대한 언어에서는 결과가 제대로 4로 나올 것이다.

4.2.1 자바의 게으른 반복자

자바는 게으른 컬렉션을 네이티브로 지원하지 않지만, Iterator를 사용하면 흉내를 낼 수 있다.

5장 진화하라

5.1 적은 수의 자료구조, 많은 연산자

함수형 프로그래밍 언어에서는 주요 자료구조(List, Set, Map)와 거기에 따른 최적화된 연산들을 선호한다. 이런 기계 장치에 자료구조와 함수를 끼워 넣어서 특정한 목적에 맞게 커스터마이즈 하는 것이다.

5.3 디스패치 다시 생각하기

디스패치란 넓은 의미로 언어가 작동 방식을 동적으로 선택하는 것을 말한다. 디스패치 방식의 한 예로 스칼라의 패턴 매칭이 있다.

5.4 연산자 오버로딩

함수형 언어의 공통적인 기능은 연산자 오버로딩이다. 이것은 +, -, *와 같은 연산자를 새로 정의하여 새로운 자료형에 적용하고 새로운 행동을 하게 하는 기능이다.

5.4.2 스칼라

스칼라는 연산자와 메서드의 차이점을 없애는 방법으로 연산자 오버로딩을 허용한다. 즉 연산자는 특별한 이름을 가진 메서드에 불과하다. 따라서 곱셈 연산자를 스칼라에서 오버라이드하려면 * 메서드를 오버라이드하면 된다.

5.5 함수형 자료구조

대부분의 함수형 언어들은 예외 패러다임을 지원하지 않기 때문에 개발자는 다른 방법으로 오류조건을 표현해야 한다.

예외는 많은 함수형 언어가 준수하는 전체 몇가지를 깨뜨린다. 함수형 언어는 부수효과가 없는 순수함수를 선호한다. 그런데 예외를 발생시키는 것은 예외적인 프로그램 흐름을 야기하는 부수효과이다. 함수형 언어들은 주로 값을 처리하기 때문에 프로그램의 흐름을 막기 보다는 오류를 나타내는 리턴 값에 반응하는 것을 선호한다.

함수형 프로그램이 선호하는 또 하나의 특성은 참조투명성이다. 호출하는 입장에서는 단순한 값 하나를 사용하든, 하나의 값을 리턴하는 함수를 사용하든 다를 바가 없어야 한다. 만약 호출된 함수에서 예외가 발생할 수 있다면 호출하는 입장에서 안전하게 값을 함수로 대체할 수 없을 것이다.

5.5.2 Either 클래스

함수형 언어에서 다른 두 값을 리턴해야 하는 경우가 종종있는 데 그런 행동을 모델링하는 자료구조가 Either 클래스이다. Either는 왼쪽 또는 오른쪽 값중 하나만 가질 수 있게 설계되었다. 이런 자료구조를 분리합집합(disjoint union)이라하며, 분리합집합은 두 자료형이 들어 갈 자리가 있지만, 둘 중 하나만 지닐 수 있다. 함수형의 보편적인 관례에 따라 Either 클래스의 왼쪽이 예외, 오른쪽이 결과값이다.

5.5.3 Option 클래스

Option 클래스는 적당한 값이 존재하지 않는 경우를 의미하는 none, 성공적인 리턴을 의미하는 some을 사용하여 예외조건을 더 쉽게 표현한다.

6장 전진하라

6.2 함수 수준의 재사용

합성은 함수형 프로그래밍 라이브러리에서 재사용의 방식으로 자주 사용된다.

함수형 프로그래밍은 구조물들 간에 잘 알려진 관계(Coupling)를 만들기 보다는, 큰 단위의 재사용 매커니즘을 추출하려한다. 이런 노력은 객체간의 관계(Morphism)를 규정하는 수학이 한 분야인 카테고리 이론에 근거를 둔다.

7장 실용적인 사고

7.2.1 아키텍처

함수형 아키텍처는 불변성이 그 중심에 있고, 이를 최대한 사용하려 시도한다. 함수형 프로그래머 처럼 사고하려면 불변성을 받아들이는 것이 중요하다. 자바에서 불변형 객체를 만들려면 복잡한 선행작업이 필요하지만 추상화로 얻게 되는 이점들이 이를 충분히 보상해준다.

8장 폴리그랏과 폴리패러다임

함수형 프로그래밍 패러다임은 문제와 그것을 푸는 데 사용되는 도구에 관한 사고의 틀이라고 할 수 있다.

8.3 멀티 패러다임 언어의 결과

멀티 패러다임 언어는 개발자가 각 패러다임을 적재적소에 사용할 수 있다는 엄청난 이점이 있다. 이렇게 이점이 많긴 하지만, 개발자들이 큰 프로젝트에 임할 때는 멀티 패러다임 언어를 조심해서 사용해야 한다. 언어가 다양한 추상화와 철학을 반영하기 때문에 작은 개발자 그룹이 서로 전혀 다른 라이브러리를 만들 수도 있다.

각 패러다임은 근본적으로 다른 각도에서 문제 해결에 접근한다. 예를 들어 객체지향 언어에서는 코드 재사용이 구조 중심이지만 함수형 언어에서는 합성과 고계함수 중심이다. 한 가지 해법이 있다. 모든 개발자는 같은 목표를 향해 일 할 수 있어야 한다는 엔지니어링 철학에 따른 것이다.