[함수형 프로그래밍] 일급 함수

2020. 4. 4. 22:38iOS

* 이 글은 함수형 프로그래밍 스터디를 하면서 적었던 글로써 사실과 다를 수도 있음..

일급 함수?

  • 함수 자체를 값으로써 다룰 수 있다.
  • 변수에 할당할 수 있어야 한다.
  • 인자로 전달할 수 있어야 한다.
  • 반환 값으로 전달할 수 있다.

Function composition (함수의 합성)

  • 함수의 반환값이 다른 함수의 입력값으로 사용되는 것
  • 함수가 합성되기 위해서는 함수의 반환값과 반환값을 받아들이는 값은 타입이 서로 같아야함
  • 순수 함수를 만들어두고 그것을 조합 및 재활용
  • 함수 단위의 재활용성이 높아진다
  • 코드를 읽기 쉬워진다. ← 이건 아직 잘 모르겠다
  • 함수 단위의 테스트가 쉬워진다
  • 버그의 감소 ← 일단 써봐야 알겠다.
    func increment(_ value: Int) -> Int {
        return value + 1
    }

    func message(with age: Int) -> String {
        return "2020년에 \(age)살이 되었다."
    }

    func compositor(increment: @escaping (Int) -> Int,
                   message: @escaping (Int) -> String) -> (Int) -> String {
        return { (age: Int) in  // age -> compositor의 파라메터
            return message(increment(age))
        }
    }

    let composited: ((Int) -> String) = compositor(increment: increment(_:), message: message(with:))
    let result = composited(26)
    print(result) // 27살이 되었다.
  • @escaping을 쓰는 이유 - 전달인자로 받은 함수가 탈출 후 사라질 수 있기 때문에

Async Result (비동기 반환)

  • 결과는 나중에 생길 때 전달 받고, 프로그램의 수행을 멈추지 않음
  • 시간이 걸리는 작업이나 네트워크를 통한 결과를 얻을 경우 비동기 방식으로 구현하는 것이 좋음
  • 결과가 리턴값으로 전달 되는 것이 아니라 전달받은 함수를 호출함으로써 전달
  • 결과가 발생하는 시점에 수행
    func f(_ num: [Int]) -> Int {
        sleep(1)
        let sum = num.reduce(0, +)
        return sum
    }

    // async를 사용하는 경우 
    func asyncF(_ num: [Int], onCompletion: @escaping (Int) -> Void) {
            DispatchQueue.main.async {
            sleep(1)
            let sum = num.reduce(0, +)
            print(sum)
            onCompletion(sum)
        }
    }

    dispatchMain() // 커맨드라인에서는 이거 안하면 async 안돌아감

Currying (커링)

  • 여러 개의 파라미터를 받는 함수를 하나의 파라미터를 받는 여러 개의 함수로 쪼개는 것
  • 함수들이 서로 chain을 이루면서 연속적으로 연결되려면 output과 input의 타입과 갯수가 같아야한다.
  • 함수의 합성을 원활하게 하기 위해서 커링을 사용
    func curry<A, B, C>(f: (A, B) -> C) -> (A -> (B -> C)) {
      return { a: A in
                { b: B in
                  return f(a, b) // returns C
                }

    }

    func add(x: Int, y: Int) {
        return x + y
    }

    add(1,2)

    func filterWithNum(_ n: Int) -> ([Int]) -> [Int] {
        return { b in
            return b.filter { $0 % n == 0}
        }
    }

    func filterArray(_ nums: [Int], _ r: Int) -> [Int] {
        let filteredArr = filterWithNum(r)
        return filteredArr(nums)
    }
    print(filterArray([1,2,3,4], 2))   // [2, 4]


    // 함수의 실행을 늦추고 싶을경우 나중에 인자를 받으면 실행 된다.
    // 인자를 고정

Partial Application (부분 적용)

  • 여러개의 인자를 받는 함수가 있을 때 일부의 인자를 고정한 함수를 만드는 기법
  • 다인수 함수를 생략될 인수의 값을 미리 정해서 더 적은 수의 인수를 받는 하나의 함수로 변형하는 방법

커링과 부분 적용의 차이점

  • 커링은 체인의 다음 함수를 리턴하는 반면, 부분 적용은 인수가 더 적은 하나의 함수를 만들어준다.
  • 인수가 둘일 경우 사실상 결과가 동일한데, 인수가 셋일 경우 차이점이 명확해진다.여기서 process(x)와 porcess(x)(y)는 인수가 하나인 함수이다. 첫 인수만 커링을 하면 process(x)의 리턴 값은 인수가 하나인(여기서는 y) 또 하나의 함수이다.반면 부분 적용을 사용하여 변환하면 인수 숫자가 적은 함수가 남는다.
  • process(x, y, z)의 인수 하나를 부분 적용하면 인수 두개짜리의 process(y, z)가 된다.
  • 이 함수의 리턴 값은 또 하나의 일인수 함수이다.
  • 예) process(x, y, z) 의 완전한 커링 버전은 process(x)(y)(z)의 체인구조이다.

Memoization (메모이제이션)

  • 프로그래밍 언어에 내장되어 반복되는 함수의 리턴 값을 자동으로 캐싱해주는 기능
  • 언어 자체에서 제공하는 캐시 기능이기 때문에 최적화된 성능으로 코드를 거의 바꾸지 않고 캐시 혜택을 누릴 수 있다
  • 함수형 언어에서는 보편적으로 메모이제이션을 내장해서 제공
  • 단, 사용할 때 함수에 부수 효과가 없어야한다. 그렇지 않으면 캐시된 값이 리턴되어도 그 코드를 믿을수 없기 때문이다

13장 클로저 incrementer(값 획득 부분)

[Swift] Memoization

참고

[Swift] Swift5의 Result Type 사용하기

How to use Result in Swift