본문 바로가기

책 요약하기/Do it! 코틀린 프로그래밍

#03-1. 함수와 함수형 프로그래밍 2021-03-17

03-1 함수란?

함수는 여러 값을 입력받아 기능을 수행하고 결괏값을 반환하는 코드의 모음

 

함수 간결하게 선언

fun sum(a: Int, b: Int): Int {
	return a + b
}


fun sum(a: Int, b: Int) = a + b

 

인자? 매개변수?
매개변수와 인자는 같은 역할을 하는 것처럼 보이기 때문에 같은 것으로 착각하기 쉽지만 이 둘은 명확하게 구분할 수 있는 개념입니다. 함수를 선언할 때는 매개변수라고 부르고 함수를 호출할 때는 인자라고 부릅니다. 즉, sum(a: Int, b: Int) 함수의 선언 부분의 a: Int, b: Int는 매개변수이고 main() 함수에서 sum()함수를 호출할 때 sum(3, 2)에서 3, 2는 인자입니다. 이 인자는 함수 선언 부분에 있는 a와 b에 복사되어 전달됩니다.

 

함수의 호출과 메모리, 스택 프레임

fun main(args:Array<String>) { // 최초의 스택 프레임
    val num1 = 10 // 임시 변수 혹은 지역 변수
    val num2 = 3 // 임시 변수 혹은 지역 변수
    val result : Int
    
    result = max(num1, num2)
    println(result)
}

다음과 같이 코드가 작성되면 함수의 각 정보는 프레임(Frame)이라는 정보로 스택(Stack) 메모리의 높은 주소부터 거꾸로 자라듯이 채워져 갑니다.

힙 (낮은주소)
↓
↓
↓
↑
↑
--------------
3	<- b	|
10	<- a	|	max() 함수의 프레임
<지역변수>	|
--------------
↑		
↑           
------------------
?	<- result |
3	<- num2	  |
10	<- num1	  |	main() 함수의 프레임
0	<- args	  |	지역 변수
<지역변수>    |	        항(Operand)스택
------------------	상수풀
스택 (높은주소)

 

그러면 그림의 가장 아래쪽에 있는 main() 함수의 스택 프레임에서 중괄호 안에 있는 지역 변수(args, num1, num2, result)는 첫 번째 스택 프레임에 들어 있습니다. 이후에 main()함수에서 max()함수가 호출되면 새로운 스택 프레임이 만들어집니다. max()함수 스택 프레임에는 a, b의 값인 3, 10이 들어 있습니다. 이 값을 이용하여 더 큰 수를 판단하고 반환하고 종료됩니다. 그러면 max()함수의 스택 프레임이 소멸되고 반환값은 main() 함수 스택 프레임의 result변수에 저장됩니다.

 

스택 프레임의 생성과 소멸

함수가 호출될 때마다 스택 메모리에 쌓이는데 그것을 스택 프레임이라고 부릅니다. 이번에는 생성과 소멸에 대해 이야기 해보겠습니다. 함수가 호출되면 스택에 프레임이 생깁니다. 스택 프레임은 각각 분리되어 있으므로 함수에 선언된 변수도 분리하여 생각합니다. main() 함수와 max() 함수에 선언된 변수 중 같은 이름의 변수가 있어도 두 변수는 다른 프레임에 있기 때문에 프로그램 실행에는 문제가 없습니다. 만약 A() B() C() 메소드 순으로 호출 3개의 프레임이 생성 되었다면 C() B() A() 순으로 소멸됩니다.(Stack FILO : FIrst In Last Out)

스택 오버플로우
프레임은 스택에 생성되고 스택은 메모리의 높은 주소에서 낮은 주소 방향으로 생성됩니다. 힙 영역에는 동적으로 생성된 객체의 정보가 담겨져 있습니다. 힙에서는 보통 낮은 주소에서 높은 주소로 자라가며 정보를 저장합니다. 그래서 두 영역이 만나지 않도록 메모리를 관리하는 것이 중요합니다. 만약 무한하게 함수를 호출하여 스택 프레임을 생성하면 정해진 스택 영역의 경계를 넘어서면서 힙 영역과 겹치게 되는데 바로 이때 스택 오버플로우(Stack Overflow)오류가 발생합니다.

 

