Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
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
Tags
more
Archives
Today
Total
관리 메뉴

holy's story

[Kotlin In Action] 8장 이해하기 본문

카테고리 없음

[Kotlin In Action] 8장 이해하기

soom22 2023. 8. 20. 21:16
SMALL

고차 함수: 파라미터와 반환 값으로 람다 사용

  • 함수타입
  • 고차 함수와 코드를 구조화할 때 고차 함수를 사용하는 방법
  • 인라인 함수
  • 비로컬 return과 레이블
  • 무명함수

 

What is 고차함수?

  • 다른 함수를 인자로 받거나 함수를 반환하는 함수
  • 둘다해도 고차함수!
  • filter는 술어 함수를 인자로 받으므로 고차함수!
    • 술어 함수(predicate function)란?
      • Boolean 값을 반환하는 함수로 참이면 #t를, 거짓이면 ()를 반환
    list.filter{ x>0 }
    

함수타입

  • 어떤 걸 인자로 받거나 반환해야 고차함수가 될까?
val sum= {x:Int, y:Int -> x+y }
val action = { println } 
val sum: (Int, Int) -> Int = { x,y -> x+y }
val action: () -> Unit = { println(42) }
  • 함수의 타입을 정의하려면 (타입, 타입) → 반환타입 과 같이 작성해주면 된다.
  • Unit은 의미있는 값을 반환하지 않는 반환타입에 쓰는데, 그냥 함수를 정의할 때는 생략해도 되는데 타입을 정의할 때는 생략 불가능
  • 내부의 식으로 타입을 유추할 수 있다면 생략 가능이다. - 똑똑하넹
  • nullable 타입도 지정가능
    // 반환 타입으로 널이 될 수 있도록 할 때
    var canReturnNull: (Int, Int) -> Int? = { x,y -> null }
    // 함수 타입 전체가 널이 될 수 있단 걸 선언하기 위해
    var funOrNull: ((Int, Int) -> Int)? = null
    
  • ex)
fun twoAndThree(operation: (Int, Int) -> Int) { 
	val result = operation(2,3)
	println ("the result is $result")
}

질문.

💡 파라미터만 호출해도 되는건가,,? 아님 그냥 지정해두고 다음에 사용하는건가?

 

자바에서 코틀린 함수 타입 사용

  • 컴파일된 코드 안에서 함수타입은 일반 인터페이스로 바뀜
  • 반환타입이 unit일 경우에는 void를 넣으면 안됨!
    • unit은 값이 존재. void는 값이 없음.

디폴트 값을 지정한 함수 타입 파라미터나 널이 될 수 있는 함수 타입 파라미터

  • tostring을 계속 하는 게 번거로울때 함수타입 파라미터를 선언하면서 람다를 디폴트 값으로 지정안전호출 사용
  • transform: ( (T) -> String)? = null ... val str = transform?.invoke(element) ?:element.toString()
  • transform: (T) → String = { it.toString() } ... result.append(transform(element))

함수를 함수에서 반환

  • 인자로 받는게 더 많긴한데 함수를 반환하는 것도 유용
    • 배송에서 사용자가 선택한 배송 수단에 따라 결제 금액이 달라질 경우엔 함수를 반환하면 된다.
    • 연락처 앱에서 ui 상태에 따라 연락처 정보를 표시할 지 결정해야할 때 유용

람다를 활용한 중복제거

  • 중복코드를 함수로 추출
fun List<SiteVisit>.averageDurationFor(os: OS) =
	filter { it.os == os }.map(SiteVisit::Duration).average()
  • 복잡하게 필터 하드코딩 하기
val averageMobileDuration=log
	.filter { it.od in setOf{OS.IOS, OS.ANDROID} }
	.map (SiteVisit::duration)
	.average()

인라인 함수: 람다의 부가 비용 없애기

  • 람다의 성능은 어떨까?
    • 람다가 생성되는 시점마다 새로운 무명클래스 객체가 생김
    • 무명클래스 생성에 따른 부가비용
  • 그래서 인라인을 쓰자
    • 인라인은 함수를 호출하는 코드 대신 본문을 번역한 바이트 코드로 컴파일 된다.

