Post

[Kotlin] 3. 함수와 함수형 프로그래밍

※ “Do it! 코틀린 프로그래밍” 을 참고하여 작성하였습니다.

03-1 함수 선언하고 호출하기

  • 함수의 구조

    [형식]

    fun sum(a: Int, b: Int): Int{

    var sum= a+b

    return sum

    }

    fun 함수 이름(변수 이름: 자료형, 변수 이름: 자료형, …): 반환 자료형 {

    표현식

    return 반환값

    }

    • 인수와 매개변수의 차이
      • 인수: 함수를 호출할 때 필요한 변수
      • 매개변수: 함수를 선언할 때 참조할 변수
  • 반환값이 없는 함수

    • Unit: return 문이 없을 때 반환하는 특수한 객체의 자료형
      • 반환 자료형이 없으면 Unit이 생략된 것
    • void: 아무 것도 반환하지 않음
  • 매개변수 제대로 활용하기

    • 함수의 매개변수에 기본값 지정하기

    • 매개변수 이름과 함께 함수 호출하기

    • 매개변수의 개수가 고정되지 않은 함수 사용하기

      • 가변 인자 사용: 하나의 함수에서 여러 종류의 인자를 사용할 수 있음

      ex)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      
      fun main(){
      		normalVarargs(1, 2, 3, 4)
      		normalVarargs(4, 5, 6)
      }
          
      fun normalVarargs(vararg counts: Int){
      		for (num in counts){
      				print("$num ")
      		}
      		print("\\n")
      }
      

03-2 함수형 프로그래밍

  • 함수형 프로그래밍: 순수 함수를 작성하여 프로그램의 부작용을 줄이는 프로그래밍 기법

  • 순수 함수

    • 같은 인자에 대해서 항상 같은 결과를 반환하고,

    • 함수 외부의 어떤 상태도 바꾸지 않는 함수

    • 예시

      1
      2
      3
      
      fun sum(a: Int, b: Int): Int{
      		return a+b
      }
      
  • 람다 함수: 이름이 없는 함수, 2개 이상의 입력을 1개의 출력으로 단순화

    [람다식]

    • 다른 함수의 인자로 넘기는 함수

    • 함수의 결과값으로 반환하는 함수

    • 변수에 저장하는 함수

      1
      2
      3
      4
      5
      6
      
      val multi= {x: Int, y: Int -> x*y}
      val multi: (Int, Int) -> Int= {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}
      val nestedLambda: ( ) -> ( ) -> Unit= {{ println("nested") }}
      
  • 일급 객체

    • 일급 객체의 특징
      • 일급 객체는 함수의 인자로 전달할 수 있다.
      • 일급 객체는 함수의 반환값에 사용할 수 있다.
      • 일급 객체는 변수에 담을 수 있다.
    • 일급 함수: 함수가 일급 객체
      • 일급 함수에 이름이 없는 경우: ‘람다식 함수’, ‘람다식’
  • 고차 함수

    다른 함수를 인자로 사용하거나, 함수를 결과값으로 반환하는 함수

    • 다른 함수를 인자로 사용하는 경우

      1
      2
      
      fun main(){
      		println(highFunc({x, y -> x+y}, 10, 20))
      
    • 함수를 결과값으로 반환하는 함수

      1
      
      fun highFunc(sum: (Int, Int) -> Int, a: Int, b:Int): Int= sum(a+b)
      
  • 함수형 프로그래밍의 정의와 특징

    • 순수 함수를 사용해야 한다.
    • 람다식을 사용할 수 있다.
    • 고차 함수를 사용할 수 있다.

03-3 고차 함수와 람다식

  • 고차 함수의 형태

    • 일반 함수를 인자나 반환값으로 사용하는 고차 함수

      • 인자에 일반 함수 사용해보기: mul(sum(3, 3), 3)

      • 반환값에 일반 함수 사용해보기

        1
        2
        3
        
        fun funcFunc(): Int{
        		return sum(2, 2)
        }
        
    • 람다식을 인자나 반환값으로 사용하는 고차 함수

      1
      2
      3
      4
      5
      6
      7
      8
      
      fun main(){
      		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)
      }
      
    • 인자와 반환값이 없는 람다식 함수

      1
      2
      3
      4
      5
      6
      7
      
      fun main(){
      		val out: ( ) -> Unit= { println("Hello World!") }
          		
      		out() // Hello World!
      		val new= out
      		new() // Hello World!
      }
      
  • 람다식과 고차 함수 호출하기

    • 값에 의한 호출

      • 람다식 함수()가 다른 함수의 인자로 전달될 경우,
      • 람다식 함수는 값으로 처리되어 그 즉시 함수가 수행된 후 값을 전달
    • 이름에 의한 람다식 호출

      • 함수() 가 아닌 이름만이 다른 함수의 인자로 전달될 경우,
      • 다른 함수를 실행하고 return 때 람다식 함수를 호출하여 실행
    • 다른 함수의 참조에 의한 일반 함수 호출

      • 일반 함수를 다른 함수(고차 함수)에 사용하는 경우- ::(2개의 콜론)을 사용

      • 모두 동일한 표기법

        1
        2
        3
        
        hello(::text)
        hello({ a, b -> text(a, b) })
        hello { a, b -> text(a, b) }
        
  • 람다식의 매개변수

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

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

      • 1개의 파라미터는 it으로 대체 가능
      1
      2
      3
      4
      5
      6
      7
      8
      
      fun main(){
          oneParam( { a -> "Hello World! $a" })
          oneParam { "Hello World! $it" }
      }
          
      fun oneParam(out: (String) -> String){
          println(out("One Param"))
      }
      
    • 람다식에 매개변수가 2개 이상인 경우

      • 2개 이상의 변수들을 다 사용하고 싶지 않으면 _(언더 스코어) 활용
    • 일반 매개변수와 람다식 매개변수를 같이 사용하기

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

      • 람다식이 2개이면: 고차함수({첫 번째 람다식}) {두 번째 람다식} 으로 호출 가능

      • 람다식이 3개이면: 고차함수({첫 번째 람다식}, {두 번째 람다식}) {세 번째 람다식} 으로 호출 가능

        ⇒ 4~ 도 동일한 방식으로 호출

