본문 바로가기
대외활동/Naver Boostcourse

[부스트코스] 코틀린 프로그래밍 기본 1/2(함수편) - 함수의 기본, 함수형 프로그래밍

by 드인 2021. 1. 26.

코틀린 프로그래밍 기본 1/2 (함수편)

 


0. 오리엔테이션

1. 코틀린의 기본의 기본을 읽혀요!

- 코틀린이란 무엇일까?

 - 변수와 자료형, 연산자

2. 함수형 프로그래밍 이란?

 - 마법의 요술상자, 함수의 기본

 - 요술상자, 함수 가지고 놀기

3. 프로그램 흐름의 제어

 - 프로그램의 흐름을 제어해보자!

4. 코틀린의 표준함수 활용하기

 - 코틀린과 표준함수

5. 강좌 마무리 프로젝트

▶ 깃허브 코드 : github.com/0525hhgus/Kotlin-study

 

0525hhgus/Kotlin-study

Kotlin-study. Contribute to 0525hhgus/Kotlin-study development by creating an account on GitHub.

github.com


[ 함수의 기본 ]


1. 함수를 선언하고 호출해 보기

1) 함수의 선언

package chap03.section1

fun sum(a: Int, b: Int): Int{
	var sum = a + b
    return sum
}
fun 함수 이름([변수 이름: 자료형, 변수 이름: 자료형..]  ): [반환값의 자료형] { 
    표현식...
    [return 반환값] 
}

 

2) 함수의 간략화된 표현

▶ 일반적인 함수의 모습

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

 

▶ 간략화된 함수

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

 

▶ 반환 자료형 생략

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

 

3) 실습 : sum() 함수를 만들고 테스트하기

package chap03.section1

fun main() { // 최상위 (Top-leve) 함수
    var result1 = sum(2,3)
    println(result1)
}

fun sum(a: Int, b: Int): Int {
    return a + b
} // 최상위 함수

▶ 반환값이 없으면 반환하지 않는 Unit으로 정의됨

▶ 최상위 함수의 특징

- sum이라는 이름은 main의 위 혹은 아래에 두더라도 해당 이름을 main 안에서 사용할 수 있음

 

package chap03.section1

fun main() { // 최상위 (Top-leve) 함수

    fun sum(a: Int, b: Int): Int {
        return a + b
    } // 지역 함수
    
    var result1 = sum(2,3)
    println(result1)
}

▶ 지역 함수의 특징

- sum이라는 함수가 main 안쪽에서 사용된 위치보다 아래에 존재할 경우, 사용할 수 없음

 

 

🤔 생각해보세요.

 정수형 인자의 갯수에 상관없이 받아들인 모든 인자를 모두 더해서 결과를 반환하는 함수를 생각해볼까요?

Q. 정수형 인자의 갯수에 상관없이 받아들인 모든 인자를 모두 더해서 결과를 반환하는 함수

A1. 지역 함수 사용

package chap03.section1

fun main() {

    fun sum(vararg a: Int): Int{
        return a.sum()
    } // 지역 함수
    
    print(sum(10,20,30,40,50,60,70,80,90,100)) // sum : 550
}

A2. 최상위 함수 사용

 

package chap03.section1

fun sum(vararg a: Int): Int{
    return a.sum()
} // 최상위 (Top-leve) 함수

fun main() {

    print(sum(10,20,30,40,50,60,70,80,90,100)) // sum : 550
}

- 가변 인자인 vararg에 대한 설명은 아래 챕터에서 확인할 수 있습니다.

 

2) 실습 : 판단문이 포함된 max() 함수 만들어보기

package chap03.section1

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

fun max(a: Int, b: Int): Int {
    return if (a > b) a else b
}

fun main() { // 최상위 (Top-leve) 함수

    var result1 = sum(2,3)

    val a = 3
    val b = 5

    val result2 = max(a, b)

    println(result1)
    println(result2)

}

 

2) 실습 : 반환값이 없는 Unit 형식, 함수 간략화하기

package chap03.section1

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

fun max(a: Int, b: Int) = if (a > b) a else b

fun outfunc(name: String) = println("Name: $name")


fun main() { // 최상위 (Top-leve) 함수

    var result1 = sum(2,3)

    val a = 3
    val b = 5

    val result2 = max(a, b)

    println("Kotlin")
    println(result1)
    println(result2)

}

3) 실습 : 함수에 기본값 넣기 및 인자 이름지정

package chap03.section1

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

fun max(a: Int, b: Int) = if (a > b) a else b

fun outfunc(name: String) = println("Name: $name")