반환값이 없는 함수

반환값이 없는(생략된) 함수의 경우 반환 자료형은 Unit입니다. (반환값이 없을 때 사용하는 자료형)

fun printSum(a: Int, b: Int) : Unit {
    println("sum of $a and $b is ${a + b}")
}

fun printSum(a: Int, b: Int){
    println("sum of $a and $b is ${a + b}")
}
Unit와 Void의 차이점
Unit은 자바의 void형과 대응합니다. 하지만 void는 정말로 아무것도 반환하지 않고 Unit은 특수한 객체를 반환한다는 차이점이 있습니다.

 

매개변수 기본값 지정하기

// 이름과 이메일을 추가하는 함수
fun add (name: String, email: String) {

}

// 해당 add() 함수를 호출 시 이메일값은 기본값으로 하는 경우
add.("홍길동", "default")
add.("고길동", "default")
add.("김길동", "default")

// 다음과 같이 호출 마다 email의 값을 default값으로 해주고 싶을때는 번거로움

fun add(name: String, email: String = "default") {
// 이런경우 기본값을 지정할 수 있음 email: String = "default"
}

// add() 함수 호출 시 
add.("홍길동")
add.("고길동")
add.("김길동", "123455@gmail.com")

// 이런식으로 email을 작성하지 않으면 인자를 전달하지 않아도 함수 호출 시 
// 기본값인 default값이 저장됨.

 

 

여러개의 인자를 전달받는 함수

인자의 개수가 변하는 경우 가변 인자를 사용하면 단 하나의 함수로 여러 개의 인자를 받을 수 있습니다.

fun normalVarags(vararg counts: Int) {
    for (num in counts)
        print("$num ")
    println()
}

main(args:Array<String>) {
    normalVarags(1,2,3)
    normalVarags(1,2,3,4,5)
    normalVarags(1)
}

// 실행 결과
1 2 3
1 2 3 4 5
1 

 

03-2 함수형 프로그래밍

코틀린 함수형 프로그래밍(FP: Funtional Programming)과 객체 지향 프로그래밍(OOP: Object-Oriented Programming)을 모두 지원하는 다중 패러다임 언어입니다. 함수형, 객체 지향 프로그래밍의 장점은 코드를 간략하게 만들 수 있다는 것입니다. 두 기법은 대규모 프로그래밍의 설계에도 적합하며 재사용성이나, 개발 생산성이 높기 때문에 공부해야 합니다.

다중 패러다임 언어란?
다중 패러다임 언어란 한 가지 구현 규칙에 얽메이지 않고 다양한 문법과 형식을 지원하는 언어를 말합니다. 특히 현대의 컴퓨터 언어는 다중 패러다임 언어를 지향하며 발전하고 있습니다.

 

함수형 프로그래밍이란?

함수형 프로그래밍은 순수 함수를 작성하여 프로그램의 부작용을 줄이는 프로그래밍 기법입니다. 그리고 함수형 프로그래밍에서는 람다식과 고차 함수를 사용합니다.

 

순수 함수

순수 함수란 무엇일까요? 어떤 함수가 같은 인자에 대하여 항상 같은 결과를 반환하면 '부작용이 없는 함수'라고 말합니다. 그리고 부작용이 없는 함수가 함수 외부의 어떤 상태도 바꾸지 않는다면 순수 함수(Pure Funtion)라고 부릅니다. 이런 특성 ㄷㄱ분에 순수 함수는 스레드에 사용해도 안전하고 코드를 테스트하기도 쉽습니다. (순수 함수는 부작용이 없어 값이 예측이 가능해 '결정적(deterministic)'이라고 하기도 합니다.)

