Home 코틀린으로 배우는 함수형 프로그래밍 - 2장 코틀린으로 함수형 프로그래밍 시작하기
Post
Cancel

코틀린으로 배우는 함수형 프로그래밍 - 2장 코틀린으로 함수형 프로그래밍 시작하기

범위

  • 2장 코틀린으로 함수형 프로그래밍 시작하기

요약

  • 함수형 프로그래밍의 특징은 다음과 같다.
    • 불변성(immutable)
    • 참조 투명성(referential transparency)
    • 일급 함수(first-class function)
    • 게으른 평가(lazy evaluation)

개념 정리

  • 익명 함수(anonymous function): 함수 이름을 선언하지 않고, 구현부만 작성하는 함수를 표현하는 방식의 일종.
  • 확장 함수(extension function): 이미 작성된 클래스에 상속을 하거나 내부를 수정하지 않고 새롭게 추가한 함수.
  • 패턴 매칭(pattern matching): 값, 조건, 타입 등의 패턴에 따라서 매칭되는 동작을 수행하게 하는 기능.
  • 객체 분해: 객체를 구성하는 프로퍼티를 분해하여 편리하게 변수에 할당하는 것.
  • 제네릭(generic): rorcp 내부에서 사용할 데이터 타입을 외부에서 정하는 기법.
  • 변성(variance): 제네릭을 포함한 계층 관계에서 타입의 가변성을 처리하는 방식.
    • 무공변(invariant): 타입 S가 T의 하위 타입일 때, Box<S>Box<T>는 상속 관계가 없다.
    • 공변(covariant): 타입 S가 T의 하위 타입일 때, Box<S>Box<T>의 하위 타입이다.
    • 반공변(contravariant): 타입 S가 T의 하위 타입일 때, Box<T>Box<S>의 하위 타입이다.

책에서 기억하고 싶은 내용

  • 2.1 프로퍼티 선언과 안전한 널 처리
    • val: 읽기 전용 프로퍼티를 선언하는 예약어
    • var: 선언 이후에 수정이 가능한 가변(mutable) 프로퍼티를 선언하는 예약어
    • 타입 뒤에 ?를 붙이면 해당 프로퍼티는 값으로 널을 할당할 수 있다.
  • 2.2 함수와 람다
    • fun: 함수의 예약어
    • 반환 타입을 명시하지 않으면 Unit 타입을 반환한다.
  • 2.3 제어 구문
    • 코틀린에서 if문은 기본적으로 표현식이다. 표현식은 구문과 달리 결과로서 어떤 값을 반환한다.
    • when문도 표현식이다. when문은 java의 swich문이나 스칼라의 패턴 매칭과 유사한 기능을 한다.
  • 2.4 인터페이스
    • 코틀린에서 제공하는 인터페이스는 다음과 같은 특징이 있다.
      • 다중 상속이 가능하다.
      • 추상(abstract) 함수를 가질 수 있다.
      • 함수의 본문을 구현할 수 있다.
      • 여러 인터페이스에서 같은 이름의 함수를 가질 수 있다.
      • 추상 프로퍼티를 가질 수 있다.
  • 인터페이스에서는 추상 프로퍼티의 값을 직접 초기화할 수 없고, getter를 구현해야 한다.
1
2
3
4
interface Foo {
    val bar1:Int = 3 // 컴파일 에러
    val bar2: Int get() = 3
}
  • 2.5 클래스
    • data class
      • data class는 기본적으로 게터(getter), 세터(setter) 함수를 생성해주고, hashCode, equals, toString 함수와 같은 자바 Object 클래스에 정의된 함수들을 자동으로 생성한다.
      • data class로 선언된 객체는 추가로 copy와 componentN 함수를 제공한다. copy 함수는 객체의 값을 그대로 복사한 새로운 객체를 생성할 때 사용된다. componentN 함수는 객체가 가진 프로퍼티의 개수만큼 호출할 수 있는데, 프로퍼티 이름으로 접근하는 대신에 사용된다. 예를 들어 component1 함수는 객체의 첫 번째 프로퍼티의 값을 반환한다.
    • enum class
      • enum class는 특정 상수에 이름을 붙여주는 클래스다.
    • sealed class
      • sealed class는 enum class의 확장 형태로, 클래스를 묶은 클래스이다. 제약 없이 새로운 타입을 확장 할 수 있다.
  • 2.6 패턴 매칭
    • when문에 값을 넣지 않으면 조건문에 따른 패턴을 정의할 수 있다.
  • 2.7 객체 분해
  • 2.8 컬렉션
    • 코틀린에서는 불변과 가변(muable) 자료구조를 분리해서 제공하고 있고, List, Set, Map 등의 자료 구조는 기본적으로 불변이다.
    • List와 Set은 불변 자료구조이기 때문에 add 함수가 없다. 대신 plus 함수가 제공된다. plus 함수는 원본 리스트를 변경하지 않고 새로운 리스트를 반환한다.
  • 2.9 제네릭
    • 제네릭을 사용해 클래스를 일반화하면 재사용성이 높아진다. 마찬가지로 제네릭으로 함수의 타입을 일반화하면 재사용성이 높은 함수를 만들 수 있다.
  • 2.10 코틀린 표준 라이브러리
    • 람다 리시버는 receiver의 타입 T를 block 함수의 입력인 T.()로 전달한다.
    • use는 클로즈 작업을 자동으로 해 주는 함수이다.
    • let, with, run, apply, also 함수
      • 사용 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
fun main() {
    generalExample(Person("example", 30))
    letExample(Person("example", 30))
    withExample(Person("example", 30))
    runExample(Person("example", 30))
    applyExample(Person("example", 30))
    alsoExample(Person("example", 30))
}

data class Person(var name: String, val age: Int)

fun generalExample(input: Person) {
    input.name = "generalExample"
    val result = input
    println("일반적인 객체 변경")
    println("result: $result")
    println()
}


fun letExample(input: Person) {
    val result = input.let {
        it.name = "letExample"
        it
    }
    println("let")
    println("result: $result")
    println()
}

fun withExample(input: Person) {
    val result = with(input) {
        name = "withExample"
        this
    }
    println("with")
    println("result: $result")
    println()
}

fun runExample(input: Person) {
    val result = input.run {
        name = "runExample"
        this
    }
    println("run")
    println("result: $result")
    println()
}

fun applyExample(input: Person) {
    val result = input.apply {
        name = "applyExample"
    }
    println("apply")
    println("result: $result")
    println()
}

fun alsoExample(input: Person) {
    val result = input.also {
        it.name = "alsoExample"
    }
    println("also")
    println("result: $result")
    println()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
일반적인 객체 변경
result: Person(name=generalExample, age=30)

let
result: Person(name=letExample, age=30)

with
result: Person(name=withExample, age=30)

run
result: Person(name=runExample, age=30)

apply
result: Person(name=applyExample, age=30)

also
result: Person(name=alsoExample, age=30)
1
- 비교

let

with

run

apply

also

코드 블록

람다식

람다 리시버

람다 리시버

람다 리시버

람다식

접근

it

this

this

this

it

반환값

람다식 반환값

람다식 반환값

람다식 반환값

자기 자신

자기 자신

확장 함수 여부

O

X

X

O

O

선언

1
fun <T, R> T.let(block: (T) -> R): R
1
fun <T, R> with(receiver: T, block: T.() -> R): R
1
fun <R> run(block: () -> R): R
1
fun <T> T.apply(block: T.() -> Unit): T
1
fun <T> T.also(block: (T) -> Unit): T
  • 2.11 변성

무공변

공변

반공변

전제

조건

타입 S가 T의 하위 타입(S --▷ T)

ex) Kotlin --▷ Language

상속

관계

상속 관계가 없다

Box<S> --▷ Box<T>

ex) Box<Kotlin> --▷ Box<Language>

Box<S> ◁-- Box<T>

ex) Box<Kotlin> ◁-- Box<Language>

키워드

X

out

ex) Box<out Language>

in

ex) Box<in Kotlin>

java

키워드

extends (upper bound)

ex) Box<T extends Language>

(Kotlin: Box<T : Language>)

super (low bound)

ex) Box<T super Kotlin>

(Kotlin: x)

속성

out 키워드를 사용한 공변에서는 Box 안의 값을 꺼내서 읽을 때(read)는 문제가 없지만, Box에 값을 넣으려고 할 때(write) 컴파일 오류가 발생한다.

out 키워드를 사용한 반공변에서는 Box 안의 값을 넣을 때(write)는 문제가 없지만, Box에 값을 읽으려고 할 때(read) 컴파일 오류가 발생한다.

연습 문제

2-1

1
2
3
4
5
6
fun String.helloThis() = "Hello, $this"

fun main() {
    require("kotlin".helloThis() == "Hello, kotlin")
    require("FP".helloThis() == "Hello, FP")
}
This post is licensed under CC BY 4.0 by the author.

코틀린으로 배우는 함수형 프로그래밍 - 1장 함수형 프로그래밍이란?

코틀린으로 배우는 함수형 프로그래밍 - 3장 재귀