또는

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

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

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

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

 

여기에서 개선됐다는 기준은 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) {

                // 이벤트 처리

            }

         }

 

 

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

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

 

그럼, 이만~

 

+ Recent posts