본문 바로가기
개발언어/Kotlin : 코틀린

코틀린 익히기 4 - 함수형 프로그래밍

by 개발자D 2023. 9. 20.

함수형 프로그래밍

함수형 프로그래밍

함수형 프로그래밍은 함수형 기능들을 사용해 변수의 사용을 최소화한 프로그래밍입니다. 코틀린은 함수형 프로그래밍이 가능한 언어로, 함수를 사용해 간략하고 우아한 프로그래밍을 할 수 있습니다.

함수형 프로그래밍에서는 순수함수, 람다식, 고차함수를 다룹니다. 일단 코틀린에서 사용하는 함수의 문법부터 함수형 프로그래밍을 구현하는 방법까지 차근차근 알아가 보도록 합시다.

함수 문법

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)
}

 


함수와 변수의 범위

최상위 함수 : 파일 안 가장 바깥에 선언된 함수입니다.

최상위 함수끼리는 선언 순서에 상관없이 사용할 수 있습니다.

 

지역 함수 : 함수 안에 선언된 함수입니다.

지역함수는 자신보다 위에서 선언된 지역함수만을 사용할 수 있으며 상위 함수의 블록을 벗어난 곳에서는 사용할 수 없습니다.

 

전역 변수 : 파일 안 가장 바깥에 선언된 변수입니다.

같은 패키지 안에서 접근이 가능합니다.

 

지역 변수 : 특정 코드 블록 안에 있는 변수입니다.

그 코드 블록 안에서만 사용할 수 있습니다.


함수형 프로그래밍에 대해 살펴보았습니다. 다음 글에서는 클래스와 객체에 대해 설명드리겠습니다. 감사합니다. 

 

코틀릭 익히기 5 - 클래스와 객체

자바 쉽게 배우기 6 - 클래스와 객체의 도입부를 자바 > 코틀린으로 고쳐서 인용하겠습니다. 이번글에서는 클래스와 객체에 대해 알아봅시다. 그전에 왜 코틀린에서는 클래스와 객체를 사용하는

devdharu.tistory.com