조각들

 

순수함수(pure function)의 조건

  • 동일한 인자로 실행하면 항상 동일한 값을 반환한다. (지역변수만으로 로직을 처리하고 결과를 반환한다.)
  • 함수 내부에서 반환값 이외의 결과로 부수효과가 발생하지 않는다.

 

부수효과(side effect)

함수가 실행되는 과정에서 함수 외부의 data를 사용 및 수정하거나 외부의 다른 기능을 사용하는 것을 말한다.

  • 함수가 전역변수(global variable)를 사용하거나 수정하는 것이다.
  • 함수가 표준 입출력(ex. println)을 사용해서 키보드 입력과 화면 등에 출력한다.

 

Q. 여러 곳에서 하나의 전역변수를 공유하게 하는 게 편하던데 순수함수를 지향해야 하는 것인지 궁금합니다.

 

 

일급 객체 함수

함수나 정수도 문자열처럼 객체로 사용할 수 있는 것을 말한다.

  • 함수를 변수에 할당할 수 있다.
  • 함수를 매개변수의 인자로 전달할 수 있다.
  • 함수를 반환값으로 사용할 수 있다.
  • 함수를 컬렉션 자료구조에 할당할 수 있다.

 

평가(evaluation) 방법

표현식을 코드로 작성하면 로딩할 때 즉시 평가 즉 표현식을 계산해 값으로 변형한다. 이와 반대로 사용하는 시점에 평가되는 것을 지연 평가라고 한다.

  • 즉시 평가(eager): 함수를 정의하고 호출처리
  • 지연 평가(lazy): 정의된 함수를 필요로 할 경우 호출처리

특정 시점에 지연 평가를 처리하는 방법은 lazy 함수, 제너레이터 시퀀스 함수, 부분함수 등이 있다.

 

 

 

커링 함수(currying function)

하나의 함수에서 매개변수를 분리해 외부함수와 내부함수로 지정해서 처리할 수 있다. 마지막 함수의 인자가 들어오면 최종 처리를 한다. 이처럼 함수를 부분으로 나눠 처리하는 것을 커링함수라고 한다. 보통 커링함수를 만들 때는 클로저 환경이 구성된다. 커링함수를 부분함수라고도 한다. 커링함수도 확장함수로 정의할 수 있다.

 

  • 외부함수: 매개변수를 받고 내부함수를 반환하는 기능만 처리한다.
  • 내부함수: 함수, 람다표현식, 익명함수로 구성한다. 내부함수를 깊게 계층화하면 예외를 발생하므로 이때는 람다표현식으로 구성하는 게 좋다. 

중간에 특정한 기능을 추가하려면 내부함수로 작성해서 원하는 시점에 실행되도록 구성한다.

 

Q. 전역변수를 건드리는 것이 아니라 함수의 return 값을 받아서 사용하는 것이니 순수함수라고 볼 수 있겠네요?!

 

 

체이닝(Chaining)

함수나 메서드가 연속해서 호출해서 처리하는 것을 메서드 체이닝(chaining)이라고 한다. 너무 많이 사용하면 실제 코드를 이해하는 데 너무 어려울 수 있으므로 적절하게 사용하는 것이 좋다. 커링함수도 체이닝이다.

val lambda = { x: Int -> { y: Int -> { z :Int -> x+y+z } } }

println(lambda(100)(200)(300)) // 함수를 연속으로 실행
//600
// 메서드 체이닝

class Car(var ownerName: String, var color: String) {
    fun changeOwner(newName: String): Car {
    	this.ownerName = newName
        return this // 연속 호출을 위해 객체 반환
    }
	
    fun repaint(newColor: String) : Car {
    	this.color = newColor
        return this
    }
    
    fun info(): Unit = println("Car(소유자 = $ownerName, 색상 = $color)")
}

val c = Car("서정희", "빨간색")
c.info()
c.changeOwner("이재헌").repaint("파란색").info()

// Car(소유자 = 서정희, 색상 = 빨간색)
// Car(소유자 = 이재헌, 색상 = 파란색)

 

 

고차함수

함수를 객체로 생각해서 인자로 전달되거나 반환값으로 처리되는 함수 패턴을 말한다. 고차함수를 사용하는 이유는 복잡한 함수를 재활용하기 위해 행위를 하는 함수와 연산을 하는 함수를 분리해서 구성할 수 있기 때문이다.

 

  • 함수의 매개변수에 함수 자료형을 정의하고 함수를 호출할 때 인자로 다른 함수를 받는다.
  • 함수 내부에서 반환값으로 다른 함수를 반환한다.
  • 인자와 반환값으로 함수, 익명함수, 람다표현식으로 처리할 수 있다. 함수일 경우는 함수 이름이 아닌 함수 참조로 처리해야 한다.
typealias f = (Int, Int) -> Int //함수 자료형을 타입 별칭으로 지정 

fun highOrder() : f { //함수를 반환하는 고차함수
    retun { x,y -> x+y }
}

Q. 일급객체랑 고차함수랑 포함관계인가요 아니면 같은 개념인가요?! 차이를 잘 모르겠습니다.

 

 

합성함수

두 함수를 하나의 함수로 연결한 것을 합성함수(composite function)라고 한다. 구성하려면 함수의 매개변수와 자료형이 일치해야 한다.

 

  • 두 함수의 매개변수 개수와 반환 자료형이 같아야 한다.
  • 두 함수를 결합한 함수도 두 함수와 매개변수와 잘형이 같아야 한다.
  • 내부 처리는 전달되는 함수를 실행하고 그 결과를 받는 함수를 실행하는 순서로 처리한다.

 

//동일한 매개변수 개수를 받는 두 개의 함수를 배개변수로 받는다.
fun composeF( f: (Int) -> Int, g: (Int) -> Int):
				    (Int) -> Int {
    return { p1: Int -> f(g(p1)) } //반환값은 두 함수를 연결한 하나의 함수
}

 

 

재귀함수

자기 자신을 호출해서 계속 처리하는 방식이다. 꼬리 재귀도 지원하는데 마지막에 자신을 호출할 때만 이 꼬리 재귀를 처리할 수 있다.

 

재귀함수를 구성할 때 주의할 점은 무한 반복을 방지해야 하고 함수가 너무 많이 생기면 함수 스택에 오버플로우가 생겨서 작동이 중단되는 것을 방지하는 것이다.

 

  • 무한 순환이 발생하지 않도록 함수의 종료 시점을 반드시 작성한다.
  • 함수 실행의 마지막 부분에 자기 자신을 호출하고 호출된 인자의 값을 조정한다.
  • 꼬리 재귀는 함수가 다른 변수와 함수 호출 결과와의 연산도 함수의 인자로 전달되도록 작성한다.
  • 꼬리 재귀로 처리하면 코틀린은 내부적으로 함수 스택을 추가로 만들지 않는다.

 

재귀함수 호출과 실행 방식

  • 종료 시점까지 함수를 호출해서 함수 스택을 계속 구성한다.
  • 종료 시점을 만나면 특정 값이 반환되어 함수 스택의 역방향을 함수가 실행된다. 마지막까지 모두 실행되면 최종 결과를 반환한다.

'Kotlin > 스터디' 카테고리의 다른 글

코틀린 스터디 - 11장  (0) 2023.09.24
코틀린 스터디 - 9장  (1) 2023.09.10
코틀린 스터티 - 8장  (0) 2023.09.03
코틀린 스터디 - 6장  (0) 2023.08.20
코틀린 스터디 - 5장  (0) 2023.08.13