본문 바로가기

Compute

FUNCTIONAL PROGRAMMING IN SCALA . CHAPTER 1

1. 함수형 프로그램이란?
전제조건 – 프로그램을 순수 함수(pure function)들로만 구축한다는 뜻 (side effect가 없는 함수)

. 변수를수정한다
. 자료 구조를 제자리에서 수정한다
. 객체의 필드를설정한다.
. 예외(exception)를 던지거나 오류를 내면서 실행을 중단한다. 콘솔에 출력하거나사용자의 입력을 읽어들인다.
. 파일에 기록하거나 파일에서 읽어들인다.
. 화면에 그린다

이런 일들을 할수 없는 상태에서 프로그램을 짠다는것.

함수형 프로그램은 , 프로그램을 작성하는 방식에 대한 제약.
표현 가능한 프로그램의 종류에 대한 제약이 아님.

순수함수로만 이루어진 프로그램은 모듈성이 높아지고 , test, 재사용,병렬화,일반화,분석이 쉬워진다.

1.1 FP의 이점 (예제)

class Cafe {
 def buyCoffee(cc : CreditCard) : Coffee = { #buyCoffee는 함수명 , cc는 인수 
 val cup = new Coffee()
 cc.charge(cup.price) #부수효과가 발생하는 코드 (신용카드를 실제로 청구)
 cup #return
 }
}

– 함수의 목적은 Coffee를 되돌려주는 작업을 함.
– 그외의 모든 동작(사양)은 부수적으로 일어남 (on the side)
– test가 힘든 상황. (실제 대금을 청구해야하기때문)

개선1

class Cafe {
 def buyCoffee(cc : CreditCard , p : Payments) : Coffee = {
 val cup = new Coffee()
 p.charge(cc, cup.price)
 cup
 }
}

– 테스트가 힘들었던 결제(대금을 청구하는)로직을 변수로 받아오도록 수정
문제
– 커피가 생각했던 가격으로 결제가 되었는지 확인하려면? ※즉 외부환경의 변이를 체크해야함
– 만약 한 손님이 커피를 동시에 12잔을 신청했다면? 루프를 통해 12번 buyCoffee를 호출, 결제또한 12번 호출이 됨. 카드수수료도 12배.

해결방법
– buyCoffes 라는 새로운 함수를 작성
– 하지만 결제코드가 복잡해진다면 이런 방식의 처리는 한계가 있고, 코드자체의 재사용성에도 좋지 않음.

1.1.2 함수적 해법 : 부수 효과의 제거

개선2

class Cafe {
 def buyCoffee(cc : CreditCard) : (Coffee , Charge) = { #buyCoffee는 Coffee와 Charge의 pair를 돌려준다. 이제 결제와 이 함수와는 무관해짐
 val cup = new Coffee()
 (cup , Charge(cc, cup.price))
 }
}

– buyCoffee함수에서 청구(구매요청)을 ‘처리’ ‘연동’의 문제로 분리되었음.

case class Charge (cc : CreditCard , amount : Double) { #case class는 자료구조 (swift의 struct)
 def combine(other : Charge) : Charge = 
 if (cc = other.cc )
 Charge(cc , amount + other.amount) #case class 는 new 키워드 없이 생성가능.
 else
 throw new Exception(“Can’t combine charge to different cards”)
}

– Charge는 CreditCard와 amount 변수를 가지고 있는 자료형
– combine란 함수를 통해 CreditCard가 동일 할 경우에 청구를 취합할수가 있음

개선3

class Cafe {
 def buyCoffee (cc : CreditCard) : (Coffee, Charge) = … #생략
 def buyCoffees (cc : CreditCard , n : Int ) : (List[Coffee],Charge) = {
 val purchases: List[(Coffee , Charge)] = List.fill(n)(buyCoffee(cc)) #List.fill(n)(x)는 x의 복사본 n개로 이루어진 List를 생성
 val (coffees , charges) = purchases.unzip (coffees,charges.reduce((c1,c2) => c1.combine(c2))) 
 #unzip은 pair의 List를 List의 pair로 분리
 #charge.reduce는 청구(Charge) 2건을 combine을 이용해서 하나로 결합하는 과정을 반복. 최종적으로 하나의 청구건으로 환원(reduce)
 }
}

– buyCoffee를 재활용해서 buyCoffees함수를 정의할수가 있음.
– Payments 인터페이스를 구현하지 않아도 쉽게 테스트가 가능
– Cafe는 Charge가 어떻게 결제되는지 모름. 또는 알 필요가 없음

케이스
– 커피숍에서 몇시간 일하면서 음료수를 여러잔 주문했을때 , Charge가 first-class이기 때문에 쉽게 재활용이 가능해짐. (함수를 인수로 주고 받음)

def coalesce (charges : List[Charge]) : List[Charge] = charges.groupBy(_.cc).values.map(_.reduce(_ combine _)).toList

– 같은 CreditCard의 청구를 하나의 List[Charge]로 취합해준다.
– _.cc 와 _ combine _는 익명 함수(anonymous function)를 위한 구문

– 처음엔 읽기 어려울수 있지만 , 적응되면 쉽게 읽을수 있음.
– 재활용 및 테스트가 쉽다.

– 위와 같은 기능을 처음의 코드로 구현한다고한다면?

– FP는 좋다고 생각하는 기능을 논리의 극한까지 밀어붙이는 , 또 그 기능의 적용이 어려워 보이는 상황에서도 적용하는 규율
– FP의 스타일은 다른 프로그래밍의 방법과 상당히 다르지만 , 아름답고 응집력있는 프로그래밍의 접근방식

Etc
– 실제 세계에서 위와 같이 부수효과가없는 코드를 작성할수가 있을까?

– 일반적으로 부수효과가 있는 임의의 함수에 이런 종류의 변환을 적용함으로서 부수효과를 프로그램의 외부계층으로 밀어낼수가 있다!

– 언젠가는 외부에 실제로 효과를 가해야한다. 예를들면 Charge를 외부의 시스템에 의해 처리되게 만들 필요가 있다.
– 하지만 부수효과가 필요한 프로그램에도 함수적 코드로서 짜는 방식이 가능하다.
– 즉 부수효과가 발생하지만 ‘관찰되지 않도록’ 코드의 구조를 짜는 방법은 찾는것이 가능하다.
– 예를들면 함수에서 지역적으로 선언된 자료가 함수외부에서 참조되지 않는다는것을 보장할수있다면 , 변이가 가능
– 함수를 감싸는 다른 함수가 알아채지 못한다면, 함수 안에서 화일에 무엇인가를 기록할 수 있다.