코딩의 간결성과 직관적 가독성을 위해 프로그램 작성법이 조금씩 변천되어 왔는데,

특히 함수 표현에 있어 획기적이라고 할 수 있는 표현법이 익명함수와 람다식 입니다.

그리고 프로그래밍 기법에 있어서도 특히 비동기 프로그래밍을 위해

고차함수, 콜백함수들이 도입되었습니다.

 

여기에서 개선됐다는 기준은 80, 90년대의 프로그램 기법에서 개선됐다는 말입니다. ;;

그러니까 이 기술들에 대한 지원이 시작된 것은 2000년대 부터 였던 것 같네요.

그 이후 지금까지 커다란 변화는 없는 것 같습니다.

 

각 용어와 표현 방법에 대해 간략하고 쉽게 정리해봤습니다.

 

람다식

함수를 간결화한 형태.

익명 클래스나 익명 함수를 간결하게 표현할 수 있어 편리함.

한편 코드가 간결해져서 좋긴하지만, 남발할 경우 가독성이 떨어져 디버깅이 힘들어 질 수도 있음.

아래 세 가지 표현은 모두 같은 의미임.

 

① 일반 함수

fun add(x: Int, y: Int) : Int {          // 함수 선언부 (2개의 int형 인자를 받아 int형 값을 리턴)

   return x + y                     // 함수의 내용 본체

}

val sum: Int = add(3, 4)

val sum = add(3, 4)                 // 이렇게 줄임 표현도 가능

 

② 문법적으로 허용된 생략을 한 일반 함수

fun add(x: Int, y: Int) = x + y         // 함수 선언부에 인자 목록은 명시. 리턴 타입은 생략

                                   // 함수 본체의 제일 마직막 표현식에 따라 자동으로 리턴 타입이 추론됨

val sum = add(3, 4)

 

 

③ 람다식

val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }   // '{ 인수들 -> 함수본체 }' 부분이 람다식임

val sum: (Int, Int) -> Int = {x, y -> x + y }           // 이렇게 줄여 표현 하든

var sum = {x: Int, y: Int -> x + y }                  // 이렇게 줄여 표현하든 모두 가능

                                                 // (전달인자든 람다식 안에서든 데이터형 명확히 지정됐으므로)

 

                                    // 그리고 람다식이 저장된 변수 sum은 일반 함수처럼 호출이 가능함

sum(1,2)                           // 결과: 3

 

 

 고차 함수 (High-order Function)

 

함수의 전달인자나 반환 값의 데이터형에 함수가 포함된 형태.

 

fun printResult( s: String, r: Float, ff: (num: Float) -> Float) {                //  *1

   val result = ff(r)

   println("$s 는 $result")

}

printResult("원의 넓이", 3.0f, {x -> 3.14f * x*x })                          // *2

printResult("구의 체적", 3.0f, {x -> 4.0f/3.0f * 3.14f * x*x*x })             // *3

 

실행 결과)

원의 넓이 는 28.26

구의 체적 는 113.04001

 

 

*1 함수의 전달인자를 표시하는 방식은,

   문자 : 전후에 함수내에서 사용할 전달인자 이름, 그 전달인자의 데이터형을 명기한 형태입니다.

   결국 전달인자로 사용된 함수 부분은,

      함수명은 ff, 데이터형은 (num: Float) -> Float) 라고 해석할 수 있습니다.

                            

                            (num: Float) -> Float)

                            이 부분이 람다식으로 표현되어 있네요.

                            Float 형 인자 하나를 받고 Float 형으로 리턴하는 형태의 함수가

                            전달인자로 사용될 수 있음을 의미합니다.

 