// 순수 함수 예
fun sum(a : Int, b : Int) {
    retrun a + b // 동일한 인자인 a, b를 입력 받아 항상 a + b를 출력(부작용 x)
}

위의 함수와 같이 매개변수 a, b를 이용하여 덧셈 연산을 한 다음 그 값을 그대로 반환합니다. 똑같은 값이 인자로 전달되면 반환값도 항상 같다는 것을 예측할 수 있습니다. 또 함수 안에서 함수 외부의 어떤 변수 상태도 바꾸지 않습니다. 즉, 위 함수는 순수 함수 조건을 만족하는 순수 함수입니다.

순수 함수의 조건
- 같은 인자에 대하여 항상 같은 값을 반환한다.
- 함수 외부의 어떤 상태도 바꾸지 않는다.

 

// 순수 함수가 아닌 함수
fun check() {
    val test = User.grade() // check() 함수에 없는 외부의 User 객체를 사용
    if (test != null) process(test) // 변수 test는 User.grade()의 실행 결과에 따라 달라짐
}

check() 함수는 함수 안에서 함수 외부에 있는  User 객체의 함수인 grade() 함수를 실행하고 있습니다. 또 grade() 함수의 결괏값을 test에 저장하여 조건문 if(test != null)에 사용합니다. 심지어 process() 함수는 조건을 만족하지 못하면 실행하지 않습니다. check()함수만 보면 User가 어떤 객체이고 grade()함수는 어떤 값을 반환하며, process()함수는 어떤 기능을 하는지 알 수 없습니다. 즉, check()함수의 실행 결과를 예측하기 어렵다는 의미입니다. 이런 함수가 순수 함수의 조건을 만족하지 못하는 함수입니다.

 

람다식(Lambda Expressions)

람다식은 람다 대수(Lambda Calculus)에서 유래한 것으로 다음과 같은 형태입니다.

{x, y -> x + y} // 익명 함수(이름이 없는 함수)의 형태
람다 대수란
람다 대수(λ : lambda calculus)는 이론 컴퓨터 과학 및 수리논리학에서 변수의 네임 바인딩과 대입의 방법을 이용하여 함수 정의, 함수 적용, 귀납적 함수 추상화를 수행하고 수학 연산을 표현하는 형식 체계이다. 람다 대수는 임의의 튜링 기계를 시뮬레이션할 수 있는 보편적인 계산 모델이다. (중략)
람다 대수는 계산 이론, 언어학 등에 중요한 역할을 하며, 특히 프로그래밍 언어 이론의 발전에 크게 기여했다. 리스프(LISP)와 같은 함수형 프로그래밍 언어는 람다 대수로부터 직접적인 영향을 받아 탄생했으며, 단순 다입 람다 대수는 현대 프로그래밍 언어의 타입 이론의 기초가 되었다.

 

일급 객체

함수형 프로그래밍에서는 함수를 일급 객체(First Class Citizen)로 생각합니다. 람다식 역시 일급 객체의 특징을 가지고 있습니다.

일급 객체의 특징
- 일급 객체는 함수의 인자로 전달할 수 있다.
- 일급 객체는 함수의 반환값에 사용할 수 있다.
- 일급 객체는 변수에 담을 수 있다.

만약 함수가 일급 객체면 일급 함수라고 부릅니다. 그리고 일급 함수에 이름이 없는 경우 '람다식 함수' 혹은 '람다식'이라고 부를 수 있습니다. 즉, 람다식은 일급 객체의 특징을 가진 이름 없는 함수입니다. 

 

고차 함수

고차 함수(High-order Function)란 다른 함수를 인자로 사용하거나 함수를 결괏값으로 반호나하는 함수를 말합니다. 두 특징을 모두 가지고 있어도 고차 함수라고 합니다. 일급 객체 혹은 일급 함수를 서로 주고받을 수 있는 함수가 고차 함수가 되는 것입니다.

// 고차 함수의 예
fun main(args:Array<String>) {
    println(highFunc({x, y -> x + y }, 10, 20)) // 람다식 함수{x, y -> x + y}를 인자로 넘김
}

