Programer/iOS

[FP] Imperative -> Declarative 예제를 통해 바꿔보기 (FizzBuzz)

아즈샤 2018. 7. 16. 17:00
반응형

예전 포스팅 중  Imperative -> Declarative 예제를 좀 더 진행해보려고 합니다.

아래는 전에 보여드렸던  Imperative vs Declarative 예제입니다.


let fizz: (Int) -> String = { i in i % 3 == 0 ? "fizz" : "" }

let buzz: (Int) -> String = { i in i % 5 == 0 ? "buzz" : ""}

let fizzbuzz = { i in { $0.isEmpty ? "\(i)": $0}(fizz(i) + buzz(i)) }

let output  = { print($0) }


(1...100).map(fizzbuzz).forEach(output)



1. fizz(i) + buzz(i) 부분을 개선하고자 아래와 같이 + 함수를 만들어 주고, "\(i)" 부분을 i2s라는 함수로 만들어보겠습니다.


func + (_ s1: String?, _ s2: String?) -> (String?) {

    if s1 == nil, s2 == nil { return nil }

    if s1 != nil, s2 == nil { return s1 }

    if s1 == nil, s2 != nil { return s2 }

    return s1! + s2!


}


let i2s: (Int) -> String = { "\($0)" }

.

.

let fizzbuzz = { i in fizz(i) + buzz(i) ?? i2s(i) }

.

.


(1...100).map(fizzbuzz).forEach(output)


+ 함수와 i2s의 함수를 만들어주면서 fizzbuzz 함수는 더욱 더 간결하게 바뀌었습니다!


2. 이번에는 배열과 함수 받아 forEach와 함께 함수를 실행하는 iterate 함수를 만들어 보겠습니다. 


func iterate<A>(_ arr: [A], _ f: ((A) ->())) {

    arr.forEach({ f($0) })

}


iterate(Array(1...100), {i in output(fizzbuzz(i))})



생성한 iterate 함수로 1부터 100까지의 배열를 받아 그 배열을 순서대로 output 함수를 실행시킵니다.

output 함수는 fizzbuzz에 forEach의 결과값 i를 넣은 결과 값을 리턴해줍니다!

근데, output(fizzbuzz()) 형태는 뭔가 거북합니다. 실행되는 순서 반대로 되어 있잖나요?


3. 그럼 f함수 g함수 두개를 받아서 f함수를 g함수의 부분 함수로 만드는 pipe 합성함수를 만들어 보겠습니다. 


func pipe <A, B, C>(_ f: @escaping (A) -> B, _ g: @escaping (B) -> C) -> (A) -> C {

    return { a in g(f(a))}

}


iterate(Array(1...100), pipe(fizzbuzz, output))


기존 g(f(x)) 형태를 pipe함수로 pipe(f(x), g(y)) 로 변경하였습니다.

이 함수는 연결되어 있다. (순차적으로 실행한다.) 라는 생각으로 개발할 수 있겠네요!


4. 자, pipe 함수를 infix ~> 로 변경해 봅시다.


precedencegroup Action {

    associativity: left

}

infix operator ~>: Action


func ~> <A, B, C>(_ f: @escaping (A) -> B, _ g: @escaping (B) -> C) -> (A) -> C {

    return { a in g(f(a))}

}


iterate(Array(1...100), fizzbuzz ~> output)


좀 더 보기 편해졌습니다! pipe(f(x), g(y)) 형태를 ~> 로 간편하게 변경했습니다.

어!? 이제 뭔가 읽기가 편해지지 않았나요? "fizzbuzz를 실행하고 output로 반환해라" 이제 제대로 순서대로 보입니다 ㅎㅎ

5. 마지막으로, 새로운 함수 cap 를 추가해 봅시다 ^^;


let cap: (String?) -> String? = { $0?.capitalized }

iterate(Array(1...100), fizzbuzz ~> cap ~> output)


훨씬 더 이해해하기 쉬워졌죠?

fizzbuzz를 실행하고 cap를 실행하고 output를 실행하고 좀 더 Declarative 하게 작성할 수 있도록 변경되었습니다!


5. 결론

- 결국 fizz, buzz, fizzbuzz 함수만으로 선언형 프로그래밍을 할 수 있었습니다.

- 위 함수를 제외하면 다른 함수들은 라이브러리처럼 재사용이 가능합니다.

- 이러한 함수들을 모아서 사용한다면 간결하고 이해하기 쉽게 프로그래밍을 할 수 있습니다.

-> No Side Effect, Bug를 줄일 수 있고, 이해하기 쉽게 프로그래밍 할 수 있다는 장점이 있습니다.


참고링크

https://iosdevkor.github.io/let_us_go_2018_spring_review/ 에서 "곰튀김님의 Functinal Programing이 뭐하는 건가요?"

http://azsha.tistory.com/99?category=743068 [FP] Functional Programing 용어 정리


반응형