본문 바로가기
코틀린 문법

2. 고차함수,람다함수, 스코프함수-코틀린 6일차

by 농농씨 2023. 4. 4.

티스토리 두번째 글

아자아자

 

맥북은 참 뭐랄까...한/영키를 굳이 capslock에 놔야했던걸까? 이거땜에 자꾸 한영전환이 안돼서 스트레스받아서 설정하느라 혼이 났다.

그치만 설정 다 했으니까 nobody 아무도 날 막을수없으셈~ 전환 잘되는군 good~

참... 애증이다 애증

 

코드테스트 사이트:play.kotlinlang.org

참조강좌:유튜브 디모의 코틀린

고차함수와 람다함수

//고차함수:함수를 마치 클래스에서 만들어낸 '인스턴스처럼' 취급하는 법
//=>함수를 '패러미터'로 넘겨줄 수도 있고 '결과값으로 반환'받을 수도 있다.
//코틀린에선 모든 함수를 고차함수로 사용 가능하다.
//패러미터가 뭐지? 요소? 입력받는 요소?

fun main() {
    
    b(::a)//함수 b를 호출하되 함수a를 패러미터로 넘겨줌
    //:: : 일반 함수를 고차 함수로 변경해주는 연산자, 콜론 두개!
    
}

fun a (str: String){ //a라는 함수 만들어 문자열을 패러미터로 받는다
     //문자열을 받고, 반환형은 없는 함수!
    println("$str 함수 a")//패러미터로 받은 문자열 포함한 출력
}

fun b (function: (String)->Unit){ //함수를 나타낸 자료형의 일종,기억! 
    //함수 받을 패러미터 이름은 function 
    //함수 만들면서,함수 a를 고차함수 형식의 패러미터로 받을수 있도록 함
    //함수를 패러미터로 받을 때 함수의 자료형은 어떻게 해야할까?
    //(자료형,자료형,...)(함수의패러미터들->자료형(반환형)
    //이런 형태의 함수를 모두 패러미터로 받을 수 있게 됨!
    //Unit은 값이 없다는 형식!
    function("b가 호출한")//받아온 함수 실행하되 문자열도 같이 넘겨줌
    
}
//정리)main함수가 a함수를 b함수에 패러미터로 넘겼고
//b함수는 받아온 a함수에 "b가 호출한"이라는 값을 넘겨서 호출함
//최종적으로 a라는 함수가 실행되면서 "b가 호출한 함수 a"라고 출력됨

 

그.런.데

패러미터로 넘길 함수를 굳이 이름까지 붙여 따로 만들 필요가 있을까요?

함수를 람다식으로 표현하는 "람다함수"가 등장!

 

//람다함수는 일반함수와 달리 '그 자체가 고차함수'라서 별도의 연산자없이
//변수에 담을 수 있다!

fun main() {
    b(::a) //일반함수를 고차함수로 만들어주는 연산자 ::
    
    val c: (String)->Unit = {str->println("$str 람다함수")}
    //val a:Int 처럼 일반적인 변수에 자료형 쓰듯, 함수의 형식((string)-unit) 써줌
    //원래는 str:String 처럼 콜론,자료형 써줘야함, 근데 이미 받아오는
    //  함수의 자료형이 기술되어있으므로(String) 생략가능!
    //패러미터로 받아온 문자열을 매칭해 줄 변수 이름 써줌(str)
    // *str은 string으로 받아온 값을 람다함수 내에서 사용할 변수이름임
    //동작시킬 구문 :str->println() 함수 형식처럼 화살표 써주고 
    //  패러미터로 받은 함수를 포함하여 뭔가를 출력
    b(c) //함수 b에 람다함수 c를 넘겨 실행!
    
}//이때, 람다함수 축약 가능
 // val c:{str:String->println("$str 람다함수")}
 //   람다식 안에만 패러미터의 자료형 기술!
 //   이에 맞게 알아서 (String)->Unit 자료형으로 저장됨!

fun a(str: String) {
    println("$str 함수 a")
}

fun b (function: (String)->Unit) {
    function("b가 호출한")
}

 

//흐음...솔직히 마지막?축약은 이해잘안됨

 

 

함수를 변수로 사용할 수 있게 되었다~~

 

//람다함수 보충설명
//1. 람다함수도 여러줄로 사용할 수 있다. 

val c:(String)->Unit={str-> 
    println("$str 람다함수")
    println("여러 구문을")
    println("사용가능합니다")
}

val calculate:(Int, Int)->Int={a,b->
    println(a)
    println(b)
    a+b //람다함수가 여러줄이 되는 경우 마지막값이 반환됨
}


//보충설명 2.람다함수에 패러미터가 없다면?실행할 구문들만 나열하면 된다!

val a:()->Unit={println("패러미터가 없어요")}


//보충설명 3.패러미터가 하나뿐이라면 it 사용
//(패러미터 여러개면 람다함수 내에서 패러미터 이름 일일이 써줬었음)

val c:(String)->Unit={println("$it 람다함수")}
//패러미터가 String 하나라서 패러미터 이름 안써주고 바로 println 쓰고
//그 안에서는 it이라고 패러미터 지칭함


스코프함수

