[iOS] 함수형 프로그래밍 (Functional Programming)

Zoe·2022년 7월 28일
0

iOS

목록 보기
8/39

함수형 프로그래밍 (Functional Programming)


✅ 함수형 프로그래밍이란?

  • 객체지향과 아예 대립은 아니다
  • 빅데이터를 관리하는 hadoop 프레임워크에서 사용하는 스칼라도 함수형인 동시에 객체지향 언어
  • 함수형 프로그래밍은 프로그래머에게 새로운 패러다임을 제시

1️⃣ 특징

  1. 인풋과 아웃풋이 있다! -> 중간 재료들을 넘겨주면 각자 맡은 작업을 해서 결과물을 반환
  2. 외부 환경으로부터 철저히 독립적이다! -> 다른 외부 환경에 뭘 적어두거나, 참조하지 않음. 오로지 자신에게 주어진 것들로만 작업.
  3. 같은 인풋에 있어서 언제나 동일한 아웃풋을 생산! -> 순수 함수. 같은 양과 종류의 카카오를 넣어주면 언제나 같은 수와 품질의 초콜릿이 만들어진다는 개념.

🌟 비 함수형에서 다른 결과값이 발생하는 이유 : 공장장이 적어둔 숫자를 다른 외부인이 건드리거나 적어두는 사람과 확인하고 수정하는 사람의 타이밍이 엇갈림. (부작용 : 어떤 함수의 동작에 의해 프로그램 내 특정상태가 변경되는 상황) -> 함수형이 주목받는 이유는 이러한 부작용에 의한 문제로부터 보다 자유롭기 때문!

2️⃣ 선언형

  • 인풋이 같으면 아웃풋도 같다!
  • 함수도 변수처럼 생각. 함수를 값으로 보고 프로그래밍
  • 함수를 값으로 봐서 뭘 하려는 걸까? 클로저, 고차함수!

3️⃣ 커링 (Currying)

  • 파라미터 3개를 받는 함수 하나를, 파라미터 하나를 받는 함수 3개로 쪼갠 것
func _calculate(_ method: (Int, Int) -> Int, v1: Int, v2: Int) -> Int {
	return method(v1, v2)
}

//함수를 return하는 함수를 return하는 함수
func calculate(_ method: @escaping (Int, Int) -> Int) -> (Int) -> (Int) -> Int {
	return { v1 in
    	return { v2 in
        	return method(v1, v2)
        }
    }
}

calculate(+)(10)(3)

let adder = calculate(+)
adder(5)(3)
let add10 = adder(10)
add10(3)
add10(5)

let multi = calculate(*)
multi(2)(3)
let double = multi(2)
double(15)
double(20)

4️⃣ 함수 컴비네이션

  • 어떤 것을 갖다 붙이면 구현이 될까?
  • for문과 같은 반복문은 특정 변수의 상태 변화, 즉 부수효과를 필요로 함 -> 함수형 프로그래밍에서는 부수효과를 발생시키지 않는 재귀함수를 많이 사용

✅ 본질적인 함수형 프로그래밍

  • 함수를 이용해서 사이드 이펙트 없도록 선언형 프로그래밍을 하는 것이다!!

1️⃣ Function

// non-FP
account.deposit()
user.login()

// FP
// 함수를 먼저 쓰고 그 안에 데이터를 집어넣는 것
deposit(account)
user(User)

2️⃣ No Side-Effect

Modularization

  • OOP에서는 Object들의 연관관계로 프로그램이 진행됨
  • 어떤 하나의 기능을 하는 Object끼리 모으면 그것을 하나의 모듈이라고 함
  • 모듈의 최소 단위는 Object하나 Object는 저장 속성과 메서드로 나뉨
  • 메서드가 실행될 때는 저장 속성을 사용해서 실행되고, 상태를 바꾸게 됨
  • 즉 Object 하나가 State를 가지는 것

Stateless

  • 함수를 사용 : 인풋과 아웃풋이 있고, 아웃풋들이 서로 연결이 돼서 하나의 큰 아웃풋을 만드는 것
  • 여기서 함수는 순수함수, 같은 인풋에 대해 같은 아웃풋을 가지는 것
  • 따라서 state가 없음! 어떠한 상태에 따라 수행값이 달라지는 것이 아니므로!
  • 모듈화의 최소 단위는 function 하나이다.

🌟 가장 큰 차이점은 state가 있냐 없냐의 차이! state가 없도록 하는 것이 FP의 본질이다. 그것이 Side-Effect가 없다고 표현한 것이다.