fun highFunc(sum: (Int, Int)-> Int, a: Int, b: Int): Int = sum(a, b) // sum 매개변수는 함수

highFunc() 함수는 sum이라는 매개변수가 있습니다. 하지만 이 sum은 람다식 함수 형식으로 선언되어 있습니다. 즉, highFunc() 함수는 sum을 통해서 람다식 함수를 인자로 받아들일 수 있는 고차 함수입니다.

fun highFunc(sum: (Int, Int) -> Int, a: Int, b: Int) : Int {
    return sum(a, b)
}
highFunc : 고차 함수
sum : 람다식 매개변수
(Int, Int) -> Int : 자료형이 람다식으로 선언되어 {(x, y) -> x + y} 형태로 인자를 받는 것이 가능
a, b : 정수형 매개변수
Int :반환 자료형
sum(a, b) 이 함수는 람다식의 표현문에 따라 결국 a + b의 정숫값 결과를 반환
함수형 프로그래밍의 정의와 특징
- 순수 함수를 사용해야 한다.
- 람다식을 사용할 수 있다.
- 고차 함수를 사용할 수 있다.

 

03-3 고차 함수와 람다식

고차 함수의 형태

고차 함수는 인자나 반환값으로 함수를 사용합니다.  먼저 인자나 반환값이 일반 함수인 고차 함수에 대한 예제입니다.

// 인자에 일반 함수를 사용
fun main() {
    val res1 = sum(3, 2) // 일반 인자
    val res2 = mul(sum(3, 3), 3) // 인자에 함수를 사용
    
    println("res1: $res1, res2: $res2")
}

fun sum(a: Int, b: Int) = a + b
fun mul(a: Int, b : Int) = a * b

// 실행 결과
res1: 5, res2: 18
// 반환값에 일반 함수를 사용
fun main(args:Array<String>) {
    println("funcFunc: ${funcFunc(10, 20)}")
}

fun sum(a: Int, b: Int) = a + b

fun funcFunc(a: Int, b: Int) : Int{
    return sum(a, b)
}
// fun funcFunc(a: Int, b: Int) = sum(a, b)

// 실행 결과
funcFunc: 30
// 변수에 할당하는 람다식 함수
fun main(args:Array<String>) {
    var result: Int
    val multi = {x: Int, y: Int -> x * y} // 일반 변수에 람다식 할당
    result = multi(10, 20) // 람다식이 할당된 변수는 함수처럼 사용 가능
    println(result)
}

// 실행 결과
200
val multi : (Int, Int) -> Int = {x: Int, y: Int -> x * y}
val multi : 변수를 함수처럼 사용 가능
(Int, Int) -> Int : 람다식의 자료형을 선언 (람다식의 선언 자료형은 람다식 매개변수에 자료형이 명시된 경우 생략 가능)
x: Int, y: Int : 람다식의 매개변수(Int는선언 자료형이 명시되어 있으면 생략 가능)
x * y : 람다식의 처리 내용(함수의 내용과 결과 반환, 표현식이 여러 줄인 경우 마지막 표현식이 반환)

 

표현식이 2줄 이상인 경우

    val multi2 : (Int, Int) -> Int = {x: Int, y: Int ->
        print("x * y")
        x * y
    }
    println(" = ${multi2(10, 20)}")
    
    // 실행 결과
    x * y = 200

그런데 람다식의 매개변수에 자료형이 지정되어 있다면 변수의 자료형은 생략할 수 있습니다.
즉, 다음은 모두 같은 표현입니다.

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 multi3 = {x, y -> x * y} // 오류

 

반환 자료형이 아예 없거나 매개변수가 하나만 있을 때 표현 방식

val greet: ( ) -> Unit = {println("Hello, Rambda World!")}
val square: (Int) -> Int = {x -> x * x}

// 실행 시 
greet()
println("square(10) : ${square(10)}")

// 실행 결과
Hello Rambda World!
square(10) = 100


