초보 개발자의 성장기

[부스트코스] [부스트코스][코틀린 프로그래밍 기본 1/2] 한아아지와 함께하는 kotlin - 표준 함수 (1) Closer와 Let 그리고 Also 본문

IT 강의 리뷰/코틀린

[부스트코스] [부스트코스][코틀린 프로그래밍 기본 1/2] 한아아지와 함께하는 kotlin - 표준 함수 (1) Closer와 Let 그리고 Also

개발자 김케빈 2021. 1. 29. 10:19

이번 시간에는 다양한 표준 함수 중

Close와 Let 그리고 Also에 대해서 배워보겠습니다!


www.boostcourse.org/mo132/lecture/59989

 

코틀린 프로그래밍 기본 1

부스트코스 무료 강의

www.boostcourse.org

www.boostcourse.org/mo132/lecture/59990

 

코틀린 프로그래밍 기본 1

부스트코스 무료 강의

www.boostcourse.org

www.boostcourse.org/mo132/lecture/59991

 

코틀린 프로그래밍 기본 1

부스트코스 무료 강의

www.boostcourse.org

www.boostcourse.org/mo132/lecture/59992

 

코틀린 프로그래밍 기본 1

부스트코스 무료 강의

www.boostcourse.org


1. 람다식과 고차함수 요약

1.1. 람다식

람다식은 항상 중괄호로 묶여 있으며 중괄호 내에 매개변수는 화살표 표기법 왼쪽에 배치되고 오른쪽에는 그에 따른 식을 구성합니다.
만약 매개변수가 한 개인 경우, 매개변수를 생략하고 it으로 표기할 수 있었습니다.

EX 1) val sum: (Int, Int) -> Int = { x, y -> x + y }

EX 2) val mul = { x: Int, y: Int -> x * y }

EX 3) val add: (Int) -> Int = { it + 1 }

 

1.2. 고차함수

고차함수는 함수의 매개변수로 함수를 받거나 함수 자체를 반환할 수 있는 함수입니다.


fun inc(x: Int): Int {
    return x + 1
}

fun high(name: String, body: (Int)->Int): Int {
    println("name: $name")
    val x = 0
    return body(x)
}

1.3. 람다식의 다양한 예시


// 여러 개의 식이 있는 경우
val isPositive: (Int) -> Boolean = {
	val isPositive = it > 0
    isPositive // 마지막 표현식이 반환됨
}

val isPositiveLabel: (Int) -> Boolean = number@ {
	val isPositive = it > 0
    return@number isPositive // 라벨을 사용해 반환됨
}


// 함수를 이용한 람다식
val result = high("Sean", {  x -> inc(x + 3) })


// 소괄호 바깥으로 빼내고 생략
val result2 = high("Sean") { inc(it + 3) }


// 매개변수 없이 함수의 이름만 사용할 때
val result3 = high("Kim", ::inc)


// 람다식 자체를 넘겨 준 형태
val result4 = high("Sean") { x -> x + 3 }


// 매개변수가 한 개인 경우 생략
val result5 = high("Sean") { it + 3 }


2. Closer

  • 람다식 함수를 이용하다 보면 내부 함수에서 외부 변수를 사용하고 싶을 때가 있습니다.
    클로저란 람다식으로 표현된 내부 함수에서 외부 범위에 선언된 변수에 접근할 수 있는 개념입니다. 이 때 람다식 안에 있는 외부 변수는 값을 유지하기 위해 람다가 포획(capture)한 변수라고 부릅니다.
  • 실행 시점에서 람다식의 모든 참조가 포함된 닫힌(closed) 객체를 람다 코드와 함께 저장합니다. 이때 이러한 데이터 구조를 클로저(closure)라고 부르는 것입니다.
  • 기본적으로 함수 안에 정의된 변수는 로컬 변수로 스택에 저장되어 있다가 함수가 끝나면 같이 사라지게 됩니다. 하지만 클로저 개념에서는 포획한 변수는 참조가 유지되어 종료되어도 사라지지 않고 접근하거나 수정가능합니다.

Closer 기본 예시


fun main() {
	
    val Calc = Calc()
    
    var result = 0     // 외부 변수
    // result는 람다식 내부에서 재할당 되어 사용되는데 이때 할당된 값은 유지되 출력문에서 사용
    
    calc.addNum(2, 3) { x, y -> result = x + y } // 클로저
    
    println(result)    // 값을 유지하여 5가 출력
}


class Calc {
	fun addNum(a: Int, b: Int, add: (Int, Int) -> Unit) {
    	add(a, b) // 람다식 add에는 반환 값이 없음
    }
}