3️⃣ Declarative

  • 명령형(How) : 나는 어떤 과정을 통해 결과를 얻고 싶어
  • 선언형(What) : 나는 어떤 결과를 얻고 싶어

4️⃣ 예제 : Fizzbuzz

var i = 1
while i <= 100 {
	if i % 3 == 0, i % 5 == 0 {
    	print("fizzbuzz")
    } else if i % 3 == 0 {
    	print("fizz")
    } else if i % 5 == 0 {
    	print("buzz")
    } else {
    	print("\(i)")
    }
    i += 1
}

Avoid side-effect

  • state를 없애기
(1...100).forEach { i in
	if i % 3 == 0, i % 5 == 0 {
    	print("fizzbuzz")
    } else if i % 3 == 0 {
    	print("fizz")
    } else if i % 5 == 0 {
    	print("buzz")
    } else {
    	print("\(i)")
    }
}

DRY

  • 함수를 이용하기
let fizz: (Int) -> String = { i in i % 3 == 0 ? "fizz" : ""}
let buzz: (Int) -> String = { i in i % 5 == 0 ? "buzz" : ""}

(1...100).forEach { i in
	let fizzbuzz = fizz(i) + buzz(i)
    let output = fizzbuzz.isEmpty ? "\(i)" : fizzbuzz
    print(output)
}

Declarative

  • 선언형으로 바꾸기
let fizz: (Int) -> String = { i in i % 3 == 0 ? "fizz" : ""}
let buzz: (Int) -> String = { i in i % 5 == 0 ? "buzz" : ""}

//fizzbuzz 비교하는 함수
let fizzbuzz: (Int) -> String =
	{ i in { s in s.isEmpty ? "\(i)" : s }(fizz(i) + buzz(i)) }

let log: (String) -> () = { print($0) } // 출력하는 함수

(1...100).map(fizzbuzz).forEach(log) // 이것이 바로 선언형!
  • 과정은 몰라도 내가 원하는 것을 말하는 것이다.

최종 코드

  • 타입 추론 이용하기
let fizz = { $0 % 3 == 0 ? "fizz" : ""}
let buzz = { $0 % 5 == 0 ? "buzz" : ""}

//fizzbuzz 비교하는 함수
let fizzbuzz =
	{ i in { $0.isEmpty ? "\(i)" : $0 }(fizz(i) + buzz(i)) }

let output = { print($0) } // 출력하는 함수

(1...100).map(fizzbuzz).forEach(output) // 이것이 바로 선언형!

One more step

  • Monad : 기술이 아니라 개념에 관한 것. 하나로 감싼 개념
  • Swift에서는 옵셔널!
  • 값을 가지고 있거나 nil이거나 하는 것을 하나로 감쌌다!
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 fizz = { $0 % 3 == 0 ? "fizz" : nil}
let buzz = { $0 % 5 == 0 ? "buzz" : nil}
let fizzbuzz = { i in fizz(i) + buzz(i) ?? "\(i)" }
let output = { print($0 ?? "") } // 출력하는 함수

(1...100).map(fizzbuzz).forEach(output) // 이것이 바로 선언형!

One More

  • 숫자를 문자로 바꿔주는 함수 사용
let i2s: (Int) -> String = { "\($0)" }

let fizz = { $0 % 3 == 0 ? "fizz" : nil }
let buzz = { $0 % 5 == 0 ? "buzz" : nil }
let fizzbuzz = { i in fizz(i) + buzz(i) ?? i2s(i) }
let output = { print($0 ?? "") } // 출력하는 함수

(1...100).map(fizzbuzz).forEach(output) // 이것이 바로 선언형!

🌟 함수가 제네릭으로 구현이 되어 있어서 재사용이 가능하다. 따라서, 개발자가 필요할 때마다 갖다가 쓰면 된다. 개발자는 이미 만들어진 함수형 라이브러리를 가지고 프로그래밍을 하는 것이다.
🌟 위의 i2s 함수에서 생길 버그가 무엇이 있을까? 함수는 짧으면 짧을 수록 즉, 기능의 단위가 작을 수록 생길 버그가 적어진다. 작게 만들어진 함수들을 구성하고 연결해서 개발자가 원하는 기능을 구현하는 것이다.
🌟 신뢰도가 높은 함수들도 구성된 프로그램은 결국 신뢰도가 높을 것이다.

profile
iOS 개발자😺

0개의 댓글