[함수형사고] 3. 양도하라

반복처리에서 고계함수로-

클로저란 함수의 예로 표현

def Closure makeCounter(){
  def local_variable = 0
  return{return local_variable += 1}
}

c1 = makeCounter()
c1()
c1()
c1()

c2 = makeCounter()

printIn "C1 = ${c1()}, C2 = ${c2()}"
//C1 = 4, C2 = 1

만약 이 부분을 자바로 짠다면 어떻게 될까?

import java.util.List;
import java.util.ArrayList;

// BEGIN counter_demo
class Counter {
    public int varField;

    Counter(int var) {
        varField = var;
    }

    public static Counter makeCounter() {
        return new Counter(0);
    }

    public int execute() {
        return ++varField;
    }
}
// END counter_demo

class AnonCounter {
    public int varField;

    public static AnonCounter makeCounter() {
        final int var = 0;
        return new AnonCounter() ;
    }

    public int execute() {
        return ++varField;
    }
}

interface ListerFunc {
    public int call(int arg);
    public int getSum();
}

class Adder implements ListerFunc {
    private int sum = 0;

    public int getSum() {
        return sum;
    }

    Adder() {
        sum = 0;
    }

    public int call(int arg) {
        return sum += arg;
    }
}

class ListApplier {
    public static int apply(List<Integer> list, ListerFunc f) {
        for (int i : list)
            f.call(i);
        return f.getSum();
    }
}

public class CounterDemo {

    public CounterDemo() {
        Counter c1, c2;
        c1 = Counter.makeCounter();
        c1.execute();
        c1.execute();
        c1.execute();

        c2 = Counter.makeCounter();
        System.out.println("c1 = " + c1.execute() + ", c2 = " + c2.execute());

        AnonCounter c3 = AnonCounter.makeCounter();
        System.out.println("c3 (1) = " + c3.execute() + ", c3 (2) = " + c3.execute());

        List<Integer> list = new ArrayList<Integer>();
        for (int i = 0; i < 100; i++)
            list.add(i);

        int sum = ListApplier.apply(list, new Adder());
        System.out.println("sum = " + sum);
    }

    public static void main(String[] args) {
        new CounterDemo();
    }
}

커링과 부분 적용

커링과 부분 적용은 함수나 메서드의 인수의 개수를 조작할 수 있게 해준다. 주로 인수 일부에 기본값을 주는 방법을 사용한다. 이를 인수 고정이라고도 부른다. 대부분의 함수형 언어는 커링과 부분적용을 지원하지만 구현 방법은 가지각색이다.

정의와 차이점

커링이나 부분 적용 모두 몃몃 인수의 값만 주면 인수가 몃 개 빠져도 호출할 수 있는 함수를 리턴해준다. 다만 커링은 체인의 다음 함수를 리턴하는 반면에, 부분 적용은 주어진 값을 인수에 바인딩시켜서 인수가 더 적은 하나의 함수를 만들어준다. 이 차이점은 인수의 수가 둘 이상인 함수를 살펴보면 명확해진다.

예를 들자면 process(x,y,z)의 완전히 커링된 버전은 process(x)(y)(z)이다. 여기에서 process(x)와 process(x)(y)는 인수가 하나인 함수이다. 첫 인수만 커링을 하면 process(x)의 리턴 값은 인수가 하나인 또하나의 함수이다. 이 함수의 리턴 값은 또 하나의 일인 수 함수이다. 반면에 부분 적용을 사용하여 변환하면 인수 숫자가 적은 함수가 남는다. process(x,y,z)의 인수 하나를 부분 적용하면 인수 두 개짜리의 process(y,z)가 된다.

인수 커링
object CurryTest extends App{
  
  def filter(xs:List[Int], p:Int => Boolean): List[Int] =
    if(xs.isEmpty) xs
    else if (p(xs.head)) xs.head :: filter(xs.tail, p)
    else filter(xs.tail, p)
  
 	def modN(n:Int)(x: Int) = ((x%n) == 0)
 
}
부분 적용
def price(product: String) : Double =
	product match {
    case "apples" => 140
    case "oranges" => 223
  }

def withTax(cost: Double, state: String): Double =
state match {
  case "NY" => cost * 2
  case "FL" => cost * 3
}

val locallyTaxed = withTax(_: Double, "NY")
val costOfApples = locallyTaxed(price("apples"))
보편적 용례

정의도 까다롭고 구현 방법도 다양하지만, 커링과 부분 적용은 실제로 프로그래밍에서 큰 자리를 차지한다.

함수 팩토리

커링(또는 부분적용)은 전통적인 객체지향 언어에서 팩토리 함수(factory function)을 구현할 사용하면 좋다. 이를 보여주는 게 아래와 같다,

def adder = {x,y -> x+y}
def incrementer = adder.curry(1)

printIn "increment 7 : ${incrementer(7)}"// 8

| 템플릿 메서드 팩토리 | 구현의 유연성을 보장하기 위해서 내부의 추상 메서드를 사용하는 겉껍질을 정의하는데 있다. 부분적용과 커링이 이 문제를 해결하는 데 사용된다. 부분 적용을 사용하여 이미 알려진 기능을 제공하고 나머지 인수들은 추후에 구현하도록 남겨두는 것은 이 객체지향 디자인 패턴을 구현하는 것과 흡사하다.

| 묵시적 값 | 비슷한 인수 값들로 여러 함수를 연속적으로 불러올 때에는 커링을 사용하여 묵시적 인수값을 제공할 수가 있다. 일례로 자료 저장 프레임워크를 사용한다면, 데이터 소스를 첫 번째 인수로 넘겨야 한다. 다음 소스의 부분 적용을 이용하면 이 값을 묵시적으로 제공할 수 있다

(defn db-connect [data-source query params] …). . (def dbc (partial db-connect “db/some-data-source”)). . (dbc “select * from %1” “cust”)

재귀

위키디피아에 따르면 재귀란 자신을 재참조하여 같은 프로세스를 반복하는 것을 말한다. 이 또한 런타임에 세부사항을 양도하는 예가 되고, 그래서 함수형 프로그래밍과 깊은 관계가 있다. 구체적으로 말하면 재귀는 같은 메서드를 반복해서 호출하여 컬렉션을 각 단계마다 줄여가면서 반복 처리하는 것을 말한다. 이떄 조심해야 할 것은 항상 종료 조건을 보장해야 한다는 점이다. 재귀를 사용한 코드는 이해가 쉬운데, 그것은 문제의 핵심이 같은 작업을 게속하여서 목록을 줄여나가기 때문이다.

목록의 재조명

일반적으로 우리는 배열의 0,1,2, 등의 인덱스를 고려하고, 어디에 뭐가 있을지 배열의 큰 그림을 그리려고 노력하는 반면에 함수형 언어는 목록을 다른 각도에서 바라본다. 목록을 색인된 위치의 컬렉션으로 여기는 대신에, 첫 요소와 나머지의 조합으로 생각한다!