03-4 고차 함수와 람다식의 사례 알아보기

  • 동기화를 위한 코드 구현 구경하기

    • ReentrantLock의 객체를 람다식의 함수에 포함시켜서,
    • .lock()로 잠그고 임계 영역에 접근
    • finally에서 .unlock()으로 다시 잠금을 unlock
  • 네트워크 호출 구현 구경하기

    네트워크로부터 무언가를 호출하고, 호출 결과에 따라 특정 콜백함수를 처리

    • 콜백 함수: 이벤트가 발생하면 콜백 함수를 호출하여 처리
    • 함수 선언 시 try-catch로 네트워크의 성공과 실패를 호출
    • 함수 호출 시 인자에 람다식의 구현을 넣기

03-5 코틀린의 다양한 함수 알아보기

  • 익명함수: 이름이 없는 일반 함수

    • 함수 선언 및 호출

      1
      2
      3
      4
      
      val add: (Int, Int) -> Int= fun(x, y)= x + y
      val add= fun(x: Int, y: Int)= x+y
      val add= { x: Int, y: Int -> x+y }
      val result= add(10, 2)
      
    • 람다식에서는 return을 사용하기 어려움,

      ⇒ return 사용은 4장에서 ~!!

  • 인라인 함수

    인라인 함수가 호출되는 곳에 함수 본문의 내용을 모두 복사해서 붙여넣게 됨

    [형식] inline fun 함수(매개변수): 반환 자료형 {

    }

    • 인라인 함수 제한하기

      • 인라인 함수 안에 매개변수가 또 인라인 함수인 경우 성능 경고를 할 수 있음

      • 매개변수들이 함수인 경우, noinline 키워드로 함수를 인라인되지 않게 하기

        ⇒ 해당 함수는 복사 없이 사용하게 됨

    • 인라인 함수와 비지역 반환

      • 인라인 함수에서의 람다식은 return이 가능!!
      • 비지역 반환: 호출된 인라인 함수에서의 return으로 바깥 함수가 “반환” 되는 경우
    • crossinline으로 비지역 반환 금지하기

      • 인라인 함수에서의 람다식 내에서 return을 사용하지 않기
      • 그대신, 인라인 함수 선언에서의 람다식 앞에 crossinline 키워드 붙이기

      ⇒ crossinline: 비지역 반환을 금지해야 하는 람다식에 사용

  • 확장 함수

    • 클래스에 함수를 추가할 수 있는 함수

    [형식]

    fun 확장대상.함수 이름(매개변수, …): 반환 값{

    return 값

    }

    [호출] 확장대상.함수 이름(매개변수, …)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    fun main() {
        val a= 3
        val b= 10
        println(a.getMultiply(b))
    }
      
    fun Int.getMultiply(x: Int): Int {
        return this * x
    }
    
    • 특징
      • 기존 클래스의 선언 구현부를 수정하지 않고 외부에서 손쉽게 기능을 확장할 수 있음
      • 클래스 내의 메소드 이름과 확장 함수의 이름이 같다면, 멤버 메소드 먼저 호출
  • 중위 함수

    일종의 연산자를 구현할 수 있는 함수, infix 키워드 사용

    • 중위 함수의 조건

      • 멤버 메소드 또는 확장 함수여야 함
      • 하나의 매개변수를 가져야 함
      • infix 키워드를 사용하여 정의
    • 위의 확장 함수보다 더 직관적으로 표현

      1
      2
      3
      4
      5
      6
      7
      8
      
      fun main(){
          val multi=3 multiply 10
          println("multi: $multi")
      }
          
      infix fun Int.multiply(x:Int):Int{
          return this*x
      }
      
  • 꼬리 재귀 함수

    • 재귀 함수의 조건

      • 무한 호출에 빠지지 않도록 탈출 조건을 만들어야 함
      • 스택 영역을 이용하므로 호출 횟수를 무리하게 많이 지정해서 연산하면 안됨
      • 코드는 간단해야 함
    • 꼬리 재귀 함수

      • tailrec 키워드 사용
      • return에 다시 꼬리 재귀 함수의 인자를 알맞게 넣음
      1
      2
      3
      4
      5
      6
      7
      8
      
      fun main(){
          val number=5
          println("Factorial: $number -> ${factorial(number)}")
      }
          
      tailrec fun factorial(n:Int, run:Int=1):Long{
          return if(n==1) run.toLong() else factorial(n-1,run*n)
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
      fun main(){
          val n= 100
          val first= BigInteger("0")
          val second= BigInteger("1")
          println(fibonacci(n, first, second))
      }
          
      tailrec fun fibonacci(n: Int, a: BigInteger, b: BigInteger): BigInteger{
          return if (n==0) a else fibonacci(n-1, b, a+b)
      }
      

03-6 함수와 변수의 범위

  • 함수의 범위

    • 최상위 함수(사용자 함수): 선언부의 위치에 상관 없이 사용
    • 지역 함수: 선언부가 먼저 나와야 사용 가능
    • 지역 함수는 {} 블록을 벗어나면 사용하지 못함
      • 유도리있게 같은 범위 내에서 사용하기
  • 변수의 범위

    • 지역 변수: 특정 코드 블록 안에 있는 변수
    • 전역 변수: 최상위에 있는 변수로 프로그램이 실행되는 동안 삭제되지 않고 메모리에 유지됨

    ⇒ 변수도 마찬가지로 지역 변수면 같은 범위 내에서 변수를 사용하기

    단, 지역 변수 허용 범위에서 전역 변수와 이름이 동일한 지역 변수가 존재한다면

    지역 변수가 우선시됨

This post is licensed under CC BY 4.0 by the author.