[부스트코스] [부스트코스][코틀린 프로그래밍 기본 1/2] 한아아지와 함께하는 kotlin - 함수형 프로그래밍 (2) 순수함수와 람다식과 1급 객체 그리고 고차함수
이번 챕터에서는 함수형 프로그래밍 PART 2
저번 포스트에 이어서 함수형 프로그래밍을 공부해보겠습니다.
www.boostcourse.org/mo132/lecture/59974
코틀린 프로그래밍 기본 1
부스트코스 무료 강의
www.boostcourse.org
www.boostcourse.org/mo132/lecture/62509
코틀린 프로그래밍 기본 1
부스트코스 무료 강의
www.boostcourse.org
1. 순수 함수
✍️ 순수 함수란?
어떤 함수가 같은 인자에 대해서 항상 같은 결과를 반환하면 부작용이 없는 함수라고 말합니다.
그리고 부작용이 없는 함수가 함수 외부의 어떤 상태도 바꾸지 않는다면 순수 함수이며
즉 순수 함수는 부작용이 없어 값이 예측 가능해 결정적이라고도 부릅니다.
→ 순수 함수는 스레드에 사용해도 안전하고 코드의 테스트가 쉽다는 장점이 있습니다.
순수 함수의 조건
❗ 같은 인자에 대하여 항상 같은 값을 반환합니다.
❗ 함수 외부의 어떤 상태도 바꾸지 않습니다.
// 순수 함수의 예시 1
fun sum(a: Int, b: Int): Int {
return a + b // 동일한 인자인 a, b를 입력 받아 항상 a + b를 출력(부작용이 없음)
}
// 순수 함수가 아닌 것
fun check() {
val test = User.grade() // check() 함수에 없는 외부의 User 객체 사용
if (test != null) process(test) // 변수 test는 User.grade()의 실행 결과에 따라 달라짐
}
const val global = 10
fun main() {
val num1 = 10
val num2 = 3
val result = noPureFunction(num1, num2)
println(result)
}
fun noPureFunction(a: Int, b: Int): Int {
return a + b + global // 입력 값과 무관하게 외부 변수 사용
}
✍️ 순수 함수 왜 사용 할까?
- 입력과 내용을 분리하고 모듈화 하므로 재사용성이 높아진다. (여러 함수들과 조합해도 부작용이 없음)
- 특정 상태에 영향을 주지 않으므로 병행 작업 시 안전하다.
- 함수의 값을 추적하고 예측 할 수 있기 때문에 테스트, 디버깅 등이 유리하다.
2. 람다식
✍🏻 람다란?
람다는 자바 8 부터 제공되는 익명 함수 (Anonymous Functions)를 지칭하는 용어입니다.
람다식은 코드의 간결함을 주 목적으로 이용되고 고차 함수의 매개변수나 반환 값으로 자주 사용됩니다.
✍🏻 람다식의 표현식은 아래와 같습니다. (함수의 이름이 없고 화살표가 사용되었습니다)
{ x, y -> x + y } // { 매개변수 -> 함수내용}
// 람다식 선언과 할당
fun main() {
var result: Int
// 일반 변수에 람다식 할당
val multi = {x: Int, y: Int -> x * y}
// 람다식이 할당된 변수는 함수처럼 사용 가능
result = multi(10, 20)
println(result)
}
// 변수의 자료형 생략
val multi: (Int, Int) -> Int = {x: Int, y: Int -> x * y} // 생략되지 않은 전체 표현
val multi = {x: Int, y: Int -> x * y} // 선언 자료형 생략
val multi: (Int, Int) -> Int = {x, y -> x * y} // 람다식 매개변수 자료형의 생략
// 함수의 매개변수가 없을 때
val greet: ()->Unit = { println("Hello World!") }
val square: (Int)->Int = { x -> x * x }
✍🏻 람다 함수는 { } 안에 매개변수와 함수 내용을 선언하는 함수로 다음 규칙에 따라 정의합니다.
- 람다 함수는 항상 { }으로 감싸서 표현해야 한다.
- { } 안에 -> 표시가 있으며 -> 왼쪽은 매개변수, 오른쪽은 함수 내용이다.
- 매개변수 타입을 선언해야 하며 추론할 수 있을 때는 생략할 수 있다.
- 함수의 반환값은 함수 내용의 마지막 표현식이다.
✍🏻 람다식 선언 방법
🟡 람다식 함수에 매개변수가 없으면 사용시 '->' 가 사용되지 않습니다. 그리고 소괄호는 생략할 수 있습니다.
fun main() {
// 매개변수 없는 람다식 함수
noParam({ "Hello World!" })
noParam { "Hello World!" } // 위와 동일 결과, 소괄호 생략 가능
}
// 매개변수가 없는 람다식 함수가 noParam 함수의 매개변수 out으로 지정됨
fun noParam(out: () -> String) = println(out())
🟡 람다식 함수에 매개변수가 한 개 있을 경우에는 람다식에 화살표 기호(->) 왼쪽에 필요한 변수를 써 줘야 합니다.
하지만 이렇게 매개변수가 한 개인 경우에는 화살표 표기를 생략하고 it으로 대체할 수 있습니다.
fun main() {
// 매개변수 없는 람다식 함수
...
// 매개변수가 하나 있는 람다식 함수
oneParam({ a -> "Hello World! $a" })
oneParam { a -> "Hello World! $a" } // 위와 동일 결과, 소괄호 생략 가능
oneParam { "Hello World! $it" } // 위와 동일 결과, it으로 대체 가능
}
...
// 매개변수가 하나 있는 람다식 함수가 oneParam함수의 매개변수 out으로 지정됨
fun oneParam(out: (String) -> String) {
println(out("OneParam"))
}
🟡 매개변수가 두개일 때는 it을 사용해 변수를 생략할 수 없습니다.
모두 나타내야 하지만 만일 필요한 경우 _(언더바)를 사용해 특정 매개변수를 생략할 수 있습니다.
fun main() {
...
// 매개변수가 두 개 있는 람다식 함수
moreParam { a, b -> "Hello World! $a $b"} // 매개변수명 생략 불가
...
}
// 매개변수가 두 개 있는 람다식 함수가 moreParam 함수의 매개변수로 지정됨
fun moreParam(out: (String, String) -> String) {
println(out("OneParam", "TwoParam"))
}
3. 1급 객체
✍🏿 아래 3 가지조건을 충족한다면 1급 객체라고 할수 있습니다.
No | 조건 |
1 | 함수의 인자로 전달할 수 있어야 한다. |
2 | 함수의 반환 값에 사용될 수 있어야 한다. |
3 | 변수에 담을 수 있어야 한다. |
✍🏿 코틀린 함수는 1급 객체지만 자바 함수는 1급 객체가 아닌 이유 [변수나 데이타에 할당 할 수 있어야 한다.]
/* 코틀린 함수와 자바 함수 비교 */
object Main {
@JvmStatic
fun main(args: Array<String>) {
val a = test
}
val test: () -> Unit = { println("kotlin") }
}
public class java {
public static void test(){
System.out.println("java");
}
public static void main(String[] args) {
System.out.println("java");
// Object a = test;
}
}
// kotlin은 a 에 type이 () -> Unit 인 test 함수 할당이 가능하지만, Java는 불가능
4. 고차 함수
✍🏽 고차 함수란?
다른 함수를 인자로 사용하거나 함수를 결과 값으로 반환하는 함수
즉 일급 객체 혹은 일급 함수를 서로 주고 받을 수 있는 함수가 고차함수
// 고차 함수 예시 1
fun main() {
println(highFunc({ x, y -> x + y }, 10, 20)) // 람다식 함수를 인자로 넘김
}
fun highFunc(sum: (Int, Int) -> Int, a: Int, b: Int): Int = sum(a, b) // sum 매개변수는 함수
// 고차 함수 예시 2
fun highFunc2 (x1: Int, argFun: (Int) -> Int) { // 함수 타입 선언 : argFun: (Int) -> Int
val result = argFun(10)
println("x1 : $x1, someFun1 : $result")
}
highFunc2(10, {x -> x * x }) // 함수 대입 : {x -> x * x }
✍🏽 고차 함수 호출
🔸 값에 의한 호출은 함수가 인자로 전달될 경우 람다식 함수는 값으로 처리되어 그 즉시 함수가 수행된 후 값을 전달
fun main() {
val result = callByValue(lambda()) // 람다식 함수를 호출
println(result)
}
fun callByValue(b: Boolean): Boolean { // 일반 변수 자료형으로 선언된 매개변수
println("callByValue function")
return b
}
val lambda: () -> Boolean = { // 람다 표현식이 두 줄이다
println("lambda function")
true // 마지막 표현식 문장의 결과가 반환
}
🔸 람다식 함수의 이름을 인자로 넣어 사용하는 경우에는 람다식 자체가 매개변수에 복사되고
해당 함수가 호출되어 사용되기 전까지는 람다식 함수가 실행되지 않습니다. 함수가 호출될 때 함수 내용이 실행됨
fun main() {
val result = callByName(otherLambda) // 람다식 이름으로 호출
println(result)
}
fun callByName(b: () -> Boolean): Boolean { // 람다식 함수 자료형으로 선언된 매개변수
println("callByName function")
return b()
}
val otherLambda: () -> Boolean = {
println("otherLambda function")
true
}
🔸 두 개의 콜론기호(::)를 함수 이름 앞에 사용해 소괄호와 인자를 생략하고 사용하는 경우
일반 함수를 참조에 의한 호출로 사용할 수 있게 됩니다.
fun main() {
// 1. 인자와 반환값이 있는 함수
val res1 = funcParam(3, 2, ::sum)
println(res1)
// 2. 인자가 없는 함수
hello(::text) // 반환값이 없음
// 3. 일반 변수에 값처럼 할당
val likeLambda = ::sum
println(likeLambda(6,6))
}
fun sum(a: Int, b: Int) = a + b
fun text(a: String, b: String) = "Hi! $a $b"
fun funcParam(a: Int, b: Int, c: (Int, Int) -> Int): Int {
return c(a, b)
}
fun hello(body: (String, String) -> String): Unit {
println(body("Hello", "World"))
}