//스코프함수:함수형 언어의 특징을 좀더 편리하게 사용할수있도록 기본제공하는 함수들 
//클래스에서 생성한 인스턴스를 scope함수에 전달하면 
// 인스턴스의 속성이나 함수를 좀 더 깔끔하게 불러 쓸 수 있다!
//scope 함수:apply, run, with, also, let 5가지
//apply:인스턴스를 생성한 후 변수에 담기 전에 초기화 과정 수행할 때 많이 쓰임
//run:apply처럼 run스코프 안에서 참조연산자를 사용하지 않아도됨(공통)
//  일반 람다함수처럼 인스턴스 대신 마지막 구문에 결과값을 반환함 

fun main() {
    //책 변수
    var a = Book("디모의 코틀린", 10000).apply{
        name = "[초특가]"+name
        discount()
    } 
  //기존에는 인스턴스를 저장한 변수(a)를 통해 참조연산자를 사용하여
  //a.name a.discount()과 같이 속성과 함수를 사용했지만
  //apply를 이용하면 인스턴스를 생성하자마자
  //그 인스턴스에 참조연산자를 사용하여 apply를 붙이고 중괄호로 람다함수????를
  //하나 만들어 apply의 scope '안'에서 직접 인스턴스의 속성과 함수를
  //참조연산자 없이!! 사용이 가능하다~
    
  //또한 apply는 인스턴스 자신을 다시 반환!하므로
  //이렇게 생성되자마자 조작된 인스턴스를 변수에 바로 넣어줄 수 있다!
  
  //apply와 같은 스코프 함수를 사용하면 main함수와 '별도의 scope'에서
  //인스턴스의 변수와 함수를 조작하므로 코드가 깔끔해진다~
  
   var b = a.run(){
        println(a.price)
        a.name
    }

     //run은 스코프 안에서 참조연산자 사용안하는건 apply와 같지만 

     //일반 람다함수처럼, 인스턴스 대신 마지막 구문에 결과값을 반환한다는 차이점!

     //*이렇게 쓰면 가격은 출력하지만 마지막 구문인 이름은 반환하여
     // b라는 변수에 할당됨
     //따라서 이미 인스턴스가 만들어진 후에 인스턴스의 함수나 속성을 
     //scope내에서 사용해야 할 때 유용함

------->회색부분은 var a 부분 대체해서 사용

//이제 apply를 사용해 만들어진 변수a의 내용을 run을 이용해 출력해봅시다~ 
    
    a.run{
        println("상품명:${name}, 가격:${price}원")
    }
    //변수 a에 참조연산자를 사용하여 run을 붙이고 중괄호 안에서 인스턴스의
    //속성 이름을 직접 사용하여 내용을 출력해준다
    //실행해보면 apply에서 수정한 책 이름과 할인된 가격인 8000원이 잘 출력됨.
}


class Book(var name:String, var price:Int){
    //book이라는 클래스 만들어 이름과 가격을 파라미터로 받음
    fun discount()//가격을 낮춰주는 함수 생성
    {
        price -= 2000
    }
}

 

with

//<with>
//run과 기능은 동일
//but 단지 인스턴스를 참조연산자 대신 패러미터로 받는다
//a.run{...}
//with(a){...} -->형태만 좀 다르다!
//
//<also/let>
//also:처리가 끝나면 '인스턴스'를 반환(apply와 같은기능)
//let:처리가 끝나면 '최종값'을 반환(run과 같은 기능)
//차이점:apply,run은 참조연산자 없이 인스턴스의 변수와 함수 사용 가능
// also, let은 마치 패러미터로 인스턴스를 넘긴것처럼 'it'을 통해
// 인스턴스를 사용할 수 있다
// Q)왜 굳이 얘네만 패러미터로 인스턴스 사용?
// A)같은 이름의 변수나 함수가 'scope 바깥에 중복'되어있는 경우에
//  혼란을 방지하기 위해~
fun main(){
    
    var price = 5000 //book클래스의 속성 중 하나와 이름이 중복됨
    //main함수의 변수가 인스턴스 내의 속성보다 우선돼서 5000이 출력됨 
    var a = Book("디모의 코틀린",10000).apply {
        name = "[초특가]" + name
        discount()
    }//변수a는 book클래스에 "디모의 코틀린"과 10000이라는 속성을 넣은
    //인스턴스이고, apply를 이용해서 바로 함수와 속성을 사용할 수 있도록 함~
//자신을 반환한다  
a.run {
        println("상품명: ${name},가격:${price}원")
    }  
    a.let{
        println("상품명: ${it.name},가격:${it.price}원")
        //원래는 그냥 price로 썼는데 main함수 변수랑 겹쳐서
        //run->let 대체하고 it키워드도 같이 사용해줌
    }//apply도 이처럼 스코프 외부와 겹치는 속성 있으면 'also'로 대체!
}

class Book(var name:String, var price:Int)
{
    fun discount()
    {
        price -=2000
    }
}
//정리
//스코프 함수는 인스턴스의 속성이나 함수를 scope내에서 깔끔하게 분리하여
//사용할 수 있다는 점 때문에 코드의 가독성을 향상시킨다는 장점


 

 

 

 

 

인스턴스라는 말때문에 의미가 헷갈려서 게슈탈트붕괴가 올 것만 같아~

오마이갓 강의는 6분인데 내용은 6분이 아니야😱

음 프레쉬한 뇌로 수정하러 올게요~ 23.04.04.

음... 지금 수정하는거 진짜 노양심~ 23.04.30.