Kotlin 의 람다 Lambda
- Part.1 람다 식
- Part.1 멤버 참조
- Part.1 함수형 스타일
- Part.2 시퀀스
Sequence
: 지연 컬렉션 연산 - Part.2 자바 함수형 인터페이스 코틀린에서 사용
- Part.2 수신 객체 지정 람다 사용
람다 식 Lambda expression
람다 식 또는 람다 는 다른 함수에 넘길 수 있는 작은 코드 조각을 뜻한다. 프로그래밍에서 람다를 사용하면 좀 더 쉬운 방법으로 공통 코드 구조를 라이브러리 함수로 구현할 수 있다. 코틀린에서도 컬렉션 라이브러리 등에서 람다를 아주 많이 사용하고 있다고 한다. 그리고 람다를 고려하지 않는 라이브러리도 람다를 활용하게 변경할 수 있는 점이 있다.
개인적으론, 함수형 프로그래밍이란 코딩 스타일에는 이 람다 표현식이 제일 중요하고 큰 역할을 하고 있다고 생각한다. 그러기때문에 람다를 이해하는 것은 매우 중요한 부분인 것 같다.
람다 식
람다 의 궁극적인 목적은 다음과 같을 것 같다.
1. 코드 블록(함수)을 함수의 인자로 넘기기
2. 함수를 값처럼 다루는 접근 방법
자바에서는 1번 목적을 달성하기 위해 무명 내부 클래스를 활용하였다. 하지만 자바의 무명 내부 클래스의 단점이 있다면, 복잡성과 가독성 저하되는 점이 있다. 그래서 자바 8에서도 람다를 지원하면서 이런 문제를 보완할 수 있었다.
Java 의 무명 내부 클래스 예제
위 예제에서 보이듯이 람다로 구현하는 코드가 훨씬 간결하고 이해가 쉽다. 람다를 이해해야하는 이유에 대해서는 이제 충분히 인지가 된다.
람다 식의 문법
람다 식의 기본 문법은 아래와 같다.
람다 식의 특징
1. 인자 목록과 람다 본문을 화살표 ->
로 구분한다.
2. 람다 식을 변수에 저장할 수 있다.
3. 다양한 형식의 람다 식 표현이 가능하다.
4. 여러 줄로 이뤄진 람다의 본문인 경우, 마지막에 있는 식이 람다의 결과 값이 된다.
현재 영역에 있는 로컬 변수 접근
자바의 무명 내부 클래스의 역할과 동일하게 코틀린의 람다에서도 함수에 정의된 로컬 변수를 접근하고 활용할 수 있다.
위 예제처럼 람다 식안에서 prefix
라는 바깥 함수의 변수를 접근할 수 있다.
하지만 자바와 다른 중요한 점은 final
변수가 아닌 변수에 접근할 수 있다는 특징이다. 그리고 람다 안에서 바깥 변수를 변경할 수도 있다.
이런 람다 안에서 사용하는 외부 변수를 람다가 포획capture
한 변수 라고 한다.
포획한 변수의 특징
- 어떤 함수가 자신의 로컬 변수를 포획한 람다를 반환하거나 다른 변수에 저장한다면, 해당 로컬 변수의 생명 주기와 함수의 생명 주기가 달라질 수 있다.
- 이는 포획한 변수가 있는 람다를 저장해서 함수가 끝난 뒤에 실행하더라도 람다의 본문 코드에 있는 포획한 로컬 변수를 그대로 접근하고 사용할 수 있는 점이다.
람다가 포획한 변수는 람다 코드를 변수 값과 함께 저장한다.
final
이 아닌 변수를 포획한 경우에는 변수를 특별한 래퍼wrapper
로 감싸서 나중에 변경하거나 읽을 수 있게 하고, 래퍼에 대한 참조를 람다 코드와 함께 저장한다. 그러기 때문에 함수가 끝난 뒤에서 람다는 포획한 변수에 대한 접근이 가능하다.
포획한 변수의 함정
아래의 코드에서는 버튼 클릭 횟수 변경을 제대로 감지할 수 없다.
람다를 이벤트 핸들러나 다른 비동기적으로 실행되는 코드를 활용하는 경우 함수 호출이 끝난 다음에 로컬 변수가 변경되기 때문에, 위 코드에서의 clicks
변수에 대한 변경을 관찰하지 못하고 항상 0
을 반환하게 된다.
해결책으로는 onClick
핸들러에서 포획한 clicks
와 같은 변수를 함수의 내부가 아닌 클래스의 프로퍼티 또는 전역 프로퍼티 등의 위치로 빼내어 활용해야 한다.
멤버 참조
람다를 사용해 코드 블록을 다른 함수에게 인자로 넘길 수가 있는데, 매번 함수 호출하는 코드에 코드 블록을 넣을 수는 없을 것이다. 이런 코딩을 막기 위해 멤버 참조 Member Reference
는 코틀린에서 중복 코드를 좀 더 줄여줄 수 있는 역할을 해준다.
멤버 참조는 프로터피나 메소드를 단 하나만 호출하는 함수 값을 만들어준다.
이중 클론 ::
을 사용하여 멤버 참조 식을 만들 수 있다. 멤버 참조를 활용하면 아래와 같은 코드를 작성할 수 있다.
최상위 함수나 프로퍼티 참조
멤버 참조는 최상위에 선언된 함수나 프로퍼티를 참조할 수 있다.
작업 위임 함수에 대한 참조 제공
람다가 인자가 여럿인 다른 함수로 작업을 위임하는 경우도 있다. 이럴 경우 람다를 정의하지 않고 직접 위임 함수에 대한 참조를 제공할 수 있다.
생성자 참조 제공
멤버 참조를 사용하면 클래스 생성 작업을 연기하거나 저장해둘 수도 있다.
확장 함수의 멤버 참조 제공
바운드 멤버 참조
코틀린 1.1
버전 이후 부터 바운드 멤버 참조를 지원하는데, 바운드 멤버 참조를 사용하면 멤버 참조를 생성할 때 클래스 인스턴스를 함께 저장한 다음 나중에 그 인스턴스에 대한 멤버를 호출할 수 있다. 따라서 호출 시 수신 대상 객체를 별도로 지정해 줄 필요가 없게 된다.
컬렉션 함수형 API
코틀린에는 함수형 프로그래밍을 위해 자바의 컬렉션의 API를 다양하게 제공하고 있다. 라이브러리 함수를 잘 이용하면 코드를 간결하게 만들 수 있다. (실무에서도 여러 줄의 코드가 있는 함수를 하나의 식으로 만드는 경험을 할 수 있다.)
함수형 프로그래밍 에 대한 글
Java Skill UP 을 하자! step.1 - Functional Programmings
많이 사용하는 함수형 API
(코틀린에서 제공하는 함수형 API 함수는 아래에 있는 것보다 훨씬 더 많다.)
filter
: 컬렉션을 이터레이션하면서 주어진 람다에 각 원소를 람다에서 조건 검사하고, true
를 반환하는 원소들로 새로운 컬렉션을 만드는 함수
map
: 컬렉션의 각 원소를 주어진 람다로 적용한 결과를 모아서 새로운 컬렉션을 만드는 함수
all
: 컬렉션의 모든 원소가 람다 조건에 만족하는지 확인하는 함수
any
: 컬렉션의 원소 중 어느 하나라도 람다 조건에 만족하는 원소가 있는지 확인하는 함수
count
: 컬렉션의 원소 중 람다 조건에 만족하는 원소가 몇 개 존재하는지 확인하는 함수
count
함수의 장점
위 코드처럼 똑같은 3
의 배수 숫자 개수를 찾는 코드는 filter
와 size
를 활용해서도 가능하다.
하지만, 위와 같은 코드는 filter
이후, 중간 컬렉션이 생기게 된다. 크기가 크지 않은 컬렉션인 경우 큰 문제는 없겠지만, 크기가 큰 컬렉션인 경우에는 중간 컬렉션이 생겨 메모리 이슈 등 다른 에러 상황이 생긴다.
반면 count
함수는 원소의 개수만을 추적할 뿐, 조건을 만족하는 원소를 따로 저장하지 않기 때문에, 위와 같은 로직에서는 count
함수가 효율적이라 할 수 있다.
find
: 컬렉션의 원소 중에 람다 조건에 만족하는 원소 하나를 찾는 함수
find
함수의 주의 사항 : 만족하는 원소가 하나라도 있는 경우, 가장 먼저 조건에 만족하는 원소를 제일 먼저 반환한다. 없는 경우엔null
을 반환한다.
groupBy
: 리스트 컬렉션을 여러 그룹으로 이뤄진 맵으로 변경할 수 있는 함수
flatMap
: 리스트 컬렉션 원소에 람다 조건을 적용하고, 적용한 결과 얻어지는 여러 리스트를 하나의 리스트로 다시 만드는 함수
flatten
: 여러 개의 리스트로 구성되어 있는 리스트 컬렉션의 원소를 펼쳐서 새로운 컬렉션으로 다시 만드는 함수
코틀린의 람다 Lambda
의 Part.1 은 여기서 마무리하고, Part.2 에서 계속 이어가도록 하겠다.