먼저 함수형 프로그래밍은 언어나 문법이 아니라 문제에 대한 접근 방식 중 하나이다. 구조적인 방법으로 문제를 분해하고 그들을 다시 조합하는 문제 접근 방식 중 하나
예제를 살펴보면서 알아보자
var persons: [Person] = []
for name in names {
let person = Person(name: name)
if person.isValid {
persons.append(person)
}
}
Person
타입의 빈 배열을 만들고, 유효한 person인지 비교를하여 배열의 삽입을 하는 코드이다.
이것은 더 간단해 질 수 있다. 기능별로 나누어 보자면,
var possiblePersons: [Person] = []
for name in names {
let person = Person(name: name)
possiblePersons.append(person)
}
var persons: [Person] = []
for person in possiblePersons {
if person.isValid {
persons.append(person)
}
}
이것은 두 가지 동작을 수행한다. 먼저 위에 블록에서는 Person
타입을 possiblePersons
에 삽입을 한 후, 밑의 블록에서 유효성 검사를 하여 Person
배열에 삽입을 해준다. 각각의 루프에서는 이전보다 적은 양의 업무를 담당하였다.
더 간단해 질 수도 있는데, 바로 map, filter
함수를 사용하는 것이다. map
은 특정 리스트를 다른 리스트로 변환한다. filter
는 리스트의 요소를 조건에 맞는지 확인해서 조건에 통과한 결과만을 반환해준다.
let possiblePersons = names.map(Person.init)
let persons = possiblePersons.filter { $0.isValid }
// 분리된 코드를 chaining 방식을 이용해서 합친 경우
let persons = names
.map(Person.init)
.filter { $0.isValid }
이러한 프로그래밍 습관을 배우고, 익숙해져야 한다. 이 방식은 persons
를 어떻게 만들지에 대해 초점을 맞추는 대신 persons
가 무엇인지 알려주기 때문이다.
이번에 우리가 한 방식을 순서대로 표현을 하자면,
이 방식은 Haskell 프로그래머들이 시도하는 방식과 비슷하다.
대부분의 함수형 프로그래밍 언어에서 compositon 기본 유닛은 함수이다. 그러나 swift의 composition 유닛은 type
이다. swift 에서 결합(compose)할 수 있는 것은 class, struct, enum, protocol
등이 존재한다.
// MyStruct(struct)와 Sequence(protocol)의 결합
// 이 결합으로 MyStruct는 Sequence의 모든 method를 활용 가능하다.
extension MyStruct<T>: Sequence {
func makeIterator() -> AnyIterator<T> {
return ...
}
}
단순한 조각들로부터 그들을 결합하고 조합할 수 있다.
Swift에서 주로 사용되는 또 다른 composition 방법은 type
에 문맥(context)를 추가하는 것이다. 가장 대표적인 것은 optional
이다. type
에 추가한 문맥 정보는 다음과 같다. "이것은 존재하는 것인가?" 문맥을 추가하는 것은 다른 추가 정보를 추적하는 다른 방법보다 훨씬 강력한 방법이다.
// No value: magic value
let noValue = -1
let n = 0
if n != noValue { ... }
// No value: context
let n: Int? = 0
if let n = n { ... }
integer
가 아니다 or 값이 없다는 사실을 효율적으로 판단하는 방법은 integer optional
로 변경하는 것이다
다음 예제를 살펴보자
func login(username: String, password: String,
completion: (String?, Error?) -> Void)
login(username: "rob", password: "s3cret") {
(token, error) in
if let token = token {
// success
} else if let error = error {
// failure
}
}
login
함수는 특정시점에 token
과 발생가능한 error
를 발생시키는 예제. 좀 더 간단하게 만들어보자.
Token
에 좀 더 문맥적인 의미를 부가하고 싶을 때(규칙을 가지고 있는 경우에) 이것을 좀 더 구조적(struct)으로 만들 수 있다. 그것이 우리가 struct
라고 부르는 이유이다.
struct Token {
let string: String
}
이렇게 바꾸는데에는 비용과 메모리, 참조가 요구되지 않는다. 그리고 규칙을 추가하는 것이 가능해지고, Token
과 같은 type
이 무엇인지도 명확하게 확인 가능하다(label, 주석 필요 x)
struct Credential {
var username: String
var password: String
}
위 예제에서 username, password
는 함께 결합하여 보내야 하므로 그 때 필요한 type
은 struct
이다.
구조체는 "and" type 이다
마지막으로 (Token?, Error?)
튜플을 인자로 전달하는 부분을 수정해보자. 튜플은 anonymous struct
라고 할 수 있다. 따라서 튜플은 "and" type
이다. 하지만 토큰과 에러는 "OR" type
으로 전달해야한다.
token과 error 둘다 전달을 받은 경우라면 ?
error의 종류에 따라 처리를 어떻게 할지 결정해야할 문제인것 같다. login 하는 경우이므로, 회원가입이 안되어 있거나, 이미 다른 환경에서 접속이 되어 있다면 error도 같이 전달을 받는 경우라고 생각을 한다. 이런 경우에서는 실패로 결과값을 반환하면 될 것 같다. 아직 정확하게는 모르겠어서 좀 더 알아봐야 겠다.
여기 예제는 token 또는(or) error 를 표현하고자 한다. 이 경우에 쓰이는 type으로는 enum
이 존재한다.
enum Result<Value> {
case success(Value)
case failure(Error)
}
단순히 value 였던 것들이 successful value, failing error
가 되었다
결과적으로 다음과 같다.
func login(credential: Credential,
completion: (Result<Token>) -> Void)
login(credential: credential) { result in
switch result {
case .success(let token): // success
case .failure(let error): // failure
}
}
복잡한 것은 작고, 간단한 것으로 분리할 수 있다. - 이것이 함수형 프로그래밍의 진정한 유산이며. 우리가 swift에서 활용해야 하는 것들이다.
우리는 이렇게 간단해진 것들로부터 일반적인 해결책을 찾을수 있을 것이다. 프로그램에 대해 생각하고 판단하게 만드는 일관된 규칙을 적용하여 다시 이들을 결합 시킬 수 있다
분리한 다음, 다시 결합하세요 ! (Break it down, build it up)
야무지네요