알아도 크게 쓸모있지 않는 함수와 First Class Object

Heedon Ham·2023년 8월 12일
1

iOS 이것 저것

목록 보기
10/17
post-thumbnail

간단한 CS 배경 지식

procedure, subroutine, function 등으로 불리는 subprogram은 재사용되는 코드들의 모음집이다. 그리고 이 모음집을 추상화해서 함수 이름으로 해당 코드 모음집을 호출할 수 있다. 외부의 함수(caller)가 이 함수(callee)를 call해서 호출하면 해당 subprogram으로 control이 넘어가면서 subprogram의 코드를 수행한다.

subprogramDefinition


overload

1972년 등장한 C언어는 현재의 대규모 프로그램보다 하드웨어 제어 및 작업 처리를 더 빠르고 메모리 효율을 높이는데 초점을 둔다.

따라서 타입 처리에 대해 더 엄격하고 동일한 함수 이름을 가질 수 없다. 목적에 맞는 함수 정의가 각각 직접 구현되어야 하기 때문이다.

2014년 등장한 Swift는 C언어의 타입 체크에 대한 엄격함을 가져왔지만, 객체 지향과 클로저 등 고차원언어 특성도 갖기 위해 overload 특성을 포함하고 있다.

overload: 동일 이름의 함수, parameter나 type이 달라도 상관 없음

Swift로는 다음과 같이 함수 정의가 가능하다.

func add() { }
func add(a: Int, b: Int) { }
func add(a: Double, b: Double) { }
func add(a: Double, b: Double) -> Double { return a+b }
func add() -> Int { return 1 }
func add() -> Double { return 1.1 }
func add(a: Int, b: Int, c: Int) -> Int { return a+b+c }

swiftFunctionOverload

동일한 이름의 함수지만, parameter의 type, return값의 type을 모두 고려해서 함수를 구분한다.

각 함수의 타입은 다음과 같다.

  • Void -> Void
  • (Int, Int) -> Void
  • (Double, Double) -> Void
  • (Double, Double) -> Double
  • Void -> Int
  • Void -> Double
  • (Int, Int, Int) -> Int

하지만 C에서는 이 작업이 불가능하다. C에서는 함수 이름을 통해 해당 함수의 메모리 주소로 접근하므로 함수의 이름이 같을 수 없다. 또한 다른 타입의 return값을 가지는 경우, 동일 기능임에도 각자 만들어야 함으로 이름이 다른 함수를 정의해야 한다. 만약 함수 정의를 한다면 다음과 같이 해야할 것이다.

void add() { }
void add2(int a, int b) { }
void add3(float a, float b) { }
float add4(float a, float b) { return a+b; }
int add5() { return 1; }
float add6() { return 1.1; }
int add7(int a, int b, int c) { return a+b+c; }

참고)

override는 SuperClass에서 정의된 함수를 SubClass에서 재정의해서 활용하는 것으로 객체 개념이 없는 C에서는 존재하지 않는다.
굳이 비유하자면

  • overload는 iPhone 14 시기의 iPhone 14과 iPhone 14 plus 모델 차이
  • override는 M1 Pro --> M2 Pro로 다음 세대로 넘어오면서 (상속받으며) 개선을 한 부분 (재정의를 한 부분)이 존재한다는 것.

Swift는 함수 구분을 할 때 함수 자체의 타입을 고려한다.

함수 자체의 타입이란 함수를 정의할 때 외부에서 받는 인자값을 나타내는 활용하는 parameter의 type과 return 키워드로 컨트롤을 넘길 때의 반환값의 type을 통틀어 function type이라고 한다.

위에서 언급했지만 함수가 다음과 같다고 하자.

func add(a: Int, b: Int) { return }
func add(a: Int, b: Int) -> Int { return a+b }

C에서는 동일 이름 함수이므로 구분하도록 compile error를 나타냈을 것이다.

하지만 Swift에서는 두 함수를 구분한다. (Int, Int) -> Void 로 나타나는 함수와 (Int, Int) -> Int 로 나타나는 함수는 다르다고 인지한다. 메모리 상으로도 다른 위치에 존재하는 함수로 인지할 것이다.


일급객체 개념이 왜 등장?

정확히는 일등시민 용어가 더 일반적인 용어이다.
(First Class Citizen, 영국 1등, 2등 시민 용어에서 비롯)

In a given programming language design, a first-class citizen is an entity which supports all the operations generally available to other entities. These operations typically include being passed as an argument, returned from a function, and assigned to a variable.

Swift가 함수형 프로그래밍 패턴을 추구하기에 일급 시민 개념을 도입할 수 있었다.

함수형 프로그래밍 패턴 찍먹하기

C언어와 같은 절차지향형 언어는 procedure의 호출 순서를 비롯해서 코드 실행의 순서가 매우 중요하다. 심지어 C언어에서는 subprogram 호출 시, 호출 시점까지 정의가 되어있지 않다면 compile error가 나타난다. procedure로 data structure를 비롯해 데이터를 다룬다.

반면 객체지향형 언어는 객체 (object) 기반으로 각 object의 데이터 (attribute, property)와 기능(method)로 작업 처리를 한다.

