안녕하세요 Samuel 입니다.
람다로 프로그래밍
람다 식과 멤버 참조
람다는 기본적으로 다른 함수에 넘길 수 있는 작은 코드조각을 뜻한다. 람다를 사용하면 쉽게 공통 코드 구조를 라이브러리 함수로 뽑아낼 수 있다.
함수형 프로그래밍은 함수를 값처럼 다루는 접근 방법을 택함으로써 클래스를 선언하고 그 클래스의 인스턴스를 함수에 넘기는 대신 함수형 언어에서는 함수를 직접 다른 함수에 전달할 수 있다.
람다식을 사용하면 코드가 더욱 더 간결해진다. 람다식을 사용하면 함수를 선언할 필요가 없고 코드 블록을 직접 함수의 인자로 전달할 수 있다.
람다와 컬렉션
컬렉션은 Java에서 데이터의 집합, 그룹을 의미하며 JCF( Java Collections Framework)는 이러한 데이터 , 자료구조인 컬렉션과 이를 구현하는 클레스를 정의하는 인터페이스를 제공한다.
컬렉션을 다룰때 수행하는 대부분의 작업은 몇가지 일반적인 패턴에 속한다. 따라서 그 패턴은 라이브러리 안에 있어야하고 람다가 없다면 컬렉션을 편리하게 처리할 수 있는 좋은 라이브러리를 제공하기 힘들다. ( 심지어 Java 8버전 이전에서는 개발자들이 편한 컬렉션 기능을 직접 작성하곤 했다. )
val people = listOf(Person("Samuel",30), Person("Jay",31))
println(people.maxBy{it.age})
>>> Person(name=Jag, age=31)
모든 컬렉션에 대해 maxBy 함수를 호출할 수 있다. maxBy는 가장 큰 원소를 찾기 위해 비교에 사용할 값을 돌려주는 함수를 인자로 받는다. 중괄호로 둘러싸인 { it.age }는 바로 비교에 사용할 값을 돌려주는 함수다. 이 코드는 컬렉션 원소를 인자로 받아서 ( it이 그 인자를 가리킨다. )비교에 사용할 값을 반환한다. 이 예제에서는 컬렉션의 원소가 Person 객체 였으므로 이 함수가 반환하는 값은 Person 객체의 age 필드에 저장된 나이 정보다.
이런 식으로 단지 함수나 프로퍼티를 반환하는 역할을 수행하는 람다는 멤버 참조로 대치할 수 있다.
people.maxBy(Person::age)
람다 식의 문법
{ x: Int, y:Int -> x + y }
// x: Int, y:Int 파라미터
// -> 화살표가 인자 목록과 람다 본문을 구분해준다.
// x + y 본문
// 항상 중괄호 사이에 위치함
val sum = { x: Int, y: Int -> x + y }
printlnt(sum(1,2))
>>> 3
// 변수에 저장된 람다를 호출한다.
{ println(42)} ()
>>> 42
// 원한다면 직접 람다식을 호출해도 된다.
하지만 이와 같은 구문은 읽기 어렵고 그다지 쓸모가 없다.. 굳이 람다를 만들자마자 바로 호출하느니 람다 본문을 직접 실행하는 편이 낫다. 이렇게 코드의 일부분을 블록으로 둘러싸 실행할 필요가 있다면 run을 사용한다. run은 인자로 받은 람다를 실행해주는 라이브러리 함수다.
run {println(42)}
>>> 42
실행 시점에 코틀린 람다 호출에는 아무 부가 비용이 들지 않으면서 프로그램의 기본 구성 요소와 비슷한 성능을 낸다.
람다식 문법으로 개선해보기
위의 maxBy를 사용한 코드를 람다를 사용해서 리팩터링 해보겠다.
people.maxBy({p : Person -> p.age})
여기서 어떤 일이 벌어지고 있는지 더 명확히 알 수 있다. 중괄호 안에 있는 코드는 람다 식이고 그 람다식을 maxBy함수에 넘긴다. 람다식은 Person 타임의 값을 인자로 받아서 인자의 age를 반환한다.
하지만 바로 위의 코드는
- 구분자가 너무 많이 쓰여 가독성이 떨어진다.
- 컴파일러가 문맥으로부터 유추할 수 있는 인자 타입을 굳이 적을 필요는 없다.
- 인자가 단 하나뿐인 경우 굳이 인자에 이름을 붙이지 않아도 된다.
people.maxBy() {p: Person -> p.age}
- 괄호를 람다 뒤에 둘 수 있다.
- 코틀린에는 함수 호출 시, 맨 뒤에 있는 인자가 람다식이라면 그 람다를 괄호 밖으로 빼낼 수 있는 문법 관습이 있다. 이 예제에서 람다가 유일한 인자이므로 마지막 인자이기도 하다. 따라서 괄호 뒤에 람다를 둘 수 있다.
people.maxBy{ p :Person -> p.age }
- 이 코드처럼 람다가 어떤 함수의 유일한 인자이고 괄호 뒤에 람다를 썼다면 호출시 빈 괄호를 없애도 된다.
💡 IntelliJ 사용시 꿀팁
코드를 하나의 호출 형식에서 다른 호출 형식으로 바꾸고 싶다면 “람다 식을 괄호 밖으로 이동하기(Move lambda expression out of parentheses)”메뉴와 “람다식을 괄호 안으로 이동하기(Move lambda expression into parentheses)” 메뉴를 사용하면 된다.
people.maxBy{ p:Person -> p.age } // 파라미터 타입을 명시
people.maxBy{ p -> p.age } // 파라미터 타입을 생략 (컴파일러가 추론)
- 로컬 변수처럼 컴파일러는 람다 파라미터의 타입도 추론할 수 있다. 따라서 파라미터 타입을 명시할 필요가 없다. maxBy 함수의 경우 파라미터의 타입은 항상 컬렉션 원소 타입과 같다.
- 컴파일러는 여러분이 Person 타입의 객체가 들어있는 컬렉션에 대해 maxBy를 호출한다는 사실을 알고 있으므로 람다의 파라미터도 Person이라는 사실을 이해할 수 있다.
people.maxBy{it.age} // "it"은 자동 생성된 파라미터 이름이다.
- 람다의 파라미터 이름을 디폴트 이름인 it으로 바꾸면 람다식을 더 간단하게 만들 수 있다. 람다의 파라미터가 하나뿐이고 그 타입을 컴파일러가 추론할 수 있는 경우 it을 바로 쓸 수 있다.
- 람다 파라미터 이름을 따로 지정하지 않은 경우에만 it이라는 이름이 자동으로 만들어진다.
💡 it을 사용하는 관습은 코드를 아주 간단하게 만들어준다. 하지만 이를 남용하면 안된다. 특히 람다 안에 람다가 중첩되는 경우 각 람다의 파라미터를 명시하는 편이 낫다. 파라미터를 명시하지 않으면 각각의 it이 가리키는 파라미터가 어떤 람다에 속했는지 파악하기 어려울 수 있다. 문맥에서 람다 파라미터의 의미나 파라미터의 타입을 쉽게 알수 있는 경우에도 파라미터를 명시적으로 선언하면 도움이 된다.
val getAge = {p: Person -> p.age}
people.maxBy(getAge)
- 람다를 변수에 저장할때는 파라미터의 타입을 추론할 문맥이 존재하지 않는다. 따라서 파라미터 타입을 명시해애한다.
개인적인 소감
개발을 진행하면서 람다와 같은 효율적이고 실용성이 높은 부분으로 개발할 수 있는 방법을 익히 잘 알고 있었다. 하지만 그 부분을 IntelliJ가 많은 역할을 해주면서 의존적으로 개발을 하지 않았는지 반성을 하게 됐다.
그렇기 때문에 이후에 내용에서 코틀린을 학습하는 부분을 람다로 주제를 가진 부분이 매우 기쁘게 생각하며 학습할 예정이다.
'책을 읽어봅시다 > Kotlin in Action' 카테고리의 다른 글
JAVA와 차이점 그리고 클래스, 객체, 인터페이스 (0) | 2022.03.02 |
---|---|
Basic Kotlin (0) | 2022.02.21 |
Why Kotlin (0) | 2022.02.19 |