ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • kotlin) 복잡한 함수형 크로그래밍
    kotlin 2024. 7. 20. 02:50

    고차 함수와 함수 리터럴

    고차함수

    • 하나 이상의 함수를 인자로 받거나, 함수를 반환하는 함수
    • 고차 함수는 함수형 프로그래밍의 핵심 개념 중 하나로, Kotlin은 이를 언어 차원에서 지원
    • 고차 함수를 통해 코드를 더 유연하고 재사용 가능하게 만들 수 있음
    //고차함수 X
    fun add(num1: Int, num2: Int): Int{
        return num1 + num2
    }
    
    //op: (Int,Int) -> Int 를 통해 함수를 인자로 받음 , 고차함수 O
    fun compute(num1: Int, num2: Int, op: (Int,Int) -> Int): Int{
        return op(num1, num2)
    }

     

    반환 타입에도 함수가 들어갈 수 있다.

    //Int,Int 를 받아 Int 함수를 반환하는 함수
    fun opGenerator(): (Int, Int) -> Int {
    }

     

    사용법

    fun compute(num1: Int, num2: Int, op: (Int,Int) -> Int): Int{
        return op(num1, num2)
    }
    
    //람다식
    
    //덧셈 연산
    fun main() {
        val sumResult = compute(10, 5) { a, b -> a + b }		//마지막 파라미터의 람다식()은 바깥으로 뺄 수 있다.
        println("Sum: $sumResult") // 출력: Sum: 15
    }
    //뺄셈 연산
    fun main() {
        val subtractResult = compute(10, 5) { a, b -> a - b }
        println("Subtract: $subtractResult") // 출력: Subtract: 5
    }
    
    
    //익명 함수
    
    //덧셈 연산
    fun main() {
        val sumResult = compute(10, 5, fun(a: Int, b: Int): Int {
            return a + b
        })
        //val sumResult = compute(10, 5, fun(a, b) = a + b)// 파라미터 타입까지 축약할 수도 있다.
        println("Sum: $sumResult") // 출력: Sum: 15
    }
    //뺄셈 연산
    fun main() {
        val subtractResult = compute(10, 5, fun(a: Int, b: Int): Int {
            return a - b
        })
        println("Subtract: $subtractResult") // 출력: Subtract: 5
    }

     

     

    함수 리터럴(Function Literal)

    • 함수 리터럴은 이름이 없는 함수로, 코드에서 직접 정의되어 사용
    • Kotlin에서는 두 가지 형태의 함수 리터럴을 갖고있음
      • 람다 식(lambda expression) : 람다 식은 간결한 함수 리터럴 표현
      • 익명 함수(anonymous function) : 익명 함수는 이름이 없는 함수로, fun 키워드를 사용하여 정의

     

    함숫값(Function Value)

    • 함숫값은 변수에 저장하거나, 함수의 인자로 전달하거나, 함수에서 반환할 수 있는 함수
    • 함수 자체를 값처럼 취급
    • Kotlin에서는 함수 타입을 명시하여 함숫값을 사용할 수 있음
    fun add(a: Int, b: Int): Int {
        return a + b
    }
    
    fun main() {
        val sumFunction: (Int, Int) -> Int = ::add		//sumFunction은 add 함수의 참조를 담고 있는 변수
        println(sumFunction(3, 5)) // 출력: 8				add 구문은 add 함수에 대한 참조를 나타냄
    }

     

    람다 식과 익명 함수의 차이점

    1. 구문: 람다 식은 간결한 구문을 제공하며, 익명 함수는 fun 키워드를 사용하여 좀 더 명시적
    2. 반환 타입: 람다 식은 추론된 반환 타입을 사용하고, 익명 함수는 반환 타입을 명시할 수 있음
    3. 명확성: 복잡한 로직이 필요한 경우 익명 함수가 더 읽기 쉬울 수 있음

    요약

    • 함숫값: 함수 자체를 값처럼 취급하여 변수에 저장하거나 전달할 수 있음
    • 함수 리터럴: 이름이 없는 함수로, 람다 식과 익명 함수가 있음
      • 람다 식
        • 간결한 함수 리터럴 표현,
        • 람다식 안에는 return 을 쓸 수 없다.
      • 익명 함수:
        • fun 키워드를 사용하여 정의되는 함수 리터럴
        • 익명 함수 안에는 return 을 쓸 수 있다.

    고차함수를 이용한 간단한 계산기

    fun compute(num1: Int, num2: Int, op: (Int, Int) -> Int): Int {
        return op(num1, num2)
    }
    
    fun calculator(num1: Int, num2: Int, operator: String): Int {
        return when (operator) {
            "+" -> compute(num1, num2) { a, b -> a + b }
            "-" -> compute(num1, num2) { a, b -> a - b }
            "*" -> compute(num1, num2) { a, b -> a * b }
            "/" -> compute(num1, num2) { a, b -> a / b }
            else -> throw IllegalArgumentException("Invalid operator")
        }
    }

    복잡한 함수 타입과 고차 함수의 단점

    고차 함수의 타입 살펴보기

    //화살표는 반환타입
    //(Int,Int,(Int,Int -> (반환타입)Int) -> (반환타입)Int
    fun compute(num1: Int, num2: Int, op: (Int,Int) -> Int): Int{
        return op(num1, num2)
    }
    
    //() -> (Int, Int) -> Int
    fun opGenerator(): (Int, Int) -> Int {
        return { a, b -> a + b}
    }
    
    
    //괄호 앞에 수신객체 타입이 붙는다.
    //Int.(Long) -> Int
    fun Int.add(other: Long): Int = this + other.toInt()

    함수 리터럴 호출하기

    • 람다 식 정의: 람다 식은 이름 없는 함수로, { a: Int, b: Int -> a + b }와 같은 형태로 정의
    • 함수 타입: 람다 식은 함수 타입 (Int, Int) -> Int를 가짐
    • invoke 메서드: 함수 타입의 객체를 호출할 때 사용할 수 있는 특별한 메서드로, 람다 식이나 함수 타입 객체를 실행
    • 직접 호출: 함수 타입 객체는 () 연산자를 사용하여 직접 호출할 수 있으며, 이는 invoke 메서드를 호출하는 것과 동일
    fun main(){
        //두 개의 Int 값을 받아 그 합을 반환하는 람다 식
        val add = { a: Int, b: Int -> a+b}
        
        //두개의 차이점은 없다.
        add.invoke(1,2) //invoke 함수는 함수 타입을 가진 객체에서 호출할 수 있는 특별한 함수
        add(1,2)        //직접 함수 호출
        5.add(3)        //확장함수를 호출하는것과 동일하게 사용할 수도 있다.
    }

    Decompile 코드

    //고차함수에서 함수를 넘기면, FunctionN 클래스로 변환
    compute(2,3,(Function2) null.INSTANCE);
    
    public static final int compute(int num1, num2, @NotNull Function2 op){
        return ((Number) op.invoke(num1, num2)).intValue();
    }

     

    FunctionN

    • 함수를 변수처럼 사용할때마다 FunctionN 객체가 만들어진다
    • FunctionN 인터페이스는 이러한 함수 타입을 나타내는 기본 인터페이스
    • 여기서 N은 함수가 받을 수 있는 매개변수의 수를 나타냄
      • 예를 들어, Function2는 두 개의 매개변수를 받는 함수 타입을 나타냄
    fun main() {
        // compute 함수를 호출하여 2와 3을 더한 결과를 result 변수에 저장합니다.
        // 익명 객체를 사용하여 Function2<Int, Int, Int> 인터페이스를 구현하고,
        // 두 Int 값을 받아 더한 값을 반환하는 invoke 메서드를 정의합니다.
        val result = compute(2, 3, object : Function2<Int, Int, Int> {
            override fun invoke(p1: Int, p2: Int): Int {
                // 두 매개변수 p1과 p2를 더한 값을 반환합니다.
                return p1 + p2
            }
        })
        // 결과를 출력합니다. 출력값은 5입니다.
        println(result) // 출력: 5
    }
    
    // compute 함수는 두 Int 값을 받고 Function2 인터페이스를 구현한 함수 타입을 매개변수로 받습니다.
    fun compute(num1: Int, num2: Int, op: Function2<Int, Int, Int>): Int {
        // op.invoke(num1, num2)를 호출하여 num1과 num2를 더한 값을 반환합니다.
        return op.invoke(num1, num2)
    }

     

    Closure 와 연동

    fun main(){
        var num = 5
        num += 1
        val plusOne: () -> Unit = { num += 1}   //밖의 변수 num 의 정보를 미리 포획함 = Closure
    }

    inline 함수 자세히 살펴보기

    inline 함수

    • 함수를 호출하는 쪽에 함수 본문을 붙여넣게 됨
    • 함수 호출을 함수 본문으로 대체하여 호출 오버헤드를 줄이고 성능을 최적화할 수 있도록 하는 함수
    • inline 함수를 사용하면 람다 식이나 고차 함수에서 함수 호출 비용을 줄일 수 있음
    // inline 키워드를 사용하여 함수 호출 오버헤드를 줄입니다.
    inline fun compute(num1: Int, num2: Int, op: (Int, Int) -> Int): Int {
        // op 함수를 호출하여 num1과 num2를 인자로 넘겨 계산 결과를 반환합니다.
        return op(num1, num2)
    }
    
    fun main() {
        // inline 함수 compute를 호출합니다.
        val sumResult = compute(2, 3) { a, b -> a + b }
        println("Sum: $sumResult") // 출력: Sum: 5
    
        // 다른 연산을 수행하는 람다 식을 전달합니다.
        val productResult = compute(2, 3) { a, b -> a * b }
        println("Product: $productResult") // 출력: Product: 6
    }
    
    //자바의 디컴파일
    public static void main(String[] args) {
        // compute 함수의 본문이 이곳에 직접 인라인 됩니다.
        // 첫 번째 호출
        int sumResult = (2 + 3);
        System.out.println("Sum: " + sumResult); // 출력: Sum: 5
    
        // 두 번째 호출
        int productResult = (2 * 3);
        System.out.println("Product: " + productResult); // 출력: Product: 6
    }

    non-lical return

    fun main(){
        iterate(listOf(1,2,3,4,5)){num->
            if(num == 3){
                return      //inline 의 경우 main() 에 내부 함수를 그대로 가져오기때문에 return 을 사용할 수 있다.
                            //다만 해당 return 의 경우 main() 함수를 종료하는 return 이다.
            }
            println(num)
        }
    }
    
    inline fun iterate(numbers: List<Int>, exec: (Int) -> Unit){
        for(num in numbers){
            exec(num)
        }
    }

    SAM과 reference

    SAM(Single Absctract Method)

    • SAM 변환은 하나의 추상 메서드만을 갖는 인터페이스나 추상 클래스에 대해, 해당 인터페이스나 추상 클래스의 인스턴스를 람다 식으로 표현할 수 있도록 하는 기능
    • Java에서는 이러한 인터페이스를 함수형 인터페이스라고 부름.
    @FunctionalInterface
    public interface Runnable{
        //추상 메소드이고 하나만 존재
        public abstract void run();
    }
    
    //람다식으로 변환
    public class Main {
        public static void main(String[] args) {
            // Runnable 인터페이스를 람다식으로 구현
            Runnable runnable = () -> System.out.println("Running");
    
            // Thread에 runnable을 전달하여 실행
            Thread thread = new Thread(runnable);
            thread.start();
        }
    }

     

    Reference

    • 객체의 메모리 주소를 가리키는 변수
    • Reference는 객체의 실제 데이터를 포함하지 않고, 객체가 위치한 메모리 주소만을 보유

    강한 참조 (Strong Reference)

    • 기본적인 참조 형태로, 객체가 가비지 컬렉션(Garbage Collection)되지 않도록 유지
    String str = new String("Hello, World!");

     

    약한 참조 (Weak Reference)

     

    • 객체가 가비지 컬렉션의 대상이 될 수 있도록 허용하는 참조입니다. WeakReference 클래스가 사용
    • 약한 참조는 객체의 생명주기를 강하게 연장하지 않으며, 객체가 더 이상 강하게 참조되지 않으면 가비지 컬렉션
    import java.lang.ref.WeakReference;
    
    String str = new String("Hello, World!");
    WeakReference<String> weakRef = new WeakReference<>(str);

     


    소프트 참조 (Soft Reference)

     

    • 가비지 컬렉터가 메모리가 부족할 때만 객체를 수거하도록 허용하는 참조입니다. SoftReference 클래스가 사용
    • 소프트 참조는 메모리가 충분할 때는 가비지 컬렉션되지 않지만, 메모리가 부족하면 가비지 컬렉션
    import java.lang.ref.SoftReference;
    
    String str = new String("Hello, World!");
    SoftReference<String> softRef = new SoftReference<>(str);

     

     

    팬텀 참조 (Phantom Reference)

     

    • 객체가 finalize된 후, 가비지 컬렉터에 의해 수거되기 전 액션을 수행할 수 있도록 합니다. PhantomReference 클래스가 사용
    • 팬텀 참조는 객체가 실제로 가비지 컬렉션되기 전의 마지막 단계에서 사용
    import java.lang.ref.PhantomReference;
    import java.lang.ref.ReferenceQueue;
    
    String str = new String("Hello, World!");
    ReferenceQueue<String> queue = new ReferenceQueue<>();
    PhantomReference<String> phantomRef = new PhantomReference<>(str, queue);

     

     

    함수 참조 (Function Reference)

    • 함수 참조는 기존의 함수를 참조하여 이를 변수에 저장하거나 다른 함수에 전달
    • 함수 참조는 :: 연산자를 사용하여 표현
    • Java 에서는 호출 가능 참조 결과값이 Consumer / Supplier 같은 함수형 인터페이스이지만, 
      Kotlin 에서는 리플렉션 객체이다.
    fun add(a: Int, b: Int): Int {
        return a + b
    }
    
    fun main() {
        // 함수 참조를 변수에 저장
        val addFunction: (Int, Int) -> Int = ::add
    
        // 함수 참조를 사용하여 함수 호출
        val result = addFunction(2, 3)
        println(result) // 출력: 5
    }

     

    프로퍼티 참조 (Property Reference)

    • 프로퍼티 참조는 특정 프로퍼티(변수)를 참조하여 이를 변수에 저장하거나 다른 함수에 전달할 수 있게 함
    • 프로퍼티 참조 역시 :: 연산자를 사용하여 표현
    var name: String = "Kotlin"
    
    fun main() {
        // 프로퍼티 참조를 변수에 저장
        val nameReference = ::name
    
        // 프로퍼티 참조를 사용하여 값 읽기
        println(nameReference.get()) // 출력: Kotlin
    
        // 프로퍼티 참조를 사용하여 값 설정
        nameReference.set("Kotlin Language")
        println(name) // 출력: Kotlin Language
    }

     

     

    'kotlin' 카테고리의 다른 글

    kotlin) 어노테이션과 리플렉션  (2) 2024.07.24
    kotlin) 제네릭  (0) 2024.07.21
    kotlin) Kotlin 의 지연과 위임  (0) 2024.07.18
    kotlin) 추가적으로 알아두어야 할 코틀린 특성  (0) 2024.07.13
    kotlin) 코틀린에서의 FP  (0) 2024.07.09

    댓글

Designed by Tistory.