Swift는 객체지향형 언어이지만 함수형 프로그래밍 패턴을 추구한다.
새로운 언어일 수록 선배 언어들의 좋은 점을 가져다 조합하기에 아닌 언어를 찾기가...

a higher-order function (HOF) is a function that does at least one of the following:

  • takes one or more functions as arguments (i.e. a procedural parameter, which is a parameter of a procedure that is itself a procedure)
  • returns a function as its result.

실제 함수형 패턴 구현은 언어마다 다르지만 일반론으로는 고차함수(다른 함수를 매개변수로 활용하거나 반환값으로 활용)의 개념이 나타나있다. 여기에서 일급 시민 함수 개념이 비롯되었다.

a programming language is said to have first-class functions if it treats functions as first-class citizens. This means the language supports passing functions as arguments to other functions, returning them as the values from other functions, and assigning them to variables or storing them in data structures.

Swift 언어는 함수룰 일급 시민으로 설정해서 함수형 패턴을 구현했다.

따라서 함수를 type 그 자체로 활용해서 매개변수, 반환값 그리고 data로 할당하는 것이 가능하다. (함수를 마치 object처럼 활용)

변수, 상수, 데이터에 함수 할당

func checkBankInfo(bank: String) -> Bool {
	let bankList = ["신한", "카카오", "토스"]
    return bankList.contains(bank) ? true : false
}

//함수 실행, 반환값 변수에 할당
let funcResult = checkBankInfo(bank: "국민")

//함수 이름만 할당
let exampleFunc = checkBankInfo

//할당한 변수로 함수 호출
exampleFunc("국민")

이 경우, exampleFunc의 타입은 (String) -> Bool으로 함수 호출자 ( )를 붙여 할당한 변수 자체로 함수 호출이 가능하다.

문제는 overloading으로 동일한 이름의 함수가 여러개 존재할 경우, 변수로할당에 compile error가 나타난다.

overloadBindingToVariableError

마치 Array와 Set을 설정하는데 type annotation이 필요하듯이, 변수 타입을 해당 함수의 타입으로 명시하면 문제가 해결된다.

func hello(username: String) -> String {
	return "저는 \(username)입니다."
}

func hello(username: String, age: Int) -> String {
	return "저는 \(username), \(age)살 입니다."
}

let example: (String) -> String = hello

참고) 식별자를 활용하면 type annotation이 생략되어도 구분이 된다.

함수의 반환값 타입으로 활용

-> 뒤의 return값의 type으로 함수 타입을 활용할 수 있다. 마치 연속적으로 함수를 활용하는 것과 같다.

func yesCurrentBank() -> String {
	return "계좌 존재"
}

func noCurrentBank() -> String {
	return "계좌 존재하지 않음"
}

func checkBankInfo(bank: String) -> () -> String {
	let bankList = ["신한", "카카오", "토스"]
    //함수만 return, 해당 함수에서 실질적 수행
    return bankList.contains(bank) ? yesCurrentBank : noCurrentBank
}

//함수 실행
checkBank(bank: "토스")()

함수 안에 모든 기능을 다 넣기보단, 큰 함수를 작은 함수 모듈로 나눈다. 큰 함수는 작은 함수들이 어떤 기능을 하는지 알지 못한다. 함수 자체의 기능을 알기 위해선 해당 함수로 가야 알 수 있다. 각 함수가 하나의 기능만 하도록 하려는 함수형 패턴에서 비롯되었다.

함수의 매개변수 타입으로 활용

인자값으로 함수 타입의 instance를 활용할 수 있다.

func oddNumber() {
	print("It's odd number)
}

func evenNumber() {
	print("It's even number)
}

func resultNumber(number: Int, odd: () -> (), even: () -> Void) {
	return number.isMultiple(of: 2) ? even() : odd()
}

resultNumber(number: 3, odd: oddNumber, even: evenNumber)

여기서 매개변수로 전달된 함수를 call-back함수라고 한다. 해당 method의 수행이 끝나면, 시스템이 전달된 함수를 호출하도록 처리된다. 실질적 연산은 매개변수로 전달된 함수에서 이뤄진다.


closure로의 확장

위의 예시들은 모두 이름이 있는 함수들이다. 문제점은 사용하려는 함수가 많아질 수록 매번 함수에 이름을 설정할 수 없다는 점이다.

이름짓기가 제일 어려워...

programmerHardestTask

또한 함수 타입이 동일한 함수가 많을 경우, 잘못된 함수를 넘겨줄 수 있는 에러 가능성도 존재한다. (타입이 동일하므로 compile error가 나타나지 않고 실행 시 runtime error가 나타나 앱이 강제종료될 것이다.)

따라서 이름이 없는 함수를 활용하기 시작했으니 이를 closure라고 부르기 시작했다.

Closures
Group code that executes together, without creating a named function.

self-contained blocks of functionality that can be passed around and used in your code.

익명 함수로 동일 타입이면서 함수 자체 기능 구현으로 바로 들어갈 수 있다.

completionHandler와 같은 parameter로 활용가능하다.
네트워킹과 같은 비동기 프로그래밍과 자주 연계된다.

To Be Continued with closure와 비동기 정리...

profile
dev( iOS, React)

1개의 댓글

comment-user-thumbnail
2023년 8월 12일

좋은 글 감사합니다. 자주 방문할게요 :)

답글 달기