// 생략된 표현
val greet = {println("Hello, Rambda World!")} // 자료형 생략(추론 가능)
val square = {x: Int -> x * x} // square 자료형을 생략 시 x의 자료형 명시해야 함

 

람다식 안에 람다식을 넣는 경우

// 람다식 안의 람다식
val nestedLambda: () -> () -> Unit = {{println("nested")}}

// 호출 시 다음과 같이 작성
nestedLambda()()

val nestedLambda2 = nestedLambda()
nestedLambda2()

// 실행 결과
nested
nested

// 생략 코드
val nestedLambda = {{println("nested")}} // 추론 가능

 

매개 변수에 람다식 함수를 이용한 고차 함수

// 변수에 할당하는 람다식 함수
fun main(args:Array<String>) {
    var result: Int
    result = highOrder({x, y -> x + y}, 10, 20)
    println(result)
}

fun highOrder(sum:(Int, Int) -> (Int), a: Int, b: Int) : Int {
    return sum(a, b)
}

// 실행 결과
30
result = highOrder({x, y -> x + y}, 10, 20)
fun highOrder(sum: (Int, Int) -> Int, a: Int, b: Int) : Int {
    return sum(a, b)
}

{x, y -> x + y} : 함수의 인자로 람다식 사용
10, 20 : 정수형 인자
sum: (Int, Int) -> Int : 함수의 매개변수에서 선언한 람다식 자료형
a, b : 매개변수 (10, 20이 각각 a, b에 전달)
return sum(a, b) : 각각 10, 20이 인자로 전달된 a, b를 sum() 람다식에 인자로 사용

 

인자와 반환값이 없는 람다식 함수

fun main() {
    val out: () -> Unit = {println("Hello World!")} // 인자와 반환값이 없는 람다식의 선언
    // 자료형 추론이 가능하므로 val out = {println("Hello World!")와 같이 생략 가능
    
    out() // 함수처럼 사용 가능
    val new = out // 람다식이 들어있는 변수를 다른 변수에 할당
    new()
}

// 실행 결과
Hello World!
Hello World!

이름이 없는 함수를 표현하기 위해 등장한 람다식 표현은 함수형 프로그래밍에서 아주 중요한 개념입니다. 또한 람다식은 많은 코드들을 간략화하고 함수 자체를 인자나 매개변수로 이용할 수 있어 프로그램의 효율성도 높일 수 있습니다.

 

람다식과 고차 함수 호출하기

함수의 내용을 할당하거나 인자 혹은 반환값을 자유롭게 넘기려면 호출 방법을 이해해야 합니다. 기본형 변수로 할당된 값은 스택에 있고 다른 함수에 인자로 전달하는 경우에는 해당 값이 복사되어 전달됩니다. 참조형 변수로 할당된 객체는 참조 주소가 스택에 있고 객체는 힙에 있습니다. 참조형 객체는 함수에 전달할 때는 참조된 주소가 복사되어 전달됩니다.보통 JAVA나 Kotlin은 '참조에 의한 호출'은 없고 '값에 의한 호출'이 일어납니다. 자바는 객체가 전달될 때 주소 자체를 전달하는 것이 아닌 값을 복사하는데 이것은 참조에 의한 호출처럼 보이지만 그 값이 주소일 뿐입니다. 코틀린은 람다식을 사용하면서 몇 가지 확장된 호출 방법을 사용할 수 있습니다.

 

값에 의한 호출

코틀린에서 값에 의한 호출은 함수가 또 다른 함수의 인자로 전달될 경우 람다식 함수는 값으로 처리되어 그 즉시 함수가 수행된 후 값을 전달합니다.

// 변수에 할당하는 람다식 함수
fun main(args:Array<String>) {
    val result = callByValue(lambda())
    println(result)
}

fun callByValue(b: Boolean) : Boolean {
    println("CallByValue function")
    return b
}

val lambda : () -> Boolean = {
    println("lambada function")
    true
}

