일급 객체

일급객체, 일급함수란 객체나 함수가 값으로 취급될 수 있다는 것이다.

값으로 취급 할 수 있다는 것의 의미는 변수를 할당받거나, 파라미터로 전달되거나 리턴 값으로 취급 될 수 있다는 것이다.

설명으로만 보면 이해하기 힘든데 코드를 보면 쉽게 이해가 가능하다

// 반환 값
func getName() -> String {
    "Name"
}

// 파라미터로 전달
func add(_ a: Int, _ b: Int) -> Int {
    a + b
}

// 변수에 저장
struct Person {
    let name: String
    let age: Int
}

let person = Person(name: "준", age: 20)

위와 같이 처럼 정수형, 배열, 객체, 클래스 등 변수에 할당받을 수 있는 타입을 일급객체라고 한다.

 

일급함수

일급함수란 일급객체로 취급되는 함수를 일급 함수라고 한다.

즉 함수를 값처럼 다를 수 있다는 이야기. Swift에선 함수(클로저)를 일급객체로 취급한다.

// 매개변수
func run(_ completion: ((String) -> Void)?) {
    completion?("Finish")
}

// 변수에 저장
func helloWorld() {
    print("Hello World!")
}

let task1 = helloWorld
let task2 = {
    print("Good Bye World!")
}

var tasks: [() -> Void] = [task1, task2]

// 리턴 값
func plus(_ x: Int) -> (Int) -> Int {
    { y in x + y }
}

let plus5 = plus(5)
print(plus5(10)) // 15

 

고차함수

고차함수는 다른 함수를 인자를 받거나 함수를 결과값으로 반환하는 함수를 말한다.

그러므로 모든 고차함수는 일급함수(일급객체)이다.

당연하게도 모든 일급함수가 고차함수인것은 아니다

- 모든 일급함수가 다른 함수를 인자로 받거나 함수를 결과값으로 반환하는게 아니기 때문

 

Swift에선 클로저가 일급객체 취급이므로 고차 함수는 클로저를 인자로 받거나 반환할 수 있다

 

고차함수의 장점

  • 코드 가독성 향상
  • 재사용성 증가
  • 유연성 및 확장성 증가
// 코드 가독성 향상
let numbers = [1, 2, 3, 4, 5]

var evens: [Int] = []
for number in numbers {
    if number % 2 == 0 {
        evens.append(number)
    }
}

let evens2 = numbers.filter { $0 % 2 == 0 }


// 재사용성 증가
func performCalculation(_ a: Int, _ b: Int, using operation: (Int, Int) -> Int) -> Int {
    return operation(a, b)
}

let sum = performCalculation(3, 5, using: +) // 8
let product = performCalculation(3, 5, using: *) // 15


// 유연성 및 확장성 증가
// completion 구현에 따른 확장성
func reqeust(completion: (() -> Void)?) {
    DispatchQueue.global().async {
        completion?()
    }
}


reqeust {
    print("do SomeThing")
}


reqeust {
    print("do SomeThing 2")
}

 

 

하지만 단점도 존재하는데 대표적인것이 콜백 지옥이다

func getUserID(_ completion: @escaping (Result<String, Error>) -> Void) {
    ... 비동기처리 ...
    isSuccess ? completion(.success("ID")) : completion(.failure(SomeError))
}

func getUserName(id: String, _ completion: @escaping (Result<String, Error>) -> Void) {
    ... 비동기처리 ...
    isSuccess ? completion(.success("Name")) : completion(.failure(SomeError))
}


getUserID { result in
    switch result {
    case .success(let id):
        getUserName(id: id) { result in
            switch result {
            case .success(let name):
                print(name)
            case .failure(let error):
                print(error)
            }
        }
    case .failure(let error):
        print(error)
    }
}

 

순수함수

순수함수는 두가지 조건을 만족해야 한다

  • 동일한 input엔 항상 동일한 output
  • Side Effect가 없다 (함수 외부에서 변경이 일어나지 않고 함수 실행하는 것 외에 다른 외부 상호작용이 없다)

순수함수는 수학의 함수와 동일하다고 생각하면 된다

함수 f(x) = x + 5 에서 f(5)는 항상 10 이다. f(6)은 항상 11이다

 

func add(_ a: Int, _ b: Int) -> Int {
    a + b
}
add(10, 5) // 항상 10


func minus(_ a: Int, _ b: Int) -> Int {
    a - b
}
minus(10, 5) // 항상 5


// Side Effect가 존재
var multiple = 2
func increment(_ a: Int) -> Int {
    a * multiple
}

increment(5) // 10
multiple = 10
increment(5) // 100

 

즉 Side Effect가 존재하지 않으면 동일한 input에 동일한 output이 나온다

 

간혹가다 아래 처럼 Date(), random() 처럼 input이 없고 동일한 함수를 호출하는데 다른 output이 나오니까

앞에 말한 조건에 위배되는것 아닌가라고 생각할 수 있는데

엄연히 사이드 이팩트가 존재하는 불순 함수들이다

// 외부 환경인 현재 시간에 의존
func getCurrentTime() -> Date {
    return Date() 
}

// 외부 환경인 난수 생성기에 의존
func getRandomNumber() -> Int {
    return Int.random(in: 1...100)
}

+ Recent posts