*2, *3

  그림으로 좀 더 쉽게 설명을 해 보자면,...

   ①,② 각각의 함수가 *1의 전달인자 선언을 통해 printResult()내에서 result 값을 계산하는데 사용되었습니다.

 

  참고로 ①,②는 호출한 함수 printResult에 의해 거꾸로 호출되는데, 이와 같은 함수를 '콜백함수'라고 합니다.

   한편, 이번 예에서는 이 함수들을 이름 없이 (이름이 없으므로 '익명(Anonymous) 함수'라고 부릅니다)

   함수 본체만을 람다식 형태로 전달했지만, 여러 부분에서 이 함수들을 반복해서 참조한다면

   이름을 갖는 함수를 정식으로 정의하고 그 함수명을 전달해줘도 됩니다.

 

 

   참고) 람다식을 사용할 때 몇몇 표현의 융통성

 

      *2, *3의 표현은 (눈치를 챘는지 모르겠지만) 아래의 표현에서 전달인자 데이터형을 생략한 것 입니다.

      printResult("원의 넓이", 3.0f, {x: Int -> 3.14f * x*x }) 

                                                  // (데이터형이 printResult()선언부에 이미 작성되어 있으므로 생략 가능)

 

      *2, *3처럼 함수를 호출할 때 제일 마지막 전달인자가 람다식인 경우,

      람다식을 ()밖에 적어도 됩니다.

      printResult("원의 넓이", 3.0f) {x -> 3.14f * x*x }        // 람다식만 밖으로 꺼내 작성했음

 

      *2, *3처럼 심지어 람다식의 전달인자가 단 하나 뿐이라면, 람다식의 전달인자를 아예 생략해도 됩니다.

      그리고 그 전달인자는 키워드 it으로 참조할 수 있습니다.

      printResult("원의 넓이", 3.0f) {3.14f * it*it }

 

         fun sum( x: Int, y: Int, prt: (num: Int) -> Unit) {       //  Unit는 리턴형이 없다는 의미임(void와 같음)

            val total: Int

            total = x + y

            prt( total )

         }

         sum(3,4) {println(it)}                               // 결과: 7 (람다식의 표현이 거의 깡패 수준이죠?...)

 

 

      뿐만아니라, 고차함수의 전달인자가 람다식 하나 뿐이라면, 고차함수를 호출할 때 ()를 생략할 수도 있습니다.

 

         fun sum(prt: (num: Int) -> Unit) {       // 전달인자가 람다식 하나 뿐이면,

            prt(100)

         }

         sum() {print(it)}

         sum {print(it)}                         // 호출 때 빈 ()를 생략 가능함

 

 

 

 

   ※ 콜백함수의 활용

      비동기 프로그래밍에서 작업시간이 오래 걸리는 함수에게 콜백함수를 전달해주고,

      메인 프로그램은 다음 동작을 계속 진행.

 

      예)

      fun longTimeJob( ..., callback() ) {

         (오래걸리는 작업 - DB, 통신, 사용자이벤트, ...)

         callback()        // 오래걸리는 작업 완료

      }

 

      메인프로그램작업1

      longTimeJob( ..., cBackFun() )     // 콜백함수를 넣어 longTimeJob() 호출 후 다음 작업 계속

      메인프로그램작업2

      ...

      ...

 

 

      참고로, 이런 활용이 UI등에서 사용자 응답 이벤트를 다루는 경우에 이뤄진다면,

      언제 발생할 지 모르는 응답에 대해 대기를 해야 하는 '이벤트 리스너' 함수에게

      그 응답을 처리할 작업을 담고 있는 '이벤트 핸들러(= 이벤트 처리기)' 함수(콜백함수)를 전달하게 됩니다.

      말하자면 다음과 같은 모양이 됩니다.

 

      fun 리스너(핸들러) {

         (센서등 응답 대기...)

         핸들러()

      }

 

      메인프로그램작업1

      리스너(핸들러)

      메인프로그램작업2

      ...

      ...

 

 

      읽을 거리) 그럼 함수의 전달인자로 함수를 받을 수 없었던 예전에는 버튼 이벤트를 어떻게 처리했나?

         (함수가 객체는 받을 수 있었다. 자바에서 변수는 곧 객체)

         이벤트 핸들러 구현용 인터페이스를 개발팀 공용으로 만들어 놓고,

         각 팀원들이 그 인터페이스를 구현한 자신들만의 클래스를 만들고 객체화하여 리스너 함수에 전달함으로써

         콜백함수를 구현했습니다.

         아래 버튼 이벤트 처리의 경우, 인터페이스의 추상메서드 onClick()을 구현한 이벤트 핸들러 객체

         'new onClickListener()'를 전달하고 있습니다.

 

         Button button = findViewID(R.id.button)

         button.setOnClickListener( new onClickListener() {

            @override

            public void onClick(View view) {

                // 이벤트 처리

            }

         }

 

 

간략화된 표현식에 대해서는 오히려 혼동을 주는 경우도 있을 수 있습니다.

각자 편한대로 코딩하면 됩니다. 그래도 내용은 알고 있는 게 좋겠죠. 다른 프로그램을 해독하려면요.

 

그럼, 이만~

 

본 블로그를 찾은 분들에게 오늘도 축복 있으시길...

[코틀린의 고급 문법 2]

고급 문법 마지막 시간입니다, 힘내자구요~~ 화이팅!

 

람다식

람다식이란 함수를 간결하게 표시한 형태입니다. 익명 클래스나 익명 함수를 간결하게 표현할 수 있어 편리합니다.

코드가 간결해져서 좋긴하지만, 남발할 경우 가독성이 떨어져 디버깅이 힘들어 질 수도 있습니다.

아래 세 가지 표현은 모두 같은 의미입니다. (코딩 간결화의 변천 과정이라고 생각해도 됨)

 

① 일반 함수
fun add(x: Int, y: Int) : Int { return  x + y }

② 문법적으로 허용된 생략을 한 일반 함수
fun add(x: Int, y: Int) = x + y

③ 람다식
var add = {x: Int, y: Int -> x + y }      // { } 부분이 람다식임.  '{ 인수들 -> 함수본문 }' 형태임

사용 예)
println(add(3+4))        // 결과: 7

 

 

 