// 실행 결과
lambada function
CallByValue function
true
1. val result = callByValue(lambda()) : callByValue(lambda())에서 인자로 전달된 lambda()가 먼저 수행됩니다.
2. true : lambda() 람다식에 의해 lambda function이 화면에 출력하고 true를 반환합니다.
3. callByValue(b: Boolean) : 2번의 true값을 callByValue()함수의 b에 값을 복사합니다.
4. return b : b값을 반환합니다.
5. val result = callByValue(lambda()) : result 변수에 할당되어 println(result)으로 true를 출력합니다.

 

이름에 의한 람다식 호출

람다식의 이름이 인자로 전달될 때 실행되지 않고 실제로 호출할 때 실행되도록 하면 다음과 같습니다.

fun main(args:Array<String>) {
    val result = callByName(otherLambda) // 람다식 이름으로 호출
    println(result)
}

fun callByName(b: ( ) -> Boolean): Boolean {
    println("callByName function")
    return b()
}

val otherLambda : ( ) -> Boolean = {
    println("otherLambda function")
    true
}

// 실행 결과
callByName function
otherLambda function
true
callByName() 함수가 callByValue() 함수와 다른 점은 매개변수 b가 람다식 자료형으로 선언되었습니다.
1. otherLambda는 람다식 이름을 callByName() 함수의 인자로 넣습니다. 람다식 자체가 매개변수 b에 복사되어 사용되기 전까지는 람다식이 실행되지 않습니다.
2. b() : return b() 함수에서 람다식이 비로소 실행됩니다. 이것을 잘 활용하면 필요할 때만 람다식이 호출되도록 할 수 있습니다.
3. true : 람다식을 실행하고 true값을 반환합니다.
4. return b() : 3번에서 반환된 true값을 callByName()에서 그대로 반환합니다.
5. val result = callBuName(otherlambda) : result 변수에 true값을 할당합니다.

 

다른 함수의 참조에 의한 일반 함수 호출

// funcParam()메서드 호출 시 sum은 람다식이 아니므로 오류
fun main(args:Array<String>) {
    funcParam(3, 2, sum)
    
}

fun sum(x: Int, y: Int) = x + y

fun funcParam(a: Int, b: Int, c: (Int, Int) -> (Int)) : Int {
    return c(a, b)
}

sum()함수는 람다식이 아니므로 오류가 발생합니다. 하지만 sum()함수와 funcParam() 함수의 매개변수 c의 선언부 구조를 보면 인자 수와 자료형의 개수가 동일합니다. 이때는 2개의 콜론(::)기호를 함수 이름 앞에 사용해 소괄호와 인자를 생략하고 사용할 수 있습니다.

funcParam(3, 2, ::sum)
fun main(args:Array<String>) {
    // 인자와 반환값이 있는 함수
    val res1 = funcParam(3, 2, ::sum)
    println(res1)

    // 인자가 없는 함수
    hello(::text) // 반환값이 없음

    // 일반 변수에 값처럼 할당
    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)) = c(a, b)

fun hello(body: (String, String) -> (String)) : Unit {
    println(body("Hello", "World!"))
}

// 실행 결과
5
Hi! Hello World!
12
  • funcParam(3, 2, ::sum)은 인자 3, 2가 sum에 전달되며(-> c(a, b)) 결과값으로 5를 반환합니다.
  • hello(::text)는 반환값이 없으며 hello()함수 선언부의 body()함수와 함수에 인자로 전달한 2개의 문자열이 hi!와 결합되어 출력합니다.
  • ::sum을 변수에 할당하여 출력할 수도 있습니다.
