함수형 프로그래밍
함수형 프로그래밍은 함수형 기능들을 사용해 변수의 사용을 최소화한 프로그래밍입니다. 코틀린은 함수형 프로그래밍이 가능한 언어로, 함수를 사용해 간략하고 우아한 프로그래밍을 할 수 있습니다.
함수형 프로그래밍에서는 순수함수, 람다식, 고차함수를 다룹니다. 일단 코틀린에서 사용하는 함수의 문법부터 함수형 프로그래밍을 구현하는 방법까지 차근차근 알아가 보도록 합시다.
함수 문법
fun 함수명 ( 매개변수: 매개변수 타입, ... ): 반환타입 {
...
}
fun sum(a: Int, b: Int): Int {
var sum = a + b
return sum
}
함수 코드 생략
// {} 안의 코드가 한 줄일 경우 중괄호와 return문 생략 가능
fun sum(a: Int, b: Int): Int = a + b
// 추론이 가능할 경우 반환타입 생략 가능
fun sum(a: Int, b: Int) = a + b
// 반환 값이 없을 때 반환타입 생략 가능
fun sum(a: Int, b: Int) {
println("sum of $a and $b is ${a + b}")
}
❗ 반환 값이 없을 때의 반환타입은 Unit입니다.
매개변수
매개변수 기본값
매개변수에 기본값을 설정해 줄 수 있습니다. 기본값이 설정된 매개변수는 인자로 넘기지 않아도 오류가 발생하지 않습니다.
fun add(name: String, email: String = "default") {...}
add("Haru", "haru@example.com") // 오류X
add("Haru") // 오류X
❗ 함수에서 전달되는 값들은 선언할 때는 매개변수, 호출할 때는 인자라고 부릅니다.
매개변수 이름 사용
매개변수 이름과 함께 함수를 호출할 경우 매개변수 순서를 지키지 않아도 됩니다.
add(email = "haru@example.com", name = "Haru")
가변인자
인자의 개수를 정하지 않고 사용할 수 있습니다. vararg 키워드를 사용합니다. 가변인자로 받아올 경우 배열형태로 저장됩니다. 만약 가변인자와 함께 일반 매개변수도 사용하고 싶다면 가변인자를 가장 마지막에 선언합니다.
fun main() {
sum("첫 번째 ", 1, 2, 3)
sum("두 번째 ", 4, 5)
}
fun sum(order: String, vararg numbers: Int) = println(order + numbers.contentToString())
// numbers는 IntArray
// 첫 번째 [1, 2, 3]
// 두 번째 [4, 5]
함수형 프로그래밍 규칙
1. 순수 함수를 사용해야 합니다.
❓ 순수 함수란?
- 같은 인자에 대해 항상 같은 결괏값을 반환합니다.
- 함수 외부의 어떤 상태도 바꾸지 않습니다.
2. 함수를 일급 객체로 간주합니다.
❓ 일급 객체란?
- 함수의 인자로 전달할 수 있고, 함수의 반환값에 사용할 수 있고, 변수에 담을 수 있는 것
❓ 일급 함수란?
- 함수가 일급 객체의 성격을 띠고 있을 때 일급 함수라 합니다.
❓ 람다식 함수, 람다식이란?
- 일급 함수에 이름이 없을 때 람다식이라 합니다.
❓ 고차 함수란?
- 일급 함수를 사용하는 함수를 고차함수라 합니다.
람다식
람다식을 변수에 할당했을 때의 형식입니다.
val(var) 변수명 : (매개변수 타입, ...) -> 반환타입 = {매개변수: 매개변수타입, ... -> 본문}
// 자료형 생략 x
val add: (Int, Int) -> Int = {x: Int, y: Int -> x + y}
// 선언 자료형 생략
val add = {x: Int, y: Int -> x + y}
// 람다식 매개변수 자료형 생략
val add: (Int, Int) -> Int = {x, y -> x + y}
// 선언 자료형과 람다식 매개변수 자료형을 모두 생략 할 수는 없습니다. (추론이 불가능 하기 때문)
값에 의한 람다식 호출 / 이름에 의한 람다식 호출
값에 의한 람다식 호출
람다식이 인자로 넘겨질 때 매개변수가 일반 변수 자료형으로 선언되어 있다면 값에 의한 호출이 발생합니다. 이때는 람다함수가 즉시 실행되어 결괏값을 넘겨줍니다.
이름에 의한 람다식 호출
람다식이 인자로 넘겨질 때 매개변수가 람다식 자료형으로 선언되어 있다면 이름에 의한 호출이 발생합니다. 이때는 식 자체가 매개변수에 복사되어 들어갑니다.
람다식과 매개변수
람다를 호출하는 함수의 매개변수
매개변수가 람다식 하나뿐일 경우 소괄호(())를 생략할 수 있습니다.
onlyLambdaParam ({ 람다식 }) //o
onlyLambdaParam { 람다식 } //o
람다식의 매개변수
// 매개변수가 1개 있는 람다식은 (매개변수 ->) 를 it으로 대체 가능
oneParam { a -> "$a" }
oneParam { "$it" } // 위와 동일한 결과
// 매개변수가 2개 이상 있는 람다식에서 특정 매개변수를 사용하고 싶지 않을 때 _로 대체 가능
specificParam { _, b -> "$b"}
// 일반 매개변수와 람다식 매개변수를 같이 사용하기
withArgs ("a", "b", {a, b -> "$a, $b"})
withArgs ("a", "b") {a, b -> "$a, $b"}
// 함수의 마지막 매개변수가 람다식이면 () 밖으로 람다식을 뺄 수 있음
일반 함수를 인자에서 호출하는 고차 함수
고차함수에서 람다식이 아닌 일반 함수를 호출하고 싶을 때는 :: 키워드를 사용합니다. 물론, 일반 함수의 매개변수 수와 매개변수의 자료형, 반환 자료형이 고차 함수의 매개변수에서 선언된 함수 양식과 일치해야 합니다.
여기서 :: 키워드는 함수를 참조하는 역할을 합니다. 따라서 ::함수로 함수를 변수에 할당하는 것도 가능합니다.
funcHigher(1, 2, ::add)
val funAdd = ::add
익명 함수
일반함수이지만 이름이 없는 특별한 함수입니다. 람다식과 유사하나 람다식에서는 사용하기 어려운 제어문(return, break, continue 등)을 사용하기 위해 사용합니다.
val add = fun(x: Int, y: Int) = x + y
인라인 함수
함수가 호출되는 곳에 함수 본문의 내용을 모두 복사해 넣어 함수의 분기 없이 처리됩니다. 따라서 자원 사용량을 줄일 수 있다는 장점이 있습니다.
람다식 매개변수를 가지고 있을 때 사용합니다. 람다식 코드, 인라인 함수의 본문이 너무 길어지지 않게 유의하고 인라인 함수가 너무 많이 호출되지 않게 합니다.
특정 람다식을 인라인 되지 않게 하기 위해 noinline키워드를 사용할 수 있습니다.
inline fun sub (out1 () -> Unit, noinline out2: () -> Unit) {}
확장 함수
필요로 하는 대상(클래스 등)에 함수를 더 추가할 수 있습니다.
fun 확장대상.함수명(매개변수: 매개변수타입): 반환타입 {...}
중위 함수
직관적인 이름 사용이 가능한 함수입니다. 일종의 연산자처럼 함수를 사용할 수 있습니다.
일반적으로 클래스의 멤버를 호출할 때는 점(.)을 붙여야 합니다. 또한 함수를 호출할 때는 함수 이름 뒤에 소괄호(())를 붙여야 하죠. 중위 함수에서는 점(.)과 소괄호(())를 모두 생략할 수 있습니다. +나 -와 같은 연산자 처럼 사용되는 것입니다.
중위 함수가 되기 위해서는 멤버 메서드 또는 확장 함수여야 하며, 하나의 매개변수를 가져야 합니다. infix 키워드로 정의할 수 있습니다.
fun main () {
val multi = 2 multiply 10
}
infix fun Int.multiply(x: Int): Int {
return this * x // this : 2, x: 10
}
꼬리 재귀 함수
재귀 함수는 자기 자신을 호출하는 함수를 의미합니다. 재귀함수는 스택 오버플로를 발생시킬 수 있기 때문에 무한 호출되거나, 너무 많이 호출되지 않도록 주의해서 사용해야 합니다. 코틀린에서는 꼬리 재귀 함수로 스택 오버플로 현상을 해결할 수 있습니다. tailrec 키워드를 사용해 선언합니다.
일반적인 재귀 함수
재귀 함수가 먼저 호출되고 계산됩니다.
fun main() {
val number = 4
val result: Long
result = factorial(number)
}
fun factorial(n: Int): Long { // 재귀 함수
return if (n == 1) n.toLong() else n * factorial(n-1)
}
꼬리 재귀 함수
계산을 먼저 하고 재귀 함수가 호출됩니다.
fun main() {
val number = 4
val result: Long
result = factorial(number)
}
tailrec fun factorial(n: Int, run: Int = 1): Long { // 꼬리 재귀 함수
return if (n == 1) run.toLong() else factorial(n-1, run * n)
}
함수와 변수의 범위
최상위 함수 : 파일 안 가장 바깥에 선언된 함수입니다.
최상위 함수끼리는 선언 순서에 상관없이 사용할 수 있습니다.
지역 함수 : 함수 안에 선언된 함수입니다.
지역함수는 자신보다 위에서 선언된 지역함수만을 사용할 수 있으며 상위 함수의 블록을 벗어난 곳에서는 사용할 수 없습니다.
전역 변수 : 파일 안 가장 바깥에 선언된 변수입니다.
같은 패키지 안에서 접근이 가능합니다.
지역 변수 : 특정 코드 블록 안에 있는 변수입니다.
그 코드 블록 안에서만 사용할 수 있습니다.
함수형 프로그래밍에 대해 살펴보았습니다. 다음 글에서는 클래스와 객체에 대해 설명드리겠습니다. 감사합니다.
'개발언어 > Kotlin : 코틀린' 카테고리의 다른 글
코틀린 익히기 6 - 프로퍼티와 object (0) | 2023.09.25 |
---|---|
코틀릭 익히기 5 - 클래스와 객체 (0) | 2023.09.21 |
코틀린 익히기 3 - 조건문과 반복문 (1) | 2023.09.19 |
코틀린 익히기 2 - 자료형, 연산자 (0) | 2023.02.27 |
코틀린 익히기 1 - 코틀린이란 무엇인가? (0) | 2023.02.25 |