fun main() { // 최상위 (Top-leve) 함수

    val result1 = sum(2,3)
    val result3 = sum(2)

    val a = 3
    val b = 5

    val result2 = max(a, b)

    println("Kotlin")
    println(result1)
    println(result2)
    println(result3)
}
package chap03.section1

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

fun max(a: Int, b: Int) = if (a > b) a else b

fun outfunc(name: String) = println("Name: $name")


fun main() { // 최상위 (Top-leve) 함수

    val result1 = sum(2,3)
    val result3 = sum(b = 2)

    val a = 3
    val b = 5

    val result2 = max(a, b)

    println("Kotlin")
    println(result1)
    println(result2)
    println(result3)
}

 

4) 실습 : 가변인자 다루기

package chap03.section1

fun normalVarargs(vararg a: Int) {
    for (num in a) {
        print("$num ")
    }
}

fun main() {
    normalVarargs(1)
    println()
    normalVarargs(1, 2, 3, 4)
}

 

5) 요약 : 함수의 호출 원리 이해하기

낮은 주소

machine code 

 

 

 

 

 




[ max() 함수의 프레임 ]
a

[ main() 함수의 프레임 ]
: 지역 변수들, 항, 스택, 상수 등
result
num2
num1
args











10


?
3
10
0

globals
heap

















stack

높은 주소

 

🤔 생각해보세요.

 코틀린은 따로 클래스를 만들지 않아도 함수를 생성할 수 있었습니다. main()처럼 클래스 멤버가 아닌 파일에서 직접 작성되는 함수는 최상위(top-level) 함수로 이름을 어디서든 참조할 수 있게 됩니다.
이런 함수는 메모리에서 고정적으로 존재하기 때문입니다. 반면에 지역(local) 함수는 특정 블록 내에서만 그 생명주기를 갖고 있기 때문에 블록을 벗어나면 해당 함수는 존재할 수 없게 됩니다. 이때는 블록내에서 반드시 먼저 함수 선언을 가지고 있어야만 해당 이름의 함수를 이후에 사용할 수 있게 되죠. 그렇다면 함수 뿐만 아니라 함수 내부에서 선언된 변수들도 임시 메모리인 스택에 쌓이고 일정 생명주기를 가지게 됩니다. 이러한 생명주기가 필요한 이유가 무엇일까요?

Q. 생명주기가 필요한 이유는 무엇일까요?

A. 메모리를 효율적으로 관리하는 것을 목표로,

    불필요한 지역 변수를 스택에서 제거하여 성능 향상을 가져올 수 있습니다.

 

2. 함수를 활용한 예제

예제: 평균 구하기

 목표: 하나 이상의 실수를 받아서 모든 실수의 합의 평균을 구하고 출력 하려고 합니다.

다음 조건을 만족하는 함수를 구현해 보세요!

  • 초기값을 위한 인자는 Float형

  • 초기값은 두번째 부터 나열된 인자의 최종 평균 결과에 더함

  • 초기값에 아무런 인자를 입력하지 않으면 0을 기본 사용

  • 두번째부터 받아들일 인자는 가변형 인자로 모두 실수형 Float

  • 반환값은 모든 인자의 평균값으로 마찬가지로 실수형 Double

fun avgFunc(initial: ____(1)______, _____(2)_____ numbers: Float): Double {
    var result = 0f
    for (num in numbers) {
        ________(3)_________
    }
    println("result: $result, numbers.size: ${numbers.size}")
    val avg = __________(4)____________
    return ________(5)_________
}

fun main() {
    val result = avgFunc(5f, 100f, 90f, 80f)  // 첫번째 인자: 초기값, 이후 인자는 가변인자
    println("avg result: $result")
}
package chap03.section1

fun avgFunc(initial: Float = 0f, vararg numbers: Float): Double { // 초기값 0, vararg 사용
    var result = 0f
    for (num in numbers) {
        result += num // 가변 인자로 받은 값들을 모두 더함
    }
    println("result: $result, numbers.size: ${numbers.size}")
    val avg = (result/numbers.size).toDouble() // 평균을 구한 후, 출력값에 따라 Double형으로 변환 
            return avg
}

fun main() {
    val result = avgFunc(5f, 100f, 90f, 80f)  // 첫번째 인자: 초기값, 이후 인자는 가변인자
    println("avg result: $result")
}

 

3. 함수형 프로그래밍 패러다임

1) 함수형 프로그래밍이란

▶ 코틀린은 다중 패러다임 언어

- 함수형 프로그래밍(FP: Functional Programming)

- 객체 지향 프로그래밍(OOP: Object-Oriented Programming)

 