인라인의 한계

  • 함수가 인라인 될 때 한수에 인자로 전달된 람다 식의 본문이 결과 코드에 직접 들어갈 수 있는데 이러면 람다가 본문에 펼쳐져서 사용하는 방식이 한정적이게 됨
    • 본문에서 파라미터로 받은 람다를 호출하는건 괜찮
    • 하지만 파라미터로 받은 람다를 다른 변수에 저장하고 그 변수를 나중에 사용하면 람다를 표현하는 객체가 어딘가는 존재해야하니깐 람다를 인라이닝할 수 없게된다 ..? →
      1. 이미 람다를 변수에다가 저장을 해버리니깐 - 동의합니다!
    • 그러면 함수 타입의 변수는 컴파일을 했을 때 객체로 나오는건가?객체에 붙여준 이름을 변수라 한다. 프로그램 실행 중에도 변수가 가리키는 객체가 바뀔 수 있다. 이름을 가리키는 객체가 바뀔 수 있어서 변수라고 한다. 또한 한 개의 객체는 여러 이름(변수)를 가질 수 있다.
      • 변수에 함수를 저장했다 → 변수는 객체다 → 객체에 함수를 저장했다
    • 변수(Variable)
  • 둘 이상의 람다를 받는데 하나만 인라인 금지 시키고 싶다면?
  • inline fun foo(inlined: () ->Unit, noinline notInlined: () ->Unit) { // ... }

컬렉션 연산 인라이닝

  • 크기가 적으면 일반 연산 하는게 좋을수도
  • 하지만 큰 경우에는 시퀀스를 통해 성능을 향상시키기 - 시퀀스를 람다를 저장해야하므로 람다를 인라인 하지 않는다

함수를 인라인으로 선언해야 하는 경우

  • inline은 람다를 인자로 받는 함수만 성능이 좋아질 가능성이 높다.
  • 일반함수 호출 - 코틀린 인라인 함수는 대치하니깐 코드 중복이 생김 - x
  • 람다를인자로 받는 함수 -o
    • 호출 비용 및 람다를 표현하는 클래스와 람다 인스턴스에 해당하는 객체를 만들 필요없어짐
    • 현재 jvm은 함수 호출과 람다를 인라이닝 해줄 정도로 똑똑하지 못함 - 언제 쓰여진 책이지? 아직도 그런가?
    • 일반 람다에서 사용할 수 없는 몇가지 기능을 사용할 수 있다.(ex) 넌로컬 반환)

자원관리를 위해 인라인된 람다 사용

  • 자원 획득, 작업마치고 해제
    • try/finally
    • synchronized
    • withLock
    • try-with-resource
    • use

고차 함수 안에서 흐름 제어

람다 안의 return문: 람다를 둘러싼 함수로부터 반환

  • 람다를 인자로 받는 함수가 인라인 함수인 경우에만 return이 바깥쪽 함수를 반환할 수 있다.

람다로부터 반환: 레이블을 사용한 return

  • 로컬 return - break와 비슷한 역할
  • return으로 실행을 끝내려는 람다식 앞에 레이블을 붙이고 return 뒤에 레이블 추가
  • fun lookForAlice(people: List<Person>} { people.forEach label@{ if(it.name == "Alice") return@label } println("Alice might be somewhere") } >>> lookForAlice(people) Alice might be somewhere
  • 이것처럼 함수 이름을 return뒤에 레이블로 붙여줄 수 있다.<aside> 💡 단, 람다식의 레이블을 명시하면 함수 이름을 레이블로 사용 불가 람다 식에는 레이블이 2개 이상 붙을 수 없음
  • </aside>
  • fun lookForAlice(people: List<Person>} { people.forEach { if(it.name == "Alice") return@forEach } println("Alice might be somewhere") }

무명함수: 기본적으로 로컬 return

  • 무명함수 안에서 return 사용하기
  • fun lookForAlice(people: List<Person>} { people.forEach ( fun (person) { if(it.name == "Alice") return println("${person.name} is not Alice") }) } >>> lookForAlice(people) Bob is not Alice
  • filter에 무명함수 넘기기
  • peopel.filter(fun (person) : Boolean { return person.age <30 })
  • 식이 본문인 무명 함수 사용하기
  • peopel.filter(fun (person) = person.age <30)
  • 무명함수는 일반함수와 비슷해 보이지만 실제로는 람다식에 대한 문법적 편의.
  • 그래서 람다식에 대한 인라인 규칙도 적용가능!