// (::) 표기법 정리
hello(::text) // 참조 기호 사용
hello({a, b -> text(a, b)) // 람다식 표현(동일)
hello{a, b -> text(a, b)} // 소괄호 생략(동일)

 

람다식의 매개변수

람다식에 매개변수가 없는 경우

fun main(args:Array<String>) {
    // 매개변수가 없는 람다식
    val result : () -> Unit = {println("hello! World")}
    noParam({"Hello! World!"})
    noParam{"Hello! World!"} // 소괄호 생략(동일식)
}

fun noParam(out: () -> String) = println(out())

// 실행 결과
Hello! World!
Hello! World!

 noParam() 함수의 매개변수는 람다식 1개를 가지고 있는데 이때는 함수 사용 시 소괄호를 생략할 수 있습니다.
main() 함수에서 사용된 noParam() 함수의 인자에는 람다식 표현식인 {"..."}형태의 인자가 있습니다. 이 람다식에는 매개변수가 없으므로 화살표(->)기호가 사용되지 않았습니다.  매개변수가 없지만 반환 자료형은 문자열을 반환하고 있습니다. 따라서 println()에 의해 "Hello! World!"가 출력됩니다.

 

람다식의 매개변수가 1개인 경우

fun main(args:Array<String>) {
    // 매개변수가 1개 있는 람다식
    oneParam({a -> "Hellow World $a"})
    oneParam{a -> "Hellow World $a"} // 소괄호 생략 가능(동일식)
    oneParam{"Hellow World $it"} // it으로 대체 가능(동일식)
}

fun oneParam(out: (String) -> String) {
    println(out("OneParam"))
}

// 실행 결과
Hellow World OneParam
Hellow World OneParam
Hellow World OneParam

$it은 람다식 매개변수로 지정된 String형과 매칭되어 "OneParam" 문자열로 바뀌며 최종적으로 "Hello World OneParam"을 출력합니다.

 

람다식의 매개변수가 2개 이상인 경우

fun main(args:Array<String>) {
    // 매개변수가 2개 있는 람다식
    moreParam({a, b -> "Hello World! $a, $b"})
    moreParam{a, b -> "Hello World! $a, $b"} // 소괄호 생략, 매개변수 생략 불가
}

fun moreParam(out: (String, String) -> (String)) {
    println(out("OneParam", "twoParam"))
}

// 실행 결과
Hello World! OneParam, twoParam
Hello World! OneParam, twoParam

2개 이상의 매개변수가 있는 경우 $it을 사용해 변수를 생략 할 수 없습니다.
만일 특정 매개변수를 사용하고 싶지 않을 때는 이름 대신에 언더스코어(_)로 대체 가능합니다.

moreParam{_, b -> "Hello World! $b"} // 첫 번째 문자열은 사용하지 않고 생략

 

일반 매개변수와 람다식 매개변수를 같이 사용하기

fun main(args:Array<String>) {
    // 1. 인자와 함께 람다식을 사용하는 경우
    withArgs("Arg1", "Arg2", {a, b -> "Hello World $a $b"})

    // 2. withArgs() 함수의 마지막 인자가 람다식인 경우 소괄호 분리 가능
    withArgs("Arg1", "Arg2") {a, b -> "Hello World $a $b"}
}

fun withArgs(a: String, b: String, out: (String, String) -> (String)) {
    println(out(a, b))
}

// 실행 결과
Hello World Arg1 Arg2
Hello World Arg1 Arg2

 

일반 함수에 람다식 매개변수를 2개 이상 사용하기

fun main(args:Array<String>) {
    twoLambda({a, b -> "First $a $b"}, {"Second $it"})
    twoLambda({a, b -> "First $a $b"}) {"Second $it"} // 동일식

}

fun twoLambda(first: (String, String) -> String, second: (String) -> (String)) {
    println(first("OneParam", "twoParam"))
    println(second("OneParam"))
}

// 실행 결과
First OneParam twoParam
Second OneParam
First OneParam twoParam
Second OneParam

다음과 같이 twoLambda()함수의 소괄호를 생략할 수 없습니다. 하지만 마지막 인자는 소괄호 밖에 둘 수 있습니다.

({첫 번째}, {두 번째})
({첫 번째}) {두 번째}

람다식 함수가 3개가 되었을 때도 마찬가지입니다.

({첫 번째}, {두 번째}, {세 번째})
({첫 번째}, {두 번째}) {세 번째}

 

이상 마치겠습니다. :)