▶ 함수형 프로그래밍

- 코드 간략, 테스트나 재사용성 증가

- 람다식, 고차 함수를 사용해 구성

- 순수 함수

 

2) 순수 함수의 개념

▶ 순수 함수(pure function)의 이해

- 부작용(side-effect)이 없는 함수

  • 동일한 입력 인자에 대해서는 항상 같은 결과를 출력 혹은 반환함

  • 값이 예측이 가능해 결정적(deterministic)임

// 순수 함수의 예
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 // 입력값과 무관하게 외부의 변수 사용
}

 

▶ 순수 함수를 사용하는 이유

- 입력과 내용을 분리하고 모듈화하므로 재사용성이 높아짐

  • 여러가지 함수들과 조합해도 부작용이 없음 

- 특정 상태에 영향을 주지 않으므로 병행 작업 시 안전함

- 함수의 값을 추적하고 예측할 수 있기 때문에 테스트, 디버깅 등이 유리함

 

▶ 함수형 프로그래밍에 적용

- 함수를 매개변수, 인자에 혹은 반환값에 적용 (고차 함수)

- 함수를 변수나 데이터 구조에 저장

- 유연성 증가

 

3) 람다식의 개념

▶ 람다식(Lambda Expression)이란?

- 익명 함수의 하나의 형태로 이름 없이 사용 및 실행이 가능

- 람다 대수(Lambda calculus)로부터 유래

{ x, y -> x + y } // 람다식의 예 (이름이 없는 함수 형태)

 

▶ 람다식의 이용

- 람다식은 고차 함수에서 인자로 넘기거나 결과값으로 반환 등을 할 수 있음

 

4) 일급 객체란

▶ 일급 객체(First Class Citizen)란?

- 일급 객체는 함수의 인자로 전달할 수 있음

- 일급 객체는 함수의 반환값에 사용할 수 있음

- 일급 객체는 변수에 담을 수 있음

 

▶ 코틀린에서 함수는 1급 객체로 다룸

- 1급 함수라고도 함

 

5) 고차 함수란

▶ 고차 함수(high-order function)란?

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 매개변수는 함수  

                                                                        함수는 람다식 표현문으로 a+b 정수값 결과 반환

fun highFunc ( sum(Int, Int) -> IntaIntbInt): Int = sum(a, b)

     고차함수                                                              반환 자료형   

             람다식 매개 변수             정수형 매개변수

                            자료형이 람다식으로 선언되어 { x, y -> x + y } 형태로 인자를 받는 것이 가능

 

🤔 생각해보세요.

 이번 강의에서는 다양한 용어가 나왔습니다. 순수 함수, 람다식, 일급 객체, 고차 함수.. 이름만 들어봐도 어려운것 같습니다. 하지만 일단 개념을 잘 정리해 두면 향후의 내용이 점점 이해될 것입니다. 람다식처럼 얼굴만 여러개로 바뀔 뿐 결국 우리가 이용하고 있는것은 함수라는 것을 잊지마세요!

그렇다면 이름이 없는 함수에 대해 좀 더 생각해 볼까요? 왜 이런 개념이 필요하게 되었을까요?

Q. 이름 없는 함수의 개념이 필요하게 된 이유

A. 지역 함수를 간단하게 구현할 수 있으며, 사용하는 코드가 줄어들고 가독성이 좋아지는 장점이 있기 때문에 필요하다고 생각됩니다.

 

6) 실습 : 고차 함수와 람다식의 간단한 예

package chap03.section3

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

fun main() {
    val result = highFunc({x, y -> x + y}, 1, 3)
    println(result)
}
package chap03.section3

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

fun main() {
    val result = highFunc(1, 3) {x, y ->
        x + y
    }
    println(result)
}

- 함수의 마지막 매개변수로 람다식 사용 시 위와 같이 표현 가능 

 

7) 함수형 프로그래밍의 개념 요약

▶ 함수형 프로그래밍 사용 이유

- 프로그램을 모듈화해 디버깅하거나 테스트가 쉬움

- 간략한 표현식을 사용해 생산성이 높음

- 람다식과 고차함수를 사용하면서 다양한 함수 조합을 사용할 수 있음

 

▶ 정리

- 함수형 프로그래밍은 순수 함수를 조합해 상태 데이터 변경이나 부작용이 없는 루틴을 만들어 내며 이름 없는 함수 형태의 하나인 람다식을 이용해 함수를 변수처럼 매개변수, 인자, 반환값 등에 활용하는 고차 함수를 구성해 생산성을 높인 프로그래밍 방법

 

 


서포터즈 네임택


감사합니다!