[ Swift 드루와 ] 고차함수

Cook Kim·2022년 12월 4일
0

Swift 드루와

목록 보기
2/2

최근 함수지향프로그래밍 관심을 가지다 보니 [Swift 드루와] 시리즈 첫 글 주제가 고차함수가 되었습니다. 앞 소개글에도 언급해드렸지만, 최대한 자유로운 분위기로 진행을 하고 싶습니다! 자유로운 의견, 지적 등등 댓글로 남겨주시면 감사하겠습니다! 그럼 오늘도 빠샤하시죠


요즘 함수지향 프로그래밍 패러다임이 주목, 관심을 받고 있습니다.
여러가지 이유가 있으나, 자세히는 함수지향 프로그래밍을 주제로 따라 글 하나를 쓰도록 하겠습니다.
swift 또한 해당 패러다임을 지향하고 있으며, 도울 수 있는 요소도 제공해주고 있습니다.
그런 요소 중 하나가 고차 함수 입니다.

| 고차함수란?

고등학교 수업시간에 졸지 않았다면, 한번 쯤은 들어보셨죠..? 수학적 정의는 모두들 알고 계시니.. 생략하겠습니다. 수학적 고차함수와 흡사합니다. 원리 자체는 같다고 생각됩니다.

프로그래밍적 고차함수는 보통 함수매개변수로 사용하거나 함수를 반환하는 함수이다.
와닿으시지 않을 수 있으나, 코드를 통해 확인해보시죠.
추가적으로 swift의 함수는 일급 시민으로 고차함수를 구현 할 수 있습니다.

func calculate(_ firstNumber: Int, _ secondsNumber: Int, calFunction: (Int, Int) -> Int) {
    return calFunction(firstNumber, secondsNumber)
}

func addCal(_ firstNumber: Int, _ secondsNumber: Int) -> Int{
    return firstNumber + secondsNumber
}

print(calculate(3, 4, calFunction: addCal))
// 7

계산 방법인 calFuction 을 (Int, Int) → Int 클조저 타입으로 받아 두 수를 계산하여 print하는 calculate 함수

func calculateContorl(_ check: Bool) -> (Int, Int) -> Int {
    if(check){
        return multiCal
    }
    else {
        return dividCal
    }
}

func multiCal(_ firstNumber: Int, _ secondsNumber: Int) -> Int{
    return firstNumber * secondsNumber
}

func dividCal(_ firstNumber: Int, _ secondsNumber: Int) -> Int{
    return firstNumber / secondsNumber
}

let cal = calculateContorl(true)

print(cal(2, 3))
// 6

check 변수를 받아 곱하기 계산을 진행할지, 나누지 계산을 진행할지 결정하는 calculateContorl 함수
머리가 돌아가지 않아.. 억지로 만들기는 했습니다..! 좋은 예제 있으면 댓글 남겨주세요 :)
위와 같이 매개변수 나 반환 값의 타입이 함수인 함수를 고차함수라 말합니다.


| Swift 고차함수

Swift Collection 에서는 기본적으로 유용한 고차함수를 제공해주고 있습니다.
map, filter, reduce, forEach, compactMap, flatMap 등..
차근 차근 하나씩 살펴보시죠 !

:: Map

func map<T>(_ transform: (Self.Element) throws -> T) rethrows -> [T]

기존의 컨테이너 요소에 대해 정의한 클로저로 매핑한 결과를 새로운 컨테이너로 반환한다.
한글 설명인데 왜 와닿지 않을까요.. 예시로 살펴보겠습니다.

let commonArray = [1, 2, 3, 4, 5]

let doubleArray = commonArray.map { element in return element * 2 }
print(doubleArray)
// [2, 4, 6, 8, 10]

컬랙션 요소에 순차적으로 접근해 클로저에 정의 된 변환 에 따라 변환반환 해줍니다!
이제야 한글 설명도 이해가 가는거 같습니다.

:: Filter

func filter(_ isIncluded: (Self.Element) throws -> Bool) rethrows -> [Self.Element]

기존 컨테이너 요소에 대해 조건에 만족 값을 새로운 컨테이너에 반환한다.
map을 한번 이해해서 그런지, 대강 감이 오는거 같습니다. Filter 말 그대로 요소에 대한 필터 처리 아닐까요?

let numberArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

let evenNumber = numberArray.filter { element in return element & 1 == 0}
print(evenNumber)
// [2, 4, 6, 8, 10]

크흐 맞았습니다. 요소를 순차적으로 접근해 클로저에 정의 된 기준에 따라 필터해주고 반환해줍니다.
훗... 고차함수 쉽네요

:: Reduce

func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result

정의한 클로저를 사용해 컨테이너 요소에 결합한 결과를 반환합니다.
흐음.. 죄송합니다 자만했던거 같습니다 예제로 보시죠...

