코틀린 함수형 프로그래밍 [코틀린으로 제대로 함수형 프로그래밍 익히기]
- 원서명Functional Programming in Kotlin (ISBN 9781617297168)
- 지은이마르코 버뮬런(Marco Vermeulen), 루나르 비아르드나손(Rúnar Bjarnason), 폴 치우사노(Paul Chiusano)
- 옮긴이오현석, 서형국
- ISBN : 9791161757681
- 45,000원
- 2023년 07월 31일 펴냄
- 페이퍼백 | 692쪽 | 188*235mm
- 시리즈 : 프로그래밍 언어
책 소개
소스 코드 파일은 여기에서 내려 받으실 수 있습니다.
https://github.com/AcornPublishing/function-pro-kotlin
요약
함수형 프로그래밍을 들어본 개발자는 많지만 제대로 된 함수형 프로그래밍을 배우기 위해 하스켈이나 스칼라까지 배울 여유가 없는 독자도 많을 것이다. 이 책은 정석적인 함수형 프로그래밍 책이지만 코틀린을 사용하기 때문에 더 쉽게 함수형 프로그래밍에 접근하고 싶은 독자들에게 도움이 된다. 함수형 프로그래밍을 자신의 개발에 적용하고 싶은 개발자나, 함수형 패러다임을 익혀서 새로운 사고 방식을 익히고 직간접적으로 그 지식을 활용하고 싶은 개발자들이 연습문제를 풀어가면서 점차 함수형 프로그래밍 지식을 쌓아 나갈 수 있게 해준다.
추천의 글
함수형 프로그래밍(FP, Functional Programming)은 소프트웨어를 통해 문제에 접근하고 문제를 해결하는 방식을 근본적으로 바꿨다. 함수형 프로그래밍은 불변성, 함수의 순수성, 함수 조합을 올바르고 예측 가능한 프로그램을 구축하는 수단으로 사용한다. 그 속성들은 최근 십여 년간 분산 시스템, 응용프로그램, 소프트웨어 공학을 바라보는 방식을 변화시켰다.
여러 해 전에는 함수형 프로그래밍의 성공에 대한 의문이 제기됐고 다른 패러다임과 비교되거나 연결되는 경우가 자주 있었다. 하지만 오늘날 대부분의 언어 컴파일러와 표준 라이브러리가 함수형 프로그래밍의 개념을 광범위하게 채택하고 있으며, 커뮤니티와 팀이 시스템 설계와 아키텍처에 접근하는 방식에도 함수형 프로그래밍이 영향을 끼쳤다.
코틀린은 컨티뉴에이션(continuation), 널이 될 수 있는 타입, 인터페이스, 패턴 매칭, 대수적 데이터 타입 등과 같은 함수형 기능을 포함하는 멀티패러다임, 멀티플랫폼 언어다. 이런 특징 덕분에 코틀린은 독특하고 재미있으며 가장 효율적인 함수형 프로그래밍 언어가 될 수 있다.
언어마다 함수형 프로그래밍의 특성을 드러내는 자신만의 표현 방법이 있으므로, 여러분은 함수형 프로그래밍의 본질이 무엇이고 이 본질을 어떻게 제대로 배우는지 궁금할 수 있다. 이 책의 저자 중 한 사람인 마르코는 유명한 ‘빨간 책(Red Book)’인 『스칼라로 배우는 함수형 프로그래밍』(제이펍, 2015)을 이번에는 코틀린을 밑바닥부터 함수형 프로그래밍 패턴을 설명하는 수단으로 사용해 각색했다. 이 책은 함수 합성과 대수적 설계라는 기본 개념을 자세히 설명하고, 그 후 함수형 패턴과 데이터 타입을 밑바닥부터 어떻게 구현하는지 보여주는 연습문제와 예제를 통해 함수형 프로그래밍을 연습하고 배우도록 여러분을 초대한다.
오류 처리 같은 일반적인 시나리오에서 시작해 스트리밍 같은 더 복잡한 경우에 이르도록, 이 책은 함수형 프로그래머의 배움을 보완해주고 핵심 함수형 추상화와 패턴을 배우기 위한 근본적인 접근 방식을 제공한다.
─ 라울 라하(Raul Raja)
애로우 라이브러리 메인테이너/47 디그리스(47 Degrees) CTO
이 책에서 다루는 내용
◆ 실제 상황에서 활용할 수 있는 함수형 프로그래밍 기법
◆ 함수형 설계를 할 때 유용한 공통 구조와 숙어들
◆ 단순성, 모듈성, 버그 감소!
이 책의 대상 독자
코틀린 언어 자체와 코틀린 언어의 기능을 이미 충분히 이해한 사람을 대상으로 한다. 또한 객체지향 설계에 익숙하고 클래스, 인터페이스, 메서드, 변수를 잘 알아야 하지만, 함수형 프로그래밍에 대한 선수 지식이나 경험은 필요하지 않다.
일차적으로는 학문적인 책이지만 한편으로는 매우 실무적인 책이기도 하다. 전반에 걸쳐 여러 코드 예제를 살펴보고, 배우는 과정을 강화해주는 연습문제를 다수 제공한다. 그러므로 인텔리제이(IntelliJ) IDEA나 코틀린을 완전히 지원하는 비슷한 다른 IDE를 잘 이해하고 있어야 한다. IDE 대신 텍스트 편집기나 터미널을 사용하는 게 더 익숙하다면 그 또한 상관없다.
이 책의 구성
4개 부, 총 15개 장으로 이뤄져 있다. 1부에서는 함수형 프로그래밍을 소개하고 이 책 전반에 걸쳐 사용할 기본적인 빌딩 블록을 제공한다.
1장은 함수형 프로그래밍의 의미를 설명하고 함수형 프로그래밍의 이점에 대해 감을 잡도록 해준다.
2장은 재귀로 함수형 루프를 작성하는 방법이나 꼬리 호출 제거(tail-call elimination), 고차 함수(high-order function), 함수형 다형성(polymorphism) 같은 기본기를 알려준다.
3장은 리스트(list)와 트리(tree)라는 두 가지 컬렉션을 순수 함수형으로 정의하고 다루는 방법을 설명한다.
4장은 예외를 던지지 않고 효과적으로 오류를 처리하는 방법을 깊이 살펴본다.
5장은 효율적인 평가를 위한 비엄격성(non-strictness(지연성laziness이라고도 한다))에 대해 논의하고, 스트림 데이터 타입도 소개한다.
6장은 전이(transition)라고도 하는 상태 변이 동작(state mutation action)을 함수형 프로그램에서 다루는 방법을 알려주면서 1부를 마무리한다.
2부는 몇 가지 콤비네이터(combinator) 라이브러리를 설계하는 과정을 아주 느슨하게 설명하는 스타일로 돼 있다는 점에서 1부와 꽤 다르다.
7장은 비동기 병렬 처리(asynchronous parallel processing)를 위한 함수형 라이브러리를 설계하고 구축하는 방법을 보여준다.
8장은 난수화한 테스트를 위한 속성 기반 테스트(property-based test) 라이브러리를 설계하는 방법을 보여준다.
9장은 파싱(parsing) 영역으로 들어가서 JSON 파서 콤비네이터 라이브러리의 우아한 설계를 도출하는 방법을 보여준다.
3부에서는 타입 지정 함수형 프로그래밍에 속한 더 고급 주제인 타입 클래스(type class)를 살펴본다. 실제 상황에서 사용하는 몇 가지 설계 패턴 추상화를 다루고 여러분이 이런 추상화를 사용할 수 있도록 준비시킨다.
10장은 여러 값을 조합할 때 쓸 수 있는 추상화인 모노이드(monoid)를 다룬다.
11장은 악명 높은 모나드(monad)를 샅샅이 다루고 예제를 사용해 명확히 설명한다.
12장은 적용 가능 펑터(applicative functor)와 순회 가능 펑터(traversable functor)를 설명하면서 적용 가능(applicative)의 요건이 무엇인지 소개하고 모나드와 적용 가능의 차이를 보여준다.
마지막 4부에서는 앞서 배운 내용을 바탕으로 순수 함수형 코드에서 부수 효과를 다룬다.
13장은 임베디드된 명령형 DSL을 사용해 효과가 있는 코드의 표현을 단순화하는 방법을 보여주기 위해 I/O 모나드(프리 모나드)를 소개한다.
14장은 특정 부수 효과와 변이를 순수 함수 프로그램 안에 지역화하는(밖으로 노출시킬 수 없게 막으면서 내포시키는) 방법을 알려준다.
15장은 지금껏 배운 모든 내용을 집대성해, I/O 스트림을 점진적으로 처리할 수 있는 모듈화 가능하며 합성 가능한 프로그램을 위한 스트림 API를 개발한다.
목차
목차
- 1부. 함수형 프로그래밍 소개
- 1장. 함수형 프로그래밍이란?
- 1.1 FP의 장점: 간단한 예제
- 1.1.1 부수 효과가 있는 프로그램
- 1.1.2 함수형 해법: 부수 효과 제거하기
- 1.2 정확히 (순수) 함수란 무엇인가?
- 1.3 참조 투명성, 순수성, 치환 모델
- 1.4 앞으로 살펴볼 내용
- 요약
- 2장. 코틀린으로 함수형 프로그래밍 시작하기
- 2.1 고차 함수: 함수를 함수에 넘기기
- 2.1.1 잠시 돌아가기: 루프를 함수적으로 작성하는 방법
- 2.1.2 첫 번째 고차 함수 작성하기
- 2.2 다형적 함수: 타입에 대해 추상화하기
- 2.2.1 다형적 함수 예제
- 2.2.2 익명 함수를 사용해 고차 함수 호출하기
- 2.3 타입에 맞춰 구현하기
- 요약
- 3장. 함수형 데이터 구조
- 3.1 함수형 데이터 구조 정의하기
- 3.2 함수적 데이터 구조 다루기
- 3.2.1 타입으로 매칭하기 위한 ‘when’
- 3.2.2 if-else를 대신하는 when
- 3.2.3 패턴 매칭은 무엇이며 코틀린 매칭과 어떤 차이가 있나?
- 3.3 함수형 데이터 구조 안의 데이터 공유
- 3.3.1 데이터 공유의 효율
- 3.4 리스트에 대한 재귀와 이를 고차 함수로 일반화하는 방법
- 3.4.1 리스트에 작용하는 다른 함수들
- 3.4.2 코틀린 표준 라이브러리의 리스트
- 3.4.3 단순한 요소들로부터 리스트 함수를 합성하는 데 따른 비효율
- 3.5 트리
- 요약
- 4장. 예외를 사용하지 않고 오류 다루기
- 4.1 예외를 던지는 것의 문제점
- 4.2 예외에 대한 문제가 있는 대안
- 4.2.1 센티넬 값
- 4.2.2 디폴트 값 제공
- 4.3 Option으로 성공 상황 인코딩하기
- 4.3.1 Option 사용 패턴
- 4.3.2 Option 합성, 끌어올리기 및 예외 기반 API 감싸기
- 4.3.3 Option과 for 컴프리헨션 사용하기
- 4.4 성공과 실패 조건을 Either로 인코딩하기
- 4.4.1 Either를 for 컴프리헨션에서 사용하기
- 요약
- 5장. 엄격성과 지연성
- 5.1 엄격한 함수와 엄격하지 않은 함수
- 5.2 확장 예제: 지연 리스트
- 5.2.1 스트림을 메모화하고 재계산 피하기
- 5.2.2 스트림 관찰을 위한 도우미 함수
- 5.3 프로그램 기술과 평가 분리하기
- 5.4 공재귀 함수를 통해 무한한 데이터 스트림 생성하기
- 5.5 결론
- 요약
- 6장. 순수 함수형 상태
- 6.1 부수 효과를 사용해 난수 생성하기
- 6.2 순수 함수형 난수 생성기
- 6.3 상태가 있는 API를 순수 함수형 API로 만들기
- 6.4 상태 동작을 전달하는 암시적 접근 방법
- 6.4.1 상태 동작 조합을 통해 더 큰 능력 발휘하기
- 6.4.2 상태 동작을 내포시켜서 재귀적으로 재시도하기
- 6.4.3 콤비네이터 API를 초기 예제에 적용하기
- 6.5 일반적인 상태 동작 타입
- 6.6 순수 함수형 명령형 프로그래밍
- 6.7 결론
- 요약
- 2부. 함수형 설계와 콤비네이터 라이브러리
- 7장. 순수 함수형 병렬성
- 7.1 데이터 타입과 함수 선택하기
- 7.1.1 병렬 계산을 위한 데이터 타입
- 7.1.2 동시성을 보장하기 위해 병렬 계산 조합하기
- 7.1.3 실행할 계산을 명시적으로 표시하기
- 7.2 표현 선택하기
- 7.3 최종 사용자를 염두에 두고 API 다듬기
- 7.4 대수적 등식을 사용해 API에 대해 추론하기
- 7.4.1 매핑 규칙
- 7.4.2 논리 스레드 분기의 법칙
- 7.4.3 논블로킹 구현을 위해 액터 사용하기
- 7.5 가장 일반적인 형태로 콤비네이터 다듬기
- 요약
- 8장. 속성 기반 테스트
- 8.1 속성 기반 테스트 맛보기
- 8.2 데이터 타입과 함수 선택하기
- 8.2.1 가능한 API에 대한 짧은 초기 예제 코드 모으기
- 8.2.2 속성의 의미와 API 탐구하기
- 8.2.3 생성기의 API와 의미 발견하기
- 8.2.4 생성된 값에 의존하는 생성기
- 8.2.5 속성 데이터 타입 다듬기
- 8.3 테스트 케이스 최소화
- 8.4 라이브러리를 사용하고 사용자 경험 개선하기
- 8.4.1 몇 가지 간단한 예제
- 8.4.2 병렬 계산에 어울리는 테스트 스위트 작성하기
- 8.5 고차 함수나 다른 가능성 생성하기
- 8.6 생성기의 법칙
- 8.7 결론
- 요약
- 9장. 파서 콤비네이터
- 9.1 대수 설계하기
- 9.1.1 문자 하나를 인식하기 위한 파서
- 9.1.2 전체 문자열을 인식하기 위한 파서
- 9.1.3 반복을 인식하기 위한 파서
- 9.2 대수를 설계하는 한 가지 접근 방법
- 9.2.1 문자 반복 횟수 세기
- 9.2.2 슬라이싱과 비어 있지 않은 반복
- 9.3 문맥에 대한 민감성 처리하기
- 9.4 JSON 파서 작성하기
- 9.4.1 JSON 파서의 예상 동작 정의하기
- 9.4.2 JSON 형식 정리
- 9.4.3 JSON 파서
- 9.5 보고를 통해 오류를 표면에 드러내기
- 9.5.1 오류 보고 첫 번째 시도
- 9.5.2 오류 내포를 통해 오류 누적시키기
- 9.5.3 분기와 백트래킹 제어
- 9.6 대수 구현하기
- 9.6.1 점진적으로 대수의 구현 구축하기
- 9.6.2 파서의 시퀀스 처리하기
- 9.6.3 파서에 레이블을 붙여서 오류 메시지 잡아내기
- 9.6.4 오류 회복과 백트래킹
- 9.6.5 문맥 민감 파서를 통해 상태 전파하기
- 9.7 결론
- 요약
- 3부. 함수형 설계의 일반 패턴
- 10장. 모노이드
- 10.1 모노이드란 무엇인가?
- 10.2 모노이드로 리스트 접기
- 10.3 결합성과 병렬성
- 10.4 예제: 병렬 파싱
- 10.5 접을 수 있는 데이터 구조
- 10.6 모노이드 합성하기
- 10.6.1 더 복잡한 모노이드 조립하기
- 10.6.2 순회 융합을 위해 합성한 모노이드 사용하기
- 요약
- 11장. 모나드와 펑터
- 11.1 펑터
- 11.1.1 map 함수를 일반화해 펑터 정의하기
- 11.1.2 법칙의 중요성과 펑터에 대한 관계
- 11.2 모나드: flatMap과 unit 함수 일반화하기
- 11.2.1 모나드 인터페이스 소개
- 11.3 모나드적인 콤비네이터
- 11.4 모나드 법칙
- 11.4.1 결합 법칙
- 11.4.2 구체적인 모나드에 대해 결합 법칙 증명하기
- 11.4.3 왼쪽과 오른쪽 항등 법칙
- 11.5 도대체 모나드란 무엇인가?
- 11.5.1 항등 모나드
- 11.5.2 State 모나드와 부분적인 타입 적용
- 요약
- 12장. 적용 가능 펑터와 순회 가능 펑터
- 12.1 재사용성을 위해 모나드 일반화하기
- 12.2 모나드의 대안인 적용 가능 펑터
- 12.3 모나드와 적용 가능 펑터의 차이
- 12.3.1 Option 적용 가능 펑터와 Option 모나드의 비교
- 12.3.2 Parser 적용 가능 펑터와 Parser 모나드
- 12.4 적용 가능 펑터의 장점
- 12.4.1 모든 적용 가능 펑터가 모나드는 아니다
- 12.5 적용 가능 법칙을 사용해 프로그램에 대해 추론하기
- 12.5.1 왼쪽과 오른쪽 항등원 법칙
- 12.5.2 결합 법칙
- 12.5.3 자연성의 법칙
- 12.6 순회 가능을 사용해 traverse와 sequence 추상화하기
- 12.7 Traversable을 사용해 고류 타입을 반복적으로 변환하기
- 12.7.1 모노이드에서 적용 가능 펑터로
- 12.7.2 상태 동작을 전파시키는 동시에 컬렉션 순회하기
- 12.7.3 순회 가능 구조 조합하기
- 12.7.4 단일 패스 효율성을 위한 트리 융합
- 12.7.5 내포된 순회 가능 구조를 동시 순회하기
- 12.7.6 모나드 합성의 함정과 함정을 피하는 방법
- 요약
- 4부. 효과와 입출력
- 13장. 외부 효과와 I/O
- 13.1 효과가 있는 프로그램에서 효과 뽑아내기
- 13.2 효과가 있는 코드를 분리하기 위해 IO 타입 도입하기
- 13.2.1 입력 효과 처리하기
- 13.2.2 간단한 IO 타입의 장단점
- 13.3 실체화와 트램폴린화를 통해 스택 오버플로 오류 방지하기
- 13.3.1 데이터 생성자로 흐름 제어 실체화하기
- 13.3.2 트램폴린화: 스택 오버플로에 대한 일반적인 해법
- 13.4 더 적절한 뉘앙스의 IO 타입
- 13.4.1 타당한 가격이 붙은 모나드
- 13.4.2 콘솔 I/O만 지원하는 모나드
- 13.4.3 순수 해석기를 사용해 콘솔 I/O 테스트하기
- 13.5 논블로킹과 비동기 I/O
- 13.6 범용 IO 타입
- 13.6.1 세계의 반대쪽에 있는 주 프로그램
- 13.7 왜 IO 타입이 스트리밍 I/O에 대해 부족한가?
- 요약
- 14장. 지역 효과와 가변 상태
- 14.1 순수 함수형 코드 안에서 상태 변이가 합법적이다
- 14.2 부수 효과의 영역을 강제하는 데이터 타입
- 14.2.1 영역이 제한된 변이를 위한 DSL
- 14.2.2 가변 참조의 대수
- 14.2.3 가변 상태 동작 실행하기
- 14.2.4 ST 모나드의 데이터 타입으로 표현된 가변 배열
- 14.2.5 순수 함수적인 인플레이스 퀵소트
- 14.3 순수성은 맥락에 따라 달라진다
- 14.3.1 예제를 통한 정의
- 14.3.2 부수 효과로 취급할 수 있는 것은 무엇일까?
- 요약
- 15장. 스트림 처리와 점진적 I/O
- 15.1 명령형 I/O의 문제점: 예제
- 15.2 간단한 트랜스듀서를 사용해 스트림 변환하기
- 15.2.1 스트림 트랜스듀서를 만들기 위한 콤비네이터
- 15.2.2 이어 붙이기와 합성을 사용해 여러 트랜스듀서 합치기
- 15.2.3 파일 처리를 위한 스트림 트랜스듀서
- 15.3 프로토콜 파라미터화를 위한 확장 가능한 프로세스
- 15.3.1 스트림 발생을 위한 소스
- 15.3.2 스트림 트랜스듀서의 자원 안전성 보장하기
- 15.3.3 트랜스듀서를 단일 입력 스트림에 적용하기
- 15.3.4 다중 입력 스트림
- 15.3.5 출력 처리를 위한 싱크
- 15.3.6 효과가 있는 채널에서 효과 숨기기
- 15.3.7 동적인 자원 할당
- 15.4 실제 세계에서의 스트림 트랜스듀서 활용
- 요약
- 마무리하며
- 부록 A 연습문제 힌트와 팁
- 부록 B 연습문제 해답
- 부록 C 고류 타입
- 부록 D 타입 클래스
도서 오류 신고
정오표
정오표
[p. 575]
연습문제 3.25
fun <A> size(tree: Tree<A>): Int =
when (tree) {
is Leaf -> 1
is Branch -> 1 + size(tree.left) + size(tree.right)
}
->
fun maximum(tree: Tree<Int>): Int =
when (tree) {
is Leaf -> tree.value
is Branch -> maxOf(maximum(tree.left), maximum(tree.right))
}