SAM 변환

JAVA에서 메소드가 하나뿐인 인터페이스는 인터페이스를 구현하는 대신 함수로 구현할 수 있는데,
이것을 'SAM변환'이라고 합니다.  SAM (Simple Abstract Method ; 간단한 추상메소드)

코틀린 함수는 매개변수로 한 개의 추상메소드를 받는 형태를 취할 수 있는데,
이 때는 자바에서 작성된 인터페이스인 경우에 한해,
자바의 SAM변환과 유사하게 함수로 구현된 매개변수를 전달할 수 있습니다.

예를 들어 버튼의 클릭 이벤트를 구현할 때, View.OnClickListener 인터페이스를 구현하는데,
 (이 View.OnClickListener는 onClick() 이라는 추상메소드 한 개만을 가지고 있음)
이 인터페이스를 함수로, 더 나아가 익명메소드 형태로 구현해서 전달해 봅시다.

button.setOnClickListener(object : View.OnClickListener {    //OnClickListener는 OnClick메소드 하나만 포함
   override fun onClick(v: View?) {                        // 익명메소드 형태
       // 클릭시 처리 코드
   }
}
)

그리고 위 익명메소드는 람다식으로 표현할 수 있으므로,

button.setOnClickListener({ v: View? ->                // OnClickListener가 람다식화된 형태
      // 클릭시 처리 코드(람다 블럭)
})


코틀린에서 메소드 호출 시 제일 뒤의 매개변수가 람다식인 경우에는,
가독성을 위해 람다식을 ( ) 밖으로 빼서 작성할 수 있습니다.

button.setOnClickListener( ) { v: View? ->
     // 클릭시 처리 코드(람다 블럭)
}

그리고 람다식이 어떤 메소드의 유일한 매개변수인 경우에는 메소드의 ( ) 를 생략할 수 있습니다. 게다가 컴파일러가 자료형을 추론할 수 있다면 자료형을 생략할 수 있는 것이므로, 최종적으로 다음과 같이 되겠네요.

button.setOnClickListener{ v ->
     // 클릭시 처리 코드(람다 블럭)
}

※ 만약에 '클릭시 처리 코드'에서 v라는 매개변수를 사용하지 않는다면,
v를 _ 기호로 대치할 수도 있습니다.

button.setOnClickListener{ _ ->
       // 클릭시 처리 코드(람다 블럭)
}

※ 또 만약에 그리고 람다식에서 매개변수가 하나 뿐인 경우라면,
매개변수를 아예 생략할 수도 있습니다.

button.setOnClickListener{
    // 클릭시 처리코드(람다 블럭)
}

그리고 이 때 람다블럭내에서는 매개변수를 it로 접근할 수 있습니다.

예를들면,
button.setOnClickListener{
    it.visiblity = view.GONE             // 여기서 it는 View? 형 v를 의미함 ----- (K)
}


위에서 SAM변환을 이용한 익명메소드에서 람다식화, 그리고 코틀린의 편리한 생략 표현 기법들을 총동원해 코드를
간결화시켜봤고 코드들 모두 같은 의미입니다. 어쨌거나 가장 가독성이 뛰어난 형태는 (K)의 코드죠. 앞으로 종종 등장합니다.

 
주의해야 할 점은 코틀린에서의 SAM변환은 자바에서 작성한 인터페이스일 때에만 동작한다는 것~!.


 

확장 함수

코틀린에서는 .연산자를 이용해 이미 정의된 클래스에 메소드를 쉽게 추가할 수 있습니다.
(보통 Java나 C#에서는 final로 상속이 봉인되어 있어서 메소드를 추가하지 못하는 경우가 많은데 편리한 기능인 것 같네요)
(확장 함수 내부에서 해당 객체로의 접근은 this를 사용)

예) Int 클래스에 isEven()을 추가해 보자.
fun Int.isEven( ) = this % 2 == 0    // 'this % 2 == 0'의 결과인 부울 값이 함수의 결과 값이 됨

println(5.isEven())        // 결과: false


 

형 변환

val a = 10L

