본문 바로가기
코틀린

코틀린(14) - 람다함수를 이용한 스코프 함수

by 왈레 2022. 3. 21.

이번 강의에서는 람다함수를 이용한 특별한 기능인 스코프 함수 알아보겠습니다.

스코프함수를 배우기전에 설명에 도움이 되도록 람다함수의 특별한 케이스들을 몇가지 소개해 드리겠습니다.

 

1. 람다함수도 여러 구문의 사용이 가능

val c: (String)-> Unit = {str ->
    println("$str 람다함수는")
    println("여러 구문")
    println("사용 가능합니다.")
}

val calculate: (Int, Int) -> Int = {a,b ->
    println(a)
    println(b)
	a+b
}

참고로 람다함수가 여러줄이 되는 경우 마지막 구문의 결과값이 반환된다.

 

2. 람다함수에 파라미터가 없다면 실행할 구문들만 나열하면 된다.

val a: ( ) -> Unit = { println("패러미터가 없어요!")}

 

3. 패러미터가 하나뿐이라면 it 사용

val c: (String) -> Unit = { println ("$it 람다함수")}

 

스코프 함수

스코프 함수는 함수형 언어의 특징을 편리하게 사용할 있도록 기본 제공하는 함수들이다.

클래스에서 생성한 인스턴스를 스코프 함수에 전달하면 인스턴스의 속성이나 함수를 스코프 함수내에서 편하게 사용할 있도록 하는 기능이다. (인스턴스의 속성이나 함수를 깔끔하게 불러 있다)

 

스코프 함수에는 apply, run, with, also, let 다섯가지가 있다

 

apply

apply는 인스턴스를 생성한 변수에 담기 전에 '초기화 과정' 수행할 많이 쓰인다.

책이름 앞에 [초특가]라는 문자열을 넣어주고 discount함수를 실행한다고 가정해보자.

fun main(){
    var a = Book("이상한 나라의 앨리스", 10000).apply{
        name = "[초특가]" + name
        discount()
    }
}

class Book(var name: String, var price: Int){
    fun discount(){
        price -= 2000
    }
}

기존의 경우 인스턴스를 저장한 변수를 통해 참조연산자를 사용하여 속성과 함수를 사용했겠지만, apply 이용하면 인스턴스를 생성하자마자 인스턴스에 참조연산자를 사용하여 apply 붙이고 중괄호로 람다 함수를 하나 만들어 apply scope 안에서 직접 인스턴스의 속성과 함수를 참조연산자 없이 사용이 가능하다.

또한 apply 인스턴스 자신을 다시 반환하므로 이렇게 생성되자마자 조작된 인스턴스를 변수에 바로 넣어줄 있다. 이렇게 apply 같은 스코프 함수를 사용하면 main함수와 '별도의 scope'에서 인스턴스의 변수와 함수를 조작하므로 코드가 깔끔해진다는 장점이 있다.

 

run

apply처럼 run 스코프 안에서 참조연산자를 사용하지 않아도 된다는 점은 같지만 일반 람다함수처럼 인스턴스 대신 마지막 구문에 결과값을 반환한다는 차이점이 있다.

따라서 이미 인스턴스가 만들어진 후에 인스턴스의 함수나 속성을 scope에서 사용해야 유용하다.(익명 함수처럼 사용하는법도 있는데 익명 함수처럼 사용할 때는 블록을 반환하고 블록안에 선언된 변수는 모두 임시로 사용되는 변수로 복잡한 계산에 임시변수가 많이 필요할 유용하다, 안전한 호출을 사용할수있어서 with함수보다 유용함)

 

run 반환값의 특징!

var b = a.run { // a는 book클래스의 객체가 담겨있다고 가정
    println(a.price)
    a.name // 변수 b에는 run의 반환값인 a.name이 저장된다!
}

가격은 출력하지만 마지막 구문인 이름은 반환하여 b라는 수에 할당됨

 

run 사용법

fun main(){
    var a = Book("이상한 나라의 앨리스", 10000).apply{
        name = "[초특가]" + name
        discount()
    }

    a.run {
        println("상품명 :${name}, 가격 : ${price}원 ")
    }
}

 

with

run 동일한 기능을 가지지만 단지 인스턴스를 참조연산자 대신 파러미터로 받는다는 차이뿐이다. (안전한 호출이 불가능하다.)

a.run{...} with(a) {…}

 

also / let

also 와 let은 각각 apply(also) run(let) 같은 기능을 가지고 있다.

다만 한가지 공통적인 차이점이 있는데, apply run 참조연산자 없이 인스턴스의 변수와 함수를 사용할 있었다면 also let 마치 패러미터로 인스턴스를 넘긴것처럼 it 통해서 인스턴스를 사용할 있다는 것이다.

 

그렇다면 이 두 함수는 왜 굳이 패러미터를 통해서 인스턴스를 사용하는 귀찮은 과정을 거칠까?

이는 같은 이름의 변수나 함수가 'scope 바깥에 중복'되어 있는 경우에 혼란을 방지하기 위해서다.

main 함수 내에  Book 클래스의 속성이름과 같은 price 변수를 하나 만들고, 1000이라는 값을 할당해보자.

상태로 a.run{ } 중괄호내에서 price 접근하면 Book 클래스의 속성 price 아닌 main함수의 price변수에 접근한다.

따라서 잘못된 출력을 하게된다.

이는 run함수가 인스턴스 내의 price 속성보다 run 속해있는 main함수의 price 변수를 우선시하고 있기 때문이다.

이럴경우 run 대체하는 let 사용하고 name 대신 it.name price 대신 it.price 사용하면 인스턴스의 값이 정상적으로 출력될것이다.

apply역시 같은 경우가 있다면 also 대체하여 사용하면 된다.

 

let 필요성

fun main(){
    var price = 1000
    var a = Book("이상한 나라의 앨리스", 10000).apply{
        name = "[초특가]" + name
        discount()
    }
    
    a.run{
        println("상품명 :${name}, 가격 : ${price}원 ") // 메인에서 할당된 price 1000이 출력된다!
    }
}

class Book(var name: String, var price: Int){
    fun discount(){
        price -= 2000
    }
}

//출력 값 : 상품명 :[초특가]이상한 나라의 앨리스, 가격 : 1000원

 

let 사용법

 a.run{
    println("상품명: ${name}, 가격: ${price}원 ") // main에 name과 price가 있으면 그것들을 출력!
}

a.let{
    println("상품명: ${it.name}, 가격: ${it.price}") // a의 인스턴스 속성값을 출력!
}

 

스코프 함수는 인스턴스의 속성이나 함수를 scope 내에서 깔끔하게 분리하여 사용할 있다는 때문에 코드의 가독성을 향상시킨다는 장점이 있다는 것을 기억!

댓글