※ 함수에서는 다음과 같이 매개 변수를 이용할 수도 있습니다.

// 길이가 일치하는 이름만 반환
fun filteredNames(length: Int) {
    val names = arrayListOf("Kim", "Hong", "Go", "Hwang", "Jeon")
    val filterResult = names.filter {
        it.length == length // 바깥의 length에 접근 
    }
    println(filterResult)
}
...
filteredNames(4)


3. Let()

let()함수는 함수를 호출하는 객체 T를 이어지는 block의 인자로 넘기고 block의 결과값 R을 반환합니다.

 

*표준 함수의 정의 *

public inline fun <T, R> T.let(block: (T) -> R): R { ... return block(this) }

 

 - 매개변수 block은 T를 매개변수로 받아 R을 반환

 - let() 함수 역시 R을 반환

 - 본문의 this는 객체 T를 가리키는데 람다식 결과 부분을 그대로 반환

 - 다른 메서드를 실행하거나 연산을 수행해야 하는 경우 사용

 

T나 R과 같은 문자는 제네릭에서 배우게 될 내용이지만 간단히 설명하면 let( ) 확장 함수를 사용하기 위해 어떤 자료형이더라도 사용할 수 있도록 일반화한 문자입니다. 형식 매개변수라고 합니다.

예를 들면 정수형, 문자열, 특정 클래스의 객체등에 let()함수를 확장 함수로서 사용할 수 있게 된다는 것이죠.

 

3.1. let 기본 예시


fun main() {
	
    val score: Int? = 32
    // val score = null
    
    // 일반적인 null 검사
    fun checkScore() {
    	if (score != null) {
        	println("Score: $Score")
        }
    }
    
    // let을 사용해 null 검사를 제거
    fun checkScoreLet() {
    	score?.let { println("Score: $it") }  // (1)
        val str = score.let { it.toString() } // (2)
        println(str)
    }
    
    checkScore()
    checkScoreLet()
}


// let 함수의 체이닝(chaining)
fun main() {
	
    val a = 1
    val b = 2
    
    a = a.let { it + 2 }.let {
    	println("a = $a")
        val i = it + b
        i
    }
    println(a)  // 5
}

3.2. let 중첩 사용


// 중첩 사용
var x = "Kotlin!"
	x.let { outer ->
    	outer.let { inner -> 
        	print("Inner is $inner and outer is $outer")
            // 이 때는 it을 사용하지 않고 명시적 이름을 사용
        }
    }


// 반환 값은 바깥 쪽의 람다식에만 적용
var x = "Kotlin!"
	x = x.let{ outer ->
    	outer.let{ inner ->
        	print("Inner is $Inner and outer is $outer")
            "Inner String" // 이것은 반환되지 않음
        }
        "Outer String"     // 이 문자열이 반환되어 x에 할당됨
    }

3.3. null 검사

let을 세이프 콜(?.) 과 함께 사용하면 if (null != obj) 와 같은 null 검사 부분을 대체


// (기존 방식)
var obj: String?   // null일 수 있는 변수 obj
...
if (null != obj) { // obj가 null이 아닐 경우 작업 수행 
	Toast.makeText(applicationContext, obj, Toast.LENGTH_LONG).show()
}


// (Safe calls + let 사용)
obj?.let { // obj가 null이 아닐 경우 작업 수행 
	Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show()


4. also()

also()는 함수를 호출하는 객체 T를 이어지는 block에 전달하고 객체 T 자체를 반환합니다.

 

*표준 함수의 정의

public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }

 

also는 let과 역할이 거의 동일해 보입니다. 하지만 자세히 보면 반환하는 값이 다른데,

let은 마지막 수행된 코드 블록의 결과를 반환하고 also는 블록 안의 코드 수행 결과와 상관없이 T인 객체 this를 반환하게 됩니다.

※ also와 let 비교


public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
public inline fin <T> T.also(block: (T) -> Unit): T { block(this); return this }

// also는 블록 안의 코드 수행 결과와 상관없이 T인 객체 this를 반환
var m = 1
m = m.also { it + 3 }
println(m)  // 원본 값 = 1


// 예시
fun main() {
	data class Person(var name: String, var skills: String)
    var person = Person("Kildong", "Kotlin")
    
    val a = person.let {
    	it.skills = "Android"
        "success"    // 마지막 문장을 결과로 반환
    }
    
    println(person)
    println("a: $a") // String
    
    val b = person.also {
    	it.skills = "Java"
        "success"    // 마지막 문장은 사용되지 않음
    }
    
    println(person)
    println("b: $b") // Person의 객체 b

 

Comments