이 글은 나의 무지를 반성하고 더 깊은 지식의 갈구를 하기 위해서 작성되었다. 코틀린을 사용해 picpho라는 앱 서비스를 5주의 개발과정을 거쳐서 출시하였다. 아무리 절대적인 시간이 적었다 하더라도 개발 지식을 보충할 시간은 충분했다고 생각한다. 더 깊게 알아보지 않았을 뿐.
5주의 개발과정이 끝나고 서초 펍지 사옥에서 현업에 계신 개발자들앞에서 서비스를 발표를 한 뒤 질문을 받는 시간이 있었다. 어떤 개발자분께서 Kotlin이 함수형 프로그래밍을 지원하는 언어인데 이를 알고 사용하신거냐는 질문을 하셨다. 내가 프로젝트에 코틀린을 사용했던 이유는 다음 두 가지였다.
1 . JAVA보다 사용자 친화적 언어로 배움이 빠르기 때문에 5주라는 짧은 시간내에 익히고 개발하기에 적합하다고 생각했다.
2 . 구글에서 지원하는 언어이고 차세대로 갈수록 각광받을 언어라는 단순 미래지향적인 생각을 하였다.
따라서 함수형 프로그래밍에 대한 깊은 고민을 하지 않고 선택했었던 것이다. 그러니 관련내용에 대해서 잘 알지 못하는 것은 또한 당연했다. 또, 어떤 기업의 코딩테스트를 합격하고 1차 기술 면접을 볼 때 이런 질문을 받은 적이 있다. Kotlin에서 고차함수에 대해서 알고 있느냐 라는 질문이었다. 당시 고차함수에 대한 개념정도는 알고있었는데 정말 딱 그정도였다. 그래도 나름 안드로이드 쪽에서 나오는 질문들을 대부분 대답할 수 있겠다고 생각하고 있었는데 정말 간단한 내용에서 대답을 잘 못하는 내 자신을 반성하게 되었다.
그래서 이 글을 작성한다. 단순히 개념정도를 아는 것에서 벗어나 실제 코드를 작성해보고 이해해보려고 한다.
먼저 위키백과에서 정의를 본다.
함수형 프로그래밍(functional programming)은 자료 처리를 수학적 함수의 계산으로 취급하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임의 하나이다.
코드를 작성하면서 가변 변수들을 사용하면서 생길 수 있는 문제들을 배제하려는 프로그래밍 방식이다. x가 나왔을 때 무조건 f(x)라는 값이 정해져있는 수학적 함수 방식을 가진 프로그래밍이다.
고차함수는 함수를 인수로 취하거나 혹은 결과로 반환하는 함수를 고차함수라고 한다. 안드로이드를 개발하다보면 굉장히 많이쓰는 setOnclickListner같은 콜백 함수가 고차함수에 속한다.
람다 대수는 함수를 단순하게 표현할 수 있도록 도와주는 개념이다. 중괄호로 묶어 사용할 수 있다.
< 사용 예시 >
{ 변수1 : 타입, 변수2 : 타입 -> 변수1 + 변수 2 }
위와 같은 방식으로 사용할 수 있다. 고차함수의 인자로 사용할 수 있어서 고차함수와 꼭 같이 나오는 개념이다. 또한 함수가 이름을 가질 필요가 없는 익명함수이기도 하다.
fun main{
// TODO
}
람다 함수는 변수로 선언할 수도 있다.
val sum : (Int, Int) -> Int = {x, y -> x+y}
(타입, 타입) -> 반환형 타입
간단하게 표현하면 다음과 같다.
val sum = {x : Int, y : Int -> x + y}
main에서 호출을 해보자.
// main
In : println(sum(2,3))
Out : 5
fun Calculator(a : Int, b : Int, p: (Int, Int) -> Int){
// Calculator는 고차함수라고 말할 수 있다.
println("$a, $b -> ${p(a, b)}")
}
a, b와 두 개의 Int 인자를 받아 하나의 Int 반환값을 내는 함수를 인자로 받아 이를 출력하는 Calculator라는 고차함수를 만들었다. main에서 호출을 해보자.
In : Calculator(2, 4, { a : Int, b : Int -> a + b})
Out : 2, 4 -> 6
이때 lambda 함수가 인자의 제일 마지막에 있다면 밖으로 뺄 수 있다.
In : Calculator(2, 4) { a: Int, b: Int -> a + b }
Out : 2, 4 -> 6
또한 고차함수에 타입이 정의되어있는 경우 타입을 생략할 수 있다.
In : Calculator(3, 5) { a, b -> a + b }
Out : 3, 5 -> 8
람다함수가 아닌 일반함수를 인자로 넣으려면 일반함수 앞에 ::를 붙여 다음과 같이 하면 된다.
fun sum(a : Int, b : Int) = a + b
// main
In : Calculator(5,7, ::sum)
Out : 5, 7 -> 12
함수형 변수를 인자타입에 넣는 경우는 다음과 같이 한다.
In[0] : val minus : (Int, Int) -> Int = {a, b -> a-b}
In[1] : Calculator(5,2, minus)
Out : 5, 2 -> 3
fun Square(a : Int, p: (Int) -> Int){
println("square $a -> ${p(a)}")
}
위와 같이 a라는 인자 하나를 받는 경우 조금 다르게 표현할 수도 있는데 다음과 같다.
In : Square(3) {a -> a * a}
Out : square 3 -> 9
In : Square(3) {it * it}
Out : square 3 -> 9
위 두 호출은 같은 호출이다. 다만 인자가 하나일 때는 it 을 사용해 더 간단하게 표현이 가능하다.
fun PrintInfo(p : () -> Unit) {
print("Calculator Version : ")
p()
}
위와 같이 의미있는 반환값이 없을 경우 Unit을 사용한다. main에서 호출해보자.
// main
In : PrintInfo() {println("1.0")}
Out : Calculator Version : 1.0
방금 경우와 같이 고차함수의 인자로 매개변수없이 함수식만 있는 경우 소괄호() 는 생략가능하다.
// main
In : PrintInfo {println("1.1")}
Out : Calculator Version : 1.1
또한 매개변수 함수식을 Nullable로 할수도 있다.
fun PrintInfo(p : (() -> Unit)? = null{
print("Calculator Version : ")
p?.invoke()?: println("no version")
}
함수식에 null이 들어왔을 경우 ?: 기호 뒤의 식을 실행한다. main에서 호출해보자.
In : PrintInfo()
Out : Calculator Version : no version
글을 쓰며 공부하다보니 프로젝트를 진행하면서 고차함수와 람다를 알게모르게 많이 썼다는 사실을 알게되었다. 여행지를 가기 전에 미리 여행지에 대한 정보를 공부하고 가면 가서 보이지 않던 것이 보이고 더 새로운 느낌으로 다가오는 경험을 해본 적이 있다. 이처럼 그냥 사용할 수 있는 프로그래밍을 하더라도 자세히 공부해보고 사용해본다면 분명 더 발전하는 경험을 할 수 있을 것이라는 생각이 든다. 👍
몽구스 프로그래밍 / 네이버 블로그
함수형 프로그래밍 / wiki
고차함수 / wiki
람다대수 / wiki