b = a.toInt()
c = a.toDouble()
d = a.toString()

e = Integer.parseInt(d)      // 문자열 형을 숫자로 변환


※ 일반 클래스 간 형 변환 (as 키워드)
open class Human()
class Man: Human()

val man = Man()
val human = man as Human     // Human 형으로 형 변환


형 체크

val st = "hello"
if (st is String) { println(st.toUpperCase() }



고차 함수

함수를 매개 변수로 전달하거나 함수 형으로 반환할 수 있습니다. 이렇게 사용되는 함수를 '고차 함수'하고 합니다.

fun add( x: Int, y: Int, callback: (sum: Int) -> Unit) {   // 두 개의 Int 매개변수와 한 개의 익명함수 매개변수
                                                 // 매개 변수로 쓰인 익명함수는 한 개의 Int 매개변수를 받고 리턴 값은 없음
    callback( x + y )
}

add(3,4, {println(it)})      // 결과: 7    함수를 { }로 감싸고, 이 함수 내부에서는 반환 값을 it로 접근한 예임



동반 객체

'팩토리 메소드'
코틀린에서는 클래스를 객체화 하는 것과는 별개로 메소드를 이용해 객체를 생성하는 코딩 패턴을 지원하는데, 이를 '팩토리 메소드'라고 합니다.
(나중에 다루게 될 프래그먼트 컴포넌트는 특수한 제약 때문에 팩토리 메소드로만 객체를 생성할 수 있음)

코틀린에서는 타 언어에서 정적인 메소드를 만들 때 사용하는 static 키워드 같은 게 없습니다. 그 대신 '동반 객체 (companion object)'라는 것을 통해 이를 구현합니다. (companio : 동반자, 동료, 친구)

다음은 newInstance() 정적 메소드를 사용해서 Fragment 객체를 생성하는 팩토리 패턴을 구현한 것입니다.
여기서 동반 객체 내부의 메소드는 Fragment 클래스와 아무 관계가 없는 정적인 존재입니다.

class Fragment {
   companion object {
      fun newInstance(): Fragment {    // shs: 함수의 반환 형이 Fragment 형이라... 
         println("생성됨")
      }
   }
}

val fragment = Fragment.newInstance()   // shs: 이거 정적 Fragment 안의 newInstance 멤버메소드를 액세스 하는 것과 비슷한데요...



<<< 코틀린 기본 라이브러리에서 유용한 함수들 >>>

let()

블럭에 자기 자신을 인수로 전달(it로 참조)하고 실행 결과를 반환합니다. 
'안전한 호출' 즉, str이 null이 아닐때만 호출 되도록 ?.연산자를 이용하면 더 좋습니다.

val result = str?.let {            //   결과는 Int형
    Integer.parseInt(it)
}
// fun <T, R> T.let(block: (T) -> R): R


with()

객체를 매개변수로 받고 블럭에 리시버 객체형으로 전달(this로 참조)해 준 후, 실행 결과를 반환합니다.
단, '안전한 호출'이 불가능하기 때문에 반드시 str이 null아닐 때에만 호출해야 합니다.

with(str) {
    println(toUpperCase())     // this.toUpperCase()에서 this.를 생략할 수도 있다.
}
// fun <T, R> with(receiver: T, block T.() -> R): R


apply()

블럭에 객체 자신이 리시버 객체형으로 전달되고 그 객체형으로 반환됩니다.
(주로 객체의 상태를 변경해서 반환할 때 사용함)

val result = car?.apply {
   car.setColor(Color.BLACK)
   car.setPrice(1000000)
}
// fun  T.apply(block: T.() -> Unit): T


run()

run() 함수는 익명 함수처럼 쓰는 법과 객체에서 호출하는 법 모두를 제공합니다.

i) 익명 함수처럼 쓸 때
블럭의 결과를 반환합니다.
블럭 안에 선언된 변수들은 모두 임시로 잠깐 사용하는 변수들인데, 임시 변수를 많이 사용하는 복잡한 계산에 유용하겠네요.

val avg = run{
   val kor = 90
   val eng = 80
   val math = 90

   (kor + eng + math) / 3.0f
}
// fun  run(block: () -> R): R


ii) 객체에서 호출 할 때
객체를 블럭의 리시버 객체로 전달하고 결과를 반환합니다.
안전한 호출이 가능하므로 with()보다 더 유용합니다~ 

str?.run {
   println(toUpperCase())
}
// fun <T, R> T.run(block: T.() -> R): R

 

 

 

 

 

 

<<< 이전 글 보기      다음 글 보기 >>>

 

 

 

 

본 블로그를 찾은 분들에게 오늘도 축복 있으시길...

+ Recent posts