본문 바로가기
공부/Kotlin

[Kotlin] 파라미터와 반환 값으로 람다 사용해보기

by JERO__ 2022. 7. 2.

1. 고차함수

고차함수 : 다른 함수를 인자로 받거나, 함수를 받환하는 함수.

즉, 코틀린에서는 람다나 함수 참조를 인자로 넘길 수 있거나 람다나 함수 참조를 반환하는 함수이다.

1. 함수 타입

  • (Int, String) → Unit (파라미터타입 → 반환타입)
  • () → String? null을 반환할 수 있다.
  • (() → Int)? 함수 타입이 null이 될 수 있다.
    • invoke(이름 없이 간편하게 호출될 수 있는 함수)로 호출한다.

2. 인자로 받은 함수 호출

  • 예제1

twoAndThree { a, b -> a + b }  // The result is 5
twoAndThree { a, b -> a * b }  // The result is 6
  • 예제2
String.filter(predicate: (Char) -> Boolean): String {
	val sb = StringBuilder()
	for (index in 0 until length) {
		val element = get(index)
		if (predicate(element)) {      // Boolean
			sb.append(element)
		}
	}
	return sb.toString()
}

println("ab1c".filter { it in 'a'..'z' } )      // abc

3. 널이 될 수 있는 함수 타입 파라미터

fun <T> Collection<T>.joinToString(
	separator: String = ", ",
	prefix: String = "",
	postfix: String = "",
	transform: ((T) -> String)? = null
) : String {
	val result = StringBuilder(prefix)
	for((index, element) in this.withIndex()) {
		if (index > 0) result.append(separator)
		val str = transform?.invoke(element) ?: element.toString()
		result.append(str)
	}
	result.append(postfix)
	return result.toString()
}

4. 람다를 활용한 중복 제거

enum class OS { WINDOWS, LINUX, MAC, IOS, ANDROID }

fun List<SiteVisit>.averageDurationFor(predicate: (SiteVisit) -> Boolean) =
	filter(predicate)  // 람다로 입력 filter
	.map(SiteVisit::duration)    // 사용시간
	.average()  // 평균

//  사용
val log = listOf(
	SiteVisit("/", 34.0, OS.WINDOWS),
	SiteVisit("/", 22.0, OS.MAC),
	SiteVisit("/login", 12.0, OS.WINDOWS),
	SiteVisit("/signup", 8.0, OS.IOS),
	SiteVisit("/", 16.3, OS.ANDROID),
)
println(log.averageDurationFor { it.os in setOf(OS.ANDROID, OS.IOS) } )

2. inline 함수 : 람다의 부가 비용 없애기

  • 람다식을 사용할 때마다 새로운 클래스가 만들어지지 않는다.
  • 람다가 변수를 포획하면 람다가 생성되는 시점마다 새로운 무명 클래스 객체가 생긴다.
    • 부가비용 발생 → 덜 효율적
    → inline 변경자를 함수에 붙이면 컴파일러는 그 함수를 호출하는 모든 문장을 함수 본문에 해당하는 바이트코드로 바꿔치기 해준다.

1. 작동 방식

inline 선언시 함수 본문이 인라인된다. → 바이트코드로 컴파일한다.

  • 바이트코드로 컴파일 한다는 것은, 람다를 호출하는 코드 정의의 일부분으로 간주되기 때문에 무명 클래스로 감싸지 않는다.

2. 한계

  • 파라미터로 받은 람다를 다른 변수에 저장하고, 나중에 그 변수를 사용한다면 람다를 inline할 수 없다. (람다를 표현하는 객체가 어딘가 존재해야 하기 때문)
  • 둘 이상의 람다를 인자를 받고, 일부 람다만 인라이닝 하고 싶은 경우 → noinline
  • inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {}

3. 컬렉션의 filter, map

  • 컬렉션 연산 함수는 inline 함수이다. 따라서, 바이트코드는 거의 같다(for문으로 한 경우와).
  • 하지만, 중간리스트를 생성한다.
    • 처리할 경우가 적은 경우 → 컬렉션연산
    • 처리할 경우가 많은 경우 → asSequence, 이는 inline 되지 않는다.

4. inline 함수, 언제사용할까

람다를 인자로 받는 경우

  • 주의점은?
    • inline 함수가 큰 경우 함수 본문의 바이트코드가 전체적으로 커질 수 있다.

3. 람다 return

1. non-local return return

fun lookForAlice(people: List<Person>){
	people.forEach {
		if (it.name == "Alice") {
			println("Found!")
			return                   // non-local
		}
	}
	println("Alice is not Found")
}
  • 수행과정
    1. 함수실행을 끝낸다.
    2. 반환한다.
  • non-local
    • 자신을 둘러싸고 있는 블록보다 더 바깥에 있는 다른 블록을 반환
    • 람다를 인자로 받는 ****함수가 인라인 함수인 경우에만 가능(forEach 등)

2. 로컬 return return@----

for의 break와 비슷한 역할

  • 수행과정
    1. 함수실행을 끝낸다.
    2. 코드 실행을 이어간다.
fun lookForAlice(people: List<Person>) {
	people.forEach label@{
		if (it.name == "Alice") {
			return@label // 인라인함수 이름 사용가능 return@forEach
		}
	}
	println("항상실행구문")
}

3. 무명 함수 return

  • 함수의 이름이나 파라미터 타입을 생략할 수 있다.
  • return은 가장 가까운 함수를 가리킨다.
  • 람다 식에 대한 문법적 편의일 뿐이다.

 

kotlin in action 책을 통한 공부입니다.

 

댓글