저번 포스팅에서 코틀린에서의 람다함수에 대해서 따로 뺴서 정리하기로 했었다. 내용이 워낙 많기도 하고 조금 중요한 개념이기때문에 따로 빼서 정리해야지 나중에 글을 볼때도 덜 헷갈리거 같다. 많은 구글링을 하기도 했고 공식문서도 드나들었지만 내가 원하는 방향으로써의 람다함수를 설명해주신 유튜버가 있었고 그분의 영상을 참고하여 정리하도록 하겠다. 참고한 유튜브 영상은 맨마지막 하단에 따로 링크를 달도록 하겠다.
내가 이해한 개념을 정리하기 때문에 오개념일 수 도 있다. 내가 그것을 인지하게 되면 후딱와서 수정하도록 하겠다.
# 일반함수와 람다 함수 비교
람다 함수는 익명함수라는 뜻이다. 즉 1회성으로 사용되는 함수이거나, 함수자체에 이름이 없다는 것이다. 모든 언어에는 함수를 선언하는 방식이 정해져있다. 사실 람다함수라고해서 전혀 새로운 개념이 아니었다. 리액트로 이것저것 만들면서 화살표 함수를 사용했었는데 이것이 익명함수라고 할 수 있었다. 결론은 함수 자체를 변수에 담을 수 있고, 또 함수를 매개변수로 이용할 수 있다는 것이다.
밑에서 예제 코드를 작성해보면서, 일반적으로 선언하는 함수와, 람다함수(익명함수)의 선언방식이 어떻게 다른지 확인해 보도록 하겠다. 내가 람다 함수를 공부하게된 결정적인 이유는 setOnClickListner에서 람다함수가 사용됐기 때문인데 글의 말미에서는 결론적으로 여기서 어떻게 활용됐는지에 대해서 알아보도록 하겠다.
log는 찾기 편하게 log.e()로 찍었다.
# 예제로 비교해보기
일반적으로 함수를 선언하는 방식이 존재하고, 매개변수가 있을때와, 반환값(return)이 있는경우로 나누어 진다.
## 일반함수
매개변수X 반환값 X
// 매개변수X 반환값 X
fun someFunction() {
Log.e(tag,"someFunction() called")
}
someFunction()
매개변수O 반환값 X
// 매개변수O 반환값X
fun paramFunction(str : String) {
Log.e(tag,"paramFunction( [ $str ] ) called")
}
paramFunction("매개변수입니다")
매개변수 O 반환값 O
// 매개변수O 반환값O
fun paramAndReturnFunction(num1:Int, num2:Int) : Int {
val result = num1 + num2
return result
}
val sum = paramAndReturnFunction(10,20)
Log.e(tag,"paramAndReturnFunction() = $sum")
위의 함수를 각각 실행한 결과는 아래와 같다
###실행결과
그렇다면 위와같이 사용한 것들을 람다함수로 사용이 가능해야 한다.
## 람다함수
반환값 Unit은 자바에서 Void로 생각하면 된다, 즉 따로 반환값이 존재하지 않는다는 것이다. 설명에 앞서 변수 선어에 타입을 지정해주는 모습과 연관지어 생각해보면 조금 더 이해가 빨랐다. 그리고 내가 생각한 주된 컨셉(concept)은 변수에 함수를 담는다는 느낌이었다.
매개변수X 반환값X
val lambda : ()->Unit = {
Log.e(tag,"lambda called!")
}
lambda.invoke()
// or
lambda()
위와 같이 () -> Unit 의 형태로, 매개변수와 반환형을 적어준다.
매개변수O 반환값X
val paramLambda : (String) -> Unit = {
Log.e(tag, "paramLambda called!! param is [ $it ]")
}
// or
val paramLambda : (String) -> Unit = { myinput ->
Log.e(tag, "paramLambda called!! param is [ $myinput ]")
}
val param2Lambda : (Int,Int) -> Unit = { num1,num2 ->
Log.e(tag, "paramLambda called!! param is [ $num1, $num2 ]")
}
paramLambda("김호쭈입니다")
param2Lambda(11,22)
여기서 짚고 넘어가야할 것은 매개변수가 하나일 경우에는 생략이 가능하고, 이렇게 생략했을 시에는 it으로 매칭된다. 생략하지 않고 별도로 지정해줘도 된다.
매개변수O 반환값O
val paramReturnLambda : (Int,Int) -> Int = {num1,num2 ->
Log.e(tag, "in paramReturnLambda")
val result = num1 + num2
result
}
val result = paramReturnLambda(10,20)
Log.e(tag,"result = $result")
// ' _ ' 키워드는 해당 매개변수를 사용하지 않는다는 의미
val paramReturnLambda : (Int,Int) -> Int = {num1, _ ->
Log.e(tag, "in paramReturnLambda")
val result = num1
result
}
반환값을 주는 방법이 조금 새로운데, 맨마지막 줄이 반환값이 된다. 따로 return을 적지 않는다. 즉 result가 자동으로 반환값이 된다.
### 실행결과
# 람다함수 적용
이제 중요한 개념이 등장한다. 람다함수를 사용하면 함수 자체를 변수에 할당해서 사용하는 것 처럼 사용할 수 있다고 했다. 그렇다면 이 람다함수를 함수의 매개변수로 넘겨줄 수 있다는 것이 된다. js에서 콜백함수와 똑같다. 함수를 매개변수로 넘겨 함수를 사용한다.
## 람다함수만 매개변수로 가지는 함수
// 일반적인 매개변수를 사용하는 함수
fun paramFunction(str : String) {
Log.e(tag,"paramFunction( [ $str ] ) called")
}
paramFunction("매개변수입니다")
// 람다함수가 매개변수인 함수
fun paramIsLambdaFun( myFun : () -> Unit ) {
Log.e(tag,"paramIsLambdaFun() called!!")
Handler().postDelayed({
myFun()
},1000L)
}
paramIsLambdaFun {
Log.e(tag,"여기서 함수의 기능을 새로 정의해서, 1초 뒤 콜백함수로 실행됨!")
}
이제 감이 딱 왔을거 같다. 결국 함수를 매개변수로 넘겨주기 위해서 사용했구나! 함수 호출 부분에서 paramIsLambdaFun { ... } 형식으로 사용하는데 { ... } 부분은 람다함수로써, 저 부분이 즉 함수의 매개변수로 넘어가게 되는 것이다. 위 paramIsLambdaFun함수를 실행하면 어떻게 되는지 보자
### 실행결과
근데 아직 뭔가 찝찝한 느낌이 든다. 예제코드를 하나더 만들어 보겠다.
## 일반적인 매개변수와, 람다함수를 매개변수로 가지는 함수
fun param2IsLambdaFun( num : Int, myFun : (String) -> Unit ){
Log.e(tag,"param2IsLambdaFun() called!!, first param num is $num")
Handler().postDelayed({
// 여기(here)
myFun("람다함수의 매개변수 입니다. $num")
},1000L)
}
param2IsLambdaFun(123, myFun = {
Log.e(tag," myFun's param : [ $it ]")
})
### 실행결과
여기까지 왔으면 어느정도 드는 생각이 있다. 매개변수로 들어가는 람다함수의 매개변수 자체는 그 함수를 실행시키는 부분 "여기(here)"이라고 주석친 부분에서 결정된다. 코틀린에서 제공받는 람다함수로 이벤트를 내가 구현하게 되는 함수들은 이부분은 이미 어떤게 들어가는지 결정 돼 있다는 것이다.
# 응용, 고차함수에서의 람다
리액트하면서 map이랑 forEach가 뭐가 다른지 확실히 이해를 못했는데 여기서 이걸 이해할 줄은 꿈에도 몰랐다. 코틀린.. 너의 매력은 어디까지니..?
## map
map을 쓰다가 말면 코틀린이 친절히 설명을 해준다. 이제까지 한것을 기반으로 해석을 해보자.
- map에는 람다함수로 기능을 정의해라!
- 매개변수로 쓰이는 람다함수(내가 정의할)는 Int을 받아서 R(새로운 자료형)을 반환시켜야한다
- 최종적으로 List<자료형>을 반환한다.
val myList = mutableListOf<Int>(1,2,3,4,5,6)
val mapedMyList = myList.map{ num ->
"Int to String $num"
}
Log.e(tag,"$mapedMyList")
### 실행결과
## forEach
그럼 위에서 map을 이용해 변형시킨 mapedMyList에 forEach를 사용해보자
val tempList = mapedMyList.forEach{ it ->
"$it"
}
Log.e(tag,"$tempList")
이렇게 사용했으면 뭐 의도했다면 맞는거지만, 그게 아니라면 잘못 이해하고 있다는 것이다. 다시 안스가 친절히 알려주는 구문을 읽어보자
- forEach에는 람다함수로 기능을 정의해라
- forEach의 매개변수로 오는 람다함수는 String!을 인자로 받으며 반환값은 Unit(없음)다.
- 최종적으로 반환되는것도 없다
이런 뜻이다. 그러면 그 루프만큼 이벤트를 정의해야겠지~~~? 즉 반복문이 도는거다.
mapedMyList.forEach{ it ->
Log.e(tag,"$it")
}
### 실행결과
# 첨언
내가 참고한 유튜브에서는 이후에 람다함수를 활용하여 이벤트처리와 커스텀다이얼로그에 버튼클릭에 대한 이벤트를 정의하도록 한다. 근데 미리 만들어 놓으신 예제를 사용하시기 때문에 내가 그것까지 다 구현 할 수는 없기때문에 생략한다. 대신 거기서 살짝 중요한거 하나만 기록해두록 하겠다.
코틀린은 이렇게 null또는 String을 지정해주어 null에 대해서 확고히 사용 할 수 있도록해준다.
// String or null
val myString : String? = null
그렇다면 람다함수의 null은 어떻게 처리해줄까?
val likeAction : (()->Unit)? = null
이렇게 추상화 시켜놓고, 커스텀한걸 사용할때
val myCustomDialog = MyCustomDialog(this,this)
myCustomDialog.likeAction = {
// 이벤트 정의
}
요로코롬 사용한다. 이렇게 응용이 되구나 알아만 두자!
정말 쉽게 잘 설명해주신다. 자기전에 틀어놓고 들으면서 자야겠다. 여튼 난 람다함수 이해 완료다. 헷갈리면 다시 들어와서 쭉 훓어봐야지~~