고차함수

Park Jae Hong·2022년 9월 22일
0

고차 함수(Higher-Order Function)

: 함수를 인자로 전달받거나, 함수를 결과로 반환하는 함수를 말한다

Kotlin의 모든 함수는 고차 함수로 활용 가능 !

사용 방법 ❗

fun main(){
	 animal(::cat)
}

fun cat(str: String) {
    println("$str Meow~~")
}

fun animal(function: (String) -> Unit){
    function("Cat")
}

(자료형,자료형..parameter) -> 함수의 반환 자료형

:: 일반 함수를 고차 함수로 변경해주는 연산자

결과

💡 람다(익명) 함수 ➡ 더 간결한 코드를 위해


fun main(){

	val dog : (String) -> Unit = {str -> println("$str bark!")} 
    
    // 타입 추론
    val dog1 = {str : String -> println("$str bark!")}

    animal(dog)
    animal(dog1)
}

결과


사용 이유 ❗

추상화란 ❓

: 복잡한 어떤 것들을 압축해서 핵심만 추출한 상태로 만드는 것.

1. 추상화는 곧 생산성 향상

: 컴퓨터는 0과 1만 이해하지만, 코드가 해석되고 복잡한것들은 컴파일러 대신해주기에 우리는 문법을 올바르게 사용하는 것만으로도, 다양한 프로그램 언어를 비교적 쉽게 작성할 수 있어 생산성이 높아진다.

2. 함수는 추상화 대표적인 사례

: 반복되는 로직은 별도의 함수로 작성 !

3. 추상화를 한단계 높인 고차함수

: 함수는 값을 전달받아 값을 리턴하고 단순히 값을 받아 처리하는 수준인데, 고차함수는 함수를 전달받거나 함수를 리턴하기 때문에 특정 함수의 값을 미리 지정하지 않고 고차함수를 사용할때 1번만 사용하게 된다. 고로, 추상화의 수준이 높아지는 만큼 생산성도 높아지게 된다.


종류 ❗

  • forEach

    : for문을 대체하는 고차 함수

  • .map()

    : forEach 같이 순회하면서, 콜백함수에서의 실행결과를 리턴한 값으로 이루어진 배열을 만들어 반환

  • .find()

    : 주어진 배열을 순회하면서 콜백 함수 실행의 반환값이 true에 해당하는 첫번째 요소를 반환

  • .findIndex()

    : 열 메소드 indexOf() 의 콜백함수 버젼. 고차함수 find()의 리턴값이 인덱스인 버젼.

  • .filter()

    : 주어진 배열을 순회하면서 콜백 함수의 반환값이 true에 해당하는 요소로만 구성된 새로운 배열을 생성하여 반환. (즉, find()의 찾아서 값을 반환하는 기능과 map()의 배열 생성 기능의 융합 버젼.)

  • .reduce()

    : 콜백 함수의 실행된 반환값(initialValue)을 전달 받아 연산의 결과값이 반환. 사실상 forEach, map, filter기능을 reduce로 모두 구현해서 쓸순 있어 고차함수의 부모라고 불림

  • .sort()

    : 배열 정렬.(단, 복사본이 만들어지는게 아니라 원 배열이 정렬됨.)

  • .some()

    : 배열의 요소들을 주어진 함수(조건)을 통과하는데 한개라도 통과되면 true, 아닐때에는 false를 출력.(단, 빈 배열로 함수(조건)을 통과하면 무조건 false를 출력.)

  • every()

    : some() 의 반대 버전 배열안의 모든 요소가 주어진 함수(조건)을 모두 통과하면 true, 한 요소라도 통과하지 못하면 false를 출력. (빈 배열을 함수에 적용시키면 무조건 true를 반환.)


for Loop vs forEach()

for문 과 forEach 차이 ?

  • for문은 동기 방식 , forEach는 비동기 방식
    : brack 문 같이 반복을 중간에 종료해야할 경우에는 forEach는 종료할 수 없기 때문에 비효율적일 수 있어, 이 경우에는 for 를 사용 !
    ( think. forEach에서도 return으로 반복을 종료시킬수는 있지만 단편적으로 보이는 부분에서 종료 되어있을 뿐이지 내부적으로는 반복이 돌아가고 있다.)

for문 보다 forEach가 효율적이다 ?

  • 일반적인 반복문 속도 비교
  fun loop(i: Int){
    for(i in 0..i){}
}
fun main() {
    checkFor()
    checkForEach()
    // ForLoop Time: 3848875
    // ForEach Time: 13782833
}
fun checkFor(){
    println("ForLoop Time: " + measureNanoTime{
        for (i in 0..100000) { loop(i) }
    })
}
fun checkForEach(){
    println("ForEach Time: " + measureNanoTime{
        (0..10000).forEach { i -> loop(i) }
    })
}

: 일반적인 반복문에서는 for문이 더 빠르다 !
이유는 단순 계산작업일 뿐더러 forEach문은 람다형식으로 함수를 계속 호출하는 형태이므로 퍼포먼스가 저하가됩니다.

  • Collection 의 반복문 시간 계산
fun loop(i: Int){
    for(i in 0..i){}
}

fun main() {
    val list = (1..10000).toList()
    checkFor(list)
    checkForEach(list)
    // ForLoop Time: 14210459
    // ForEach Time: 6133083
}

fun checkFor(list: List<Int>){
    println("ForLoop Time: " + measureNanoTime{
        for (i in list) { loop(i) }
    })
}

fun checkForEach(list: List<Int>){
    println("ForEach Time: " + measureNanoTime{
        list.forEach { i -> loop(i) }
    })
}

: 자료구조에는 List, Set, Map 등 이 있는데, 이 클래스들은 Collection을 상속받고 있으며 해당 클래스들에 대한 몇몇 함수들은 inline을 제공하게 되는데, 때문에 자료구조를 식별할 필요가 없다. (for 문은 자료구조를 식별해 줘야한다.)
그 중 forEach도 포함되는데, 그덕에 for문보다 더 좋은 퍼포먼스를 낼 수 있는 것.

❓inline
: 함수의 내용 호출을 통해서 실행시키는 것이 아니라, 호출하는 코드 자체가 함수 내용의 코드가 되는 것.

forEach 단점 ?

  • 오직 읽기 전용으로 불러오기 때문에 데이터를 수정할 수 없다.

  • 배열을 역순으로 탐색할 수 없다.

결론

  • 고차함수를 사용하더라도 사용 원리를 이해하고 나면 응용도가 높아진다.

  • 많은 언어에서 forEach 사용을 권장하고 있는데, 그냥 단순히 사용만 하는 것이 아니라 forEach에 대해 이해하고 for 과 forEach 를 적절히 사용한다면 더 좋은 성능의 코드를 구현할 수 있을 것.

profile
The people who are crazy enough to think they can change the world are the ones who do. -Steve Jobs-

0개의 댓글