선생님, 개발을 잘하고 싶어요.

Kotlin in Action - 1부 5장 - 람다로 프로그래밍 본문

일상/책 리뷰

Kotlin in Action - 1부 5장 - 람다로 프로그래밍

알고싶은 승민 2022. 5. 22. 12:00

다루는 거

  • 람다
  • 멤버 참조
  • 컬렉션 라이브러리
  • 코틀린 시퀀스
  • 함수형 인터페이스, SAM 인터페이스
  • 수신 객체 지정 람다 (with, apply)

행동을 변수로 저장하거나 함수 인자로 넘기기: 람다(lambda) 와 멤버 참조(member reference)

람다

코틀린은 함수를 값처럼 다루는 방식을 선택했다. 함수를 직접 다른 함수에 전달할 수 있다.

예시로 컬렉션을 다룰 때 수행하는 대부분의 작업은 몇 가지 일반적 패턴에 속하고 이 패턴은 라이브러리로 제공된다. 컬렉션으로 수행하는 일반적인 알고리즘을 구성하고 람다를 인자로 받아서 재활용할 수 있게 되었다.

람다나 멤버 참조를 인자로 받는 함수를 통해 개선한 코드는 더 짧고 더 이해하기 쉽다.

people.maxBy { it.age } // 람다로 넘기기
people.maxBy(Person::age) // 멤버 참조로 넘기기

람다는 값처럼 여기저기 전달할 수 있는 동작의 모음이다.

몇 가지 유용한 규칙이 있다.

  1. 함수 호출 시 맨 뒤에 있는 인자가 람다 식이라면 그 람다를 괄호 밖으로 빼낼 수 있다.
  2. people.maxBy() { p: Person -> p.age } // 가능
  3. 람다가 어떤 함수의 유일한 인자이고 괄호 뒤에 람다를 썼다면 호출 시 빈 괄호를 없애도 된다.
  4. people.maxBy { p: Person -> p.age } // 빈 괄호는 없애도 괜찮다.
  5. 컴파일러가 람다 파라미터 타입을 추론할 수 있는 경우는 생략해도 괜찮다.
  6. people.maxBy { p -> p.age } // people이 List<Person> 타입인 걸 컴파일러가 알고있다.
  7. 람다의 파라미터가 하나뿐이고 그 타입을 컴파일러가 추론할 수 있는 경우 it을 바로 쓸 수 있다.
  8. people.maxBy { it.age } // 인자가 하나니까 괜찮다.

람다 파라미터뿐 아니라 람다 정의 앞에 선언된 로컬 변수까지 람다에서 모두 사용할 수 있다.

자바 람다와 다르게 코틀린 람다에선 파이널 변수가 아닌 변수에 접근할 수 있다. 이는 Ref<T>라는 별도 파이널 인스턴스를 활용해 자바 람다의 규칙을 우회했기 때문에 가능하다.

람다 안에서 사용하는 외부 변수를 람다가 포획한 변수 (capture)라고 부른다.

멤버 참조

:: 를 사용하는 식을 멤버 참조라고 부른다.

멤버 참조는 그 멤버를 호출하는 람다와 같은 타입이다. 혼용가능하다는 뜻

람다 인자가 여럿인 다른 함수한테 작업을 위임하는 경우 위임 함수에 대한 참조를 제공하면 편리하다.

val action = { person: Person, message: String ->
  sendEmail(person, message)
}

val nextAction = ::sendEmail // 단순 위임의 경우 람다보다 멤버 참조가 더 편리하다.

특정 인스턴스 instance::field 와 같은 문법도 제공되곤 하는데 이는 바운드 멤버 참조(bound member reference)라고 한다. 클래스 인스턴스를 함께 저장한 다음 그 인스턴스에 대한 멤버를 호출해주는 것이다.

컬렉션 함수형 API

람다나 멤버 참조를 통해서 동작을 함수 인자로 넘길 수 있으니, 이를 활용해서 많은 라이브러리 코드가 제공된다.

filter: 조건식이 일치하는 원소만 반환

map: 주어진 람다를 각 원소에 적용한 결과 리스트 반환

all: 모든 원소가 조건을 만족하는지

any: 하나의 원소라도 조건을 만족하는지

count: 조건을 만족하는 원소가 몇 개인지

find: 조건을 만족하는 첫 원소가 무엇인지

flatMap: 인자로 주어진 람다를 컬렉션의 모든 객체에 적용(map) 후 결과를 하나의 리스트로 반환(flatten)

flatten: 결과를 하나의 리스트로 풀어 반환

지연 개산 컬렉션 연산 (feat. Sequence)

컬렉션 함수를 연쇄하면 매 단계마다 계산 중간 결과를 새로운 컬렉션에 임시로 담는다.

시퀀스를 사용하면 중간 임시 컬렉션을 사용하지 않고도 컬렉션 연산을 연쇄할 수 있다.

원소가 매우 많은 경우 컬렉션 연쇄는 새로운 리스트를 만든다는 점에서 오버해드가 크다.

시퀀스의 경우 모든 연산은 각 원소에 대해 순차적으로 적용된다.

자바 8 스트림에서 제공하는 것과 코틀린 시퀀스는 별개로 만들어졌는데 이는 코틀린이 자바 6을 지원하기 때문이다. 자바 8 스트림에서 제공하는 CPU 병렬적 실행등의 기능을 활요하고 싶다면 자바 8 스트림을 선택해야한다.

generateSequence를 통해서 시퀀스를 만들 수 있다.

자바 함수형 인터페이스

자바에서 람다가 없을 때 동작을 넘기기 위해서는 함수형 인터페이스 (SAM 인터페이스, Single Abstact Method)를 무명 클래스 인스턴스로 만들어 넘기곤 했다.

코틀린은 대신 람다를 넘길 수 있다.

자바 메서드에 람다를 인자로 전달하면, 컴파일러가 자동으로 람다를 인스턴스로 변환해준다.

인라인 되지 않은 람다 식은 무명 클래스로 컴파일된다. 그 반대로 inline 코틀린 함수에 람다를 넘기면 무명 클래스를 만들지 않는다.

대부분의 경우 람다와 자바 함수형 인터페이스 사이 변환은 자동으로 이루어진다. 그렇지 않은 경우는… 자동으로 생성된 SAM 생성자를 활용하면 된다.

Runable { ... } // Runable 이름을 가진 생성자 함수가 자동 생성되었다.

람다와 다르게 무명 객체 안에서는 this가 그 무명 객체 인스턴스 자신을 가리킨다.

수신 객체 지정 람다: with, apply

일반 함수 - 람다

확장 함수 - 수신 객체 지정 람다

 

Comments