let oddNumber = [1, 3, 5, 7, 9]

let sum = oddNumber.reduce(5) { pre, next in
		print("pre : \(pre) , next : \(next)")
    return pre + next
}
print(sum)

// pre : 5 , next : 1
// pre : 6 , next : 3
// pre : 9 , next : 5
// pre : 14 , next : 7
// pre : 21 , next : 9

// 30

아하 ! reduce은 단순히 데이터 수를 줄여준다는 의미였던거 같습니다.
입력 받은 초기 값부터 클로저에 정의 된 결과누적으로 처리해 반환하고 있습니다.

:: ForEach

func forEach(_ body: (Self.Element) throws -> Void) rethrows

이 녀석도 고차함수 였다니.. 너무나도 친숙해서 반갑네요

let hello = ["H", "E", "L", "L", "O"]

hello.forEach{ element in
    print(element)
}
// H
// E
// L
// L
// O

앞 함수들과 달리 반환 값은 존재하지 않고, 순차적으로 요소에 접근해 정의 된 클로저를 진행합니다.

:: FlatMap

func flatMap<SegmentOfResult>(_ transform: (Self.Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence

func map<T>(_ transform: (Self.Element) throws -> T) rethrows -> [T]

시퀀스의 각 요소로 지정된 변환을 호출한 연결된 결과를 포함하는 배열을 반환합니다.
단어 자체가 map이 포함되어 있어 map 과 비교를 위해 두 정의를 가져와보았습니다. 두개를 봐도 where 이외 차이는 모르겠네요...

let numbers = [1, 2, 3, 4, 5]

let flatMap = numbers.flatMap{ element in
    return element * 2
}

let map = numbers.map{ element in
    return element * 2
}
print(flatMap)
// [2, 4, 6, 8, 10]
print(map)
// [2, 4, 6, 8, 10]

?!?!? 결과가 똑같습니다..

let numbers = [[1, 2, 3, 4] , [1,2]]

let flatMap = numbers.flatMap { (item) in
    return item
}

let map = numbers.map{ (item) in
    return item
}

print(flatMap)
// [1, 2, 3, 4, 1, 2]
print(map)
// [[1, 2, 3, 4], [1, 2]]

flatMap map 변환 역할에 플러스 알파로 배열의 차수를 낮춰 주는 것으로 보입니다.
그래서 접근 순서가 1 -> 2 -> 3 -> 4 -> 1 -> 2 이런식으로 될 줄 알았는데 [1, 2, 3, 4] -> [1, 2] 배열 자체를 접근해 위와 같이 item * 2 하면 item 은 [Int] 오류가 발생합니다.
주로 사용 될거 같지는 않지만, 좋은 예제 아시는 분은 댓글 남겨주시면 감사하겠습니다 !!

:: CompactMap

후 마지막입니다 ! 이것만 살펴보시고 같이 조금 쉬시죠

func compactMap<ElementOfResult>(_ transform: (Self.Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]

시퀀스의 각 요소로 지정된 변환을 호출한 nil이 아닌 결과를 포함하는 배열을 반환합니다.
map 변환 역할 + nil 처리 인 것으로 보이네요..

let optionalNumber = [1, 2, 5, nil, 7, nil]

let compactMap = optionalNumber.compactMap{ (item) in
    return item
}

let numberMap = optionalNumber.map{ (item) in
    return item
}

print(compactMap)
// [1, 2, 5, 7]
print(numberMap)
// [Optional(1), Optional(2), Optional(5), nil, Optional(7), nil]

순차적으로 요소에 접근해 클로저에 정의 된 변환, 변환 값이 nil 일 경우 제외해줍니다.

compactMap에서 return item이 아닌 return “(item)”으로 시도할 경우 결과는 ["Optional(1)", "Optional(2)", "Optional(5)", "nil", "Optional(7)", "nil"] 으로 출력되었습니다.


강의를 듣던 중 swift 선호 패러다임에 함수 지향 프로그래밍을 접하게 되었고, 기법으로 고차 함수를 알게 되었네요.

아무래도 swift 언어를 사용해 코딩 할 때 swift 다운 코드 작성이 필요하겠죠..? 위 간단한 고차함수도 유용해 보이고 익숙해질 필요가 있는거 같습니다.

다음에도 더 유익한 주제와 예시를 소개해드리도록 하겠습니다. 그럼 즐코~ 👋👋👋

위 예제 코드는 github에 제공됩니다!


참고자료

https://developer.apple.com/documentation/swift
https://jeonyeohun.tistory.com/265
https://velog.io/@mt_big/함수형-프로그래밍Functional-Programming

profile
꾸준함의 힘을 믿는 자

0개의 댓글