코틀린 언어 정리 3-2

함수형 프로그래밍

Scope Functions( 범위 함수들 )

개요

범위 함수에는 apply, also, run, let, with 함수들이 있습니다.

이 함수들을 기능적으로 보면, 주로 문맥 객체(context object: T)와 관련된 코드로 구성된 람다함수를 입력 받아 인라인화 하고 다음 연산에 사용할 객체(R 또는 T)를 반환합니다. 이런 점에서 범위 함수들은 모두 비슷한 함수로 보일 수 있습니다. 조금 극단적으로 이야기 하면 5개의 각각의 함수가 사용된 곳은 자신 이외에 나머지 4개의 다른 함수로 대체 가능합니다. 단지 각 함수의 차이점은 함수 이름, 함수가 문맥 객체(T)를 전달 받는 방법, 함수가 리턴하는 객체(R)입니다. 단지 동작하는 코드에만 관심이 있다면 이 함수들은 사용할 필요가 없습니다.

 

하지만 이 함수들은 시각적으로는 꽤 의미가 있습니다. 함수 이름으로 문맥을 그룹화 할 수 있고, 문맥 객체를 전달하는 방법, 리턴하는 객체 등을 통해 코드 중복을 제거하고 코드를 더 읽기 쉽게 만들 수 있습니다.

이 함수들(범위 함수들)은 return문 사용이 가능한 람다 함수(non-local return)를 입력 받습니다. return문 사용이 가능한 람다 함수에 문맥을 구현하고 이를 범위 함수에 전달합니다. 이 때 return문은 인라인 고차함수(범위 함수)를 호출하는 함수의 return문으로 처리 되므로 시각적(직관적)으로도 return문을 활용하기가 편합니다. 람다함수를 이용한 흐름 제어가 더 유연해집니다.

 

정리하면, 범위 함수들은 관용구처럼 사용 되는 인라인 함수들이며 각각의 함수가 특별한 기능을 수행 하지는 않습니다. 이 함수들을 사용하지 않아도 동작하는 코드를 만드는 데에는 지장이 없습니다. 하지만 문맥에 따라 적절하게 선택하여 사용한다면 코드 양이 줄어들고 시각적으로 문맥을 읽기가 쉬워집니다.

 

목적

문맥 객체의 멤버 연산 호출 또는 기타 절차 위주의 코드들, 특히 재사용 하지 않는 호출 절차들을 일반적인 언어(동사)로 명명된 함수로 블록화 하여 문맥을 읽기 쉽게 만듭니다.

 

효과

  • 범위함수에 입력되는 각 유형의 람다함수(T.()->Unit, (T)->Unit, T.()->R, (T)->R 등) 구현에서 T(context object, 문맥 객체, receiver)에 대한 접근을 쉽게 합니다.
    • 입력되는 람다함수가 T.() 형식인 경우 람다함수가 T의 확장 객체로 적용되면서 람다 함수 내에서 T를 this로 접근할 수 있습니다. 따라서 T의 멤버함수 호출 시 "T."을 명시할 필요가 없습니다.
      T의 private멤버에는 직접 접근할 수 없습니다.(범위함수는 T의 멤버가 아니라 확장함수임)
    • 입력되는 람다함수가 (T) 형식인 경우(람다 함수의 매개변수가 1개일 경우) 매개변수 이름을 생략한 후 "it"로 사용할 수 있습니다.
  • 코드 문맥을 설명할 수 있는 일반적인 함수 이름(apply, also, run, let, with)으로 코드를 블록화 할 수 있습니다.
  • 다음 연산에 필요한 객체(R)를 지정할 수 있습니다. 이를 통해 문맥을 이어가기 쉽습니다.
  • 코드가 간결해지고 읽기 쉬워집니다.

 

제공되는 함수들

run

선언 1

inline fun <R> run ( block: ()->R ) : R

  • 입력된 함수("()->R")를 실행하고 결과("R")를 리턴합니다.

 

선언 2

inline fun <T, R> T.run ( block: T.()->R ) : R

  • 입력된 함수("T.()->R")를 실행하고 결과("R")을 리턴합니다.
  • 입력된 함수(block 람다 함수)가 T객체의 확장함수로 처리 되므로 람다 함수 구현 시 T객체를 this로 취급하여 구현할 수 있습니다. 따라서 코드 양이 줄어듭니다.

 

apply

선언

inline fun <T> T.apply ( block: T.()->Unit ) : T

  • 입력된 함수("T.()->Unit")를 실행하고 T 객체를 리턴합니다.

 

also

선언

inline fun <T> T.also ( block: (T)->Unit ): T

  • 입력된 함수("(T)->Unit")를 실행하고 T 객체를 리턴합니다.

 

let

선언

inline fun <T, R> T.let ( block: (T)->R ) : R

  • T를 매개변수로 전달받는 함수를 실행하고 결과("R")을 리턴합니다. run과 다르게, 입력되는 함수("(T)->R")에서 T객체를 사용하는 경우 "T."을 모두 명시해 주어야 합니다.

 

with

선언

inline fun <T, R> with ( receiver:T, block:T.()->R ) : R

  • receiver(T객체)를 명시적으로 입력 받고 입력된 함수("T.()->R")를 실행합니다.



범위 함수들 간의 차이점과 의미

 

  • 함수 이름
    • 각각의 함수 이름은 문맥을 표현하는 접두어로 사용됩니다.
  • 문맥 객체(context object: T)를 전달 받는 방법
    • 함수에 따라 반복적인 문맥 객체 접근을 생략하거나 문맥 객체의 이름을 새로운 문맥에 맞게 변경(매개변수)하여 접근할 수 있습니다.
  • 리턴하는 객체(R)
    • 현재 범위 함수 종료 후 이어서 사용할 문맥 객체를 결정할 수 있습니다.

 

+ Recent posts