위의 책을 읽고 정리한 글
→ 실제 구현 참조는 도움이 필요한 부분만 정리함
-
함수형 프로그래밍
- 함수를 이용하여 데이터의 참조 투명성을 보장하고 상태/가변 데이터 생성을 피하는 프로그래밍 방식
- 특징
- 불변성: 상태 변화가 없음
- 참조 투명성: 프로그램의 변경 없이 어떤 표현식을 값으로 대체 가능한 상태
- 일급 함수
- 함수를 함수의 매개변수로 넘길 수 있다
- 함수를 함수의 반환 값으로 넘길 수 있다
- 함수를 변수나 자료구조에 담을 수 있다
- 게으른 평가: 값이 필요한 시점에 실행 가능하며 시점을 핸들링 가능하다
- 이론 상 무한대의 값을 자료구조에 담을 수 있다
- 장점
- 부수 효과가 없는(적은) 프로그래밍으로 동시성 프로그래밍에 적합하다
- 모듈성이 높고 간결한 코드를 작성할 수 있다
- 컴파일러가 예측하기 좋은 코드를 만들어낸다
- 단점
- 순수 함수를 구현하기 위한 코드의 가독성이 좋지 않을 수 있다
- 재귀 스타일 코드는 무한 루프에 빠지기 쉽다
- 순수 함수는 만들기는 쉬우나 조합하는 것은 쉽지 않다
-
재귀
- 함수형 프로그래밍에서는 루프 대신 재귀를 이용한다
- 메모리를 직접 할당하는 대신 스택을 활용한다
- 설계 방법
- 종료 조건 정의: 자료구조가 더 이상 쪼개지지 않는 범위. 일반적으로 항등값
- 함수의 입력을 분할하여 어떤 부분에서 재귀 할지 정의
- 메모이제이션: 동적 계획법의 핵심으로 반복된 연산을 기억하여 다시 연산하지 않는 방식
- 함수형에서는 이전 계산 값을 재귀 함수의 매개변수로 받아서 캐싱을 대신한다
- 꼬리 재귀
- 재귀 함수 중에서 자신의 호출이 가장 마지막 연산인 경우
- 마지막 결과가 전체의 결과이므로 스택프레임을 사용하지 않아 오버플로 방지가 가능하다
- 상호 재귀 와 트램펄린
- 상호 재귀: 두 함수의 종료 조건 중 한 조건에 만족하면 재귀 호출이 종료되는 재귀
- 트램펄린: 상호 재귀를 꼬리 재귀로 변경하는 루프
sealed class Bounce<A> class Done<A>(val result: A): Bounce<A> class More<A>(val thunk: () -> Bounce<A>): Bounce<A> tailrec fun <A> tram(bounce: Bounce<A>) : A = when (bounce) { is Done -> bounce.result is More -> tram(bounce.thunk()) } fun even(n: Int): Bounce<Boolean> { 0 -> Done(false) else -> More { odd(n - 1) } } fun odd(n: Int): Bounce<Boolean> { 0 -> Done(false) else -> More { even(n - 1) } } tram(even(999))
-
고차 함수
-
함수를 매개변수로 받거나 반환하는 함수
-
장점
- 코드가 간결하고 재 사용성이 높다
- 기능 확장이 쉽다
- 자주 사용되는 패턴의 추상화에 용이하다
-
부분 함수
- 가능한 모든 입력 중 일부 입력에 대한 결과만 정의한 함수
- 하지만 가급적 만들지 않도록 해야 한다 → 함수는 모든 입력에 대해 처리 할 수 있어야 한다
-
부분 적용 함수
- 매개 변수의 일부 ( 또는 없음 ) 만 제공 받아 적용되는 함수
fun <P1, P2, R> ((P1, P2) -> R).partial(p1: P1): (P2) -> R { return { p2 -> this (p1, p2) } } fun <P1, P2, R> ((P1, P2) -> R).partial2(p2: P2): (P1) -> R { return { p1 -> this (p1, p2) } }
-
커링 함수
- 여러 개의 매개변수를 받는 함수를 분리하여, 단일 매개변수를 받는 부분 적용 함수의 체인을 만드는 방법
- 마지막 매개 변수의 입력 시점까지 함수의 실행을 늦출 수 있다
fun <P1, P2, P3, R> ((P1, P2, P3) -> R).curried(): (P1) -> (P2) -> (P3) -> R = { p1: P1 -> { p2: P2 -> { p3 : P3 -> this(p1,p2,p3) } } } fun <P1, P2, P3, R> (P1) -> (P2) -> (P3) -> R.uncurried(): (P1, P2, P3) -> R = { p1: P1, p2: P2, p3: P3 -> this(p1)(p2)(p3) }
-
합성 함수
-
고차 함수를 이용하여 두 함수를 결합하는 함수
-
입력 값의 타입과 반환 값 타입이 같아야 한다
-
포인트 프리 스타일
- 함수 함성을 이용하여 매개변수나 타입 선언 없이 함수를 만드는 방식
// 여기서 compose는 infix 인 함수합수 선언 val composed = func1 compose func2 compose func3 val result = composed( listof(1,2,3,4,5 ) )
-
여러 개의 매개 변수에 동일한 함수를 적용해야 할 때 함수 합성은 좋지 않다 → 가독성이 나쁘다
-
컬렉션 데이터 다루기
- filter, map, fold와 같은 함수가 존재한다
- foldLeft 는 크기나 크거나 함수 실행이 비용이 클 때 사용하고, foldRight는 크기가 예측 가능하고 함수 실행 비용이 작을 때 사용한다
- 두 컬렉션을 묶는 zip, zipWith 함수가 존재한다
- 순수 함수: 동일한 입력으로 항상 동일한 결과가 나오는 함수 → 부수 효과(side effect)가 없는 함수
- 부수 효과: 외부의 상태를 사용 또는 수정하는 행위
- 일급 객체
- 객체를 함수의 매개변수로 넘길 수 있다
- 객체를 함수의 반환 값으로 넘길 수 있다
- 객체를 변수나 자료구조에 담을 수 있다
- 코틀린에서 Unit은 void (primitive) 가 아닌 object ( 참조형 ) 타입이다
- 코틀린 sealed 클래스는 클래스를 묶은 클래스(타입)이라고 봐도 된다
- sealed interface는 카인드로 봐야 한다
- 코틀린 when 구문에서 패턴 매칭되는 경우 구현 블록에서 매개변수에 바로 접근가능하다
- 추상화: 관심있는 부분만 노출하는 나머지는 숨기는 것
- 일반화: 공통 부분만 추출하여 새로운 대상을 만드는 것
- 자바의 Optional은 Maybe 타입이다