반응형
필자의 생각
- 비즈니스 로직 복잡도가 높아짐에 따라 사이드 이펙트를 유지 보수성에 따라서 함수형이 뜨고 있습니다
- 필자는 Java카테고리에 넣기에는 애매한 부분이 있지만 다른 카테고리에 넣기 애매해서 java category에 넣는 걸 결정 했습니다
1. 함수형 프로그래밍
등장배경
- 비즈니스 로직이 복잡해 지기 때문에 여러 코드와 비즈니스 로직이 스파게티와 같이 엉키는 이슈가 자주 발생
- 사이드 이펙트와 같은 유지 보수에 어려움을 유발하는 요소들이 많이 발생하게 되었습니다.
- 사이드 이팩트를 제거 하기 위해서 일관적인 로직을 순수 함수로 정의하여 가독성을 높이고 유지 보수에 편리함을 제공하기 위해 나왔습니다.
2. 함수형 프로그래밍의 특징
순수 함수
- 정의
- 부수효과가 없는 함수 즉, 어떤 함수에 동일한 인자를 주었을 때 항상 같은 값을 리턴하는 함수이며, 외부의 상태를 변경하지 않는 함수(참조 투명성)
- cf) 부수 함수: 외부의 상태를 변경하는 것 또는 함수로 들어온 인자의 상태를 직접 변경하는 것을 의미
- 참조 투명성
- 동일한 값이 들어가면 동일한 반환값이 보장 되야함(멱등성)
- 기존 값은 변경 되면 안된다(Immutable Data)
- 외부 사이드 이펙트가 없는 것을 의미함
- 이때 부수 효과가 없는/참조 투명성이 보장된 순수한 함수를 1급 객체로 간주하여 파라미터로 전달 및 반환 합니다.
- 1급 객체/함수
- 정의: 1급 시민의 조건을 충족하는 객체를 의미
- 1급 시민
- 변수에 담을 수 있어야한다.
- 함수/method의 인자로 전달할 수 있다
- 함수/method에 반환값이 될 수 있다
- Method도 1등 함수가 될 수 있습니다.
- js/swift/kotlin에서는 지원하고 있습니다.
- Java에서는 Functional interface를 람다식으로 사용해서 1급 함수 처럼 사용합니다.
- 예를 들어서 10개의 인자가 필요한 비즈니스 로직이 있습니다.
- 만약 이 함수를 실행하기 위해서 10인자 모두 준비될 때까지 아무 것도 하지 못합니다.
- 그리고 read ability가 나빠 집니다.
- 여러 함수로 나눌 수 있지만 이걸 함수형 으로 인자를 받아서 처리하는 형태로 구현할 수 있습니다.
- 인자의 일부분을 처리하는 함수를 여러개 만들어서 내부에 호출 하는 방식으로 작성하면 코드가 좀 더 간결해 집니다.
- js/swift/kotlin에서는 지원하고 있습니다.
- 사이드 이팩트 없는 순수 함수를 1급 객체로 간주
- 사이드 이팩트 발생 케이스
- 기존의 변수의 값이 변경됨
- 자료 구조의 내부 값을 수정함
- 객체의 필드 값을 설정함(setter 호출)
- 콘솔 또는 파일 I/O발생
- 예외나 오류가 발생하며 실행이 중단 됨
- 사이드 이팩트 발생 케이스
- 1급 객체를 파라미터로 넘기거나, 반환 값으로 사용 가능합니다
- 1급객체의 특징
- 변수나 데이터 구조 안에 담을 수 있는 객체
- 파라미터로 전달할 수 있는 객체
- 반환 값으로 사용할 수 있는 객체
- 할당에 사용된 이름과 무관하게 고유하게 식별될 수 있는 객체
- 1급 객체 함수에서 중간에 대입문이 없습니다: 어떤 함수를 통해서 결과를 뱉어 내는 과정에서 중간에 연산에서 변수를 선언할 필요가 없는 것입니다. -> 고차 함수들을 적절히 활용 하기 때문에 중간에 변수를 선언하고 대입할 필요가 없습니다.
불변성
- 정의: 변하지 않는 값을 의미합니다.
- 예시
- 대표적으로 프리미티브 타입을 생각할 수 있습니다
- int type은 다른 변수에 집어 넣게 되면 참조가 이뤄지지 않고 새로운 int 형 데이터가 복사되어서 사용 됩니다.
- 위와 같이 프리미티브 타입처럼 함수에 input 값을 들어가는 값은 변하면 안되며, input 값과 output 값은 다름 메모리에 할당된 객체야 한다는 것을 의미 합니다
고차 함수
- 정의: 함수 인자를 전달 받거나, 함수를 결과로 반환 하는 함수를 의미
- 종류
- reduce
- map
- filter
- functional interface
- ...etc
클로저
- 정의
- 선언할 당시의 context를 기억했다가, 나중에 호출될 때 저장된 context정보에 따라 호출 되는 함수
- 함수 outer 함수의 파라미터를 저장하고있고, 내부 함수나 참조 하고 있는 함수 자체를 반환 하는 것을 의미
- 내부 함수와 외부 함수의 맥락을 고려하는 것을 의미
- 외부 함수의 파라미터를 지역 변수로 사용할 수있음
- 사용 사례
- callback 함수 내부에서 외부 데이터를 사용하고자 할 경우
- 부분 적용 함수
- 커링 함수
- callback 함수 내부에서 외부 데이터 사용
let fruits = ['apple', 'banana', 'peach'];
let ul = document.createElement('ul');
fruits.forEach(function (fruit){ // (A)
var li = document.createElement('li');
li.innerText = fruit;
li.addEventListener('click', function(){ // (B)
alert('your choice is' + fruit);
});
ul.appendChild(li);
});
document.body.appendChild(ul);
- A단계 에서는 외부 변수를 사용하고 있으므로 클로저가 없습니다.
li
tag의 event 리스너로 등록된 callback 함수 B는 fruit를 참조하는 클로저 입니다.- A단계가 종료 되더라도 버튼 클릭 이벤트 함수에서는 fruit 값을 저장하기 때문에 fruits 값이 변경되도 이전에 저장된 값이 호출 됩니다.
- 부분 적용 함수
- n개의 인자를 함수를 2개 이상으로 나눠서 m 개 n - m개의 데이터를 받아서 부분적으로 처리하기 위해서 사용하는 방식입니다.
- 커링 함수
- 여러 개의 인자를 받는 함수를 하나 이상의 인자만 받는 함수로 나눠서 순차 적으로 호출 될 수 있게 하는 것
- 예시로 여러개의 데이터를 전체 합을 구하는 함수를 구현 한다고 생각합니다
public class FunctionTest {
public static final Function<String, Function<String, Function<String, String>>> GREETING = message -> name -> result -> message + ", " + name + ": " + result + '!';
public static void main(String[] args) {
String fullMessage = GREETING.apply("메시지")
.apply("이름")
.apply("결과");
System.out.println(fullMessage); // 안녕, 세계!
}
}
// result : 메시지, 이름: 결과!
- 위예시는 stream과 같이 파이프라인에 좀 더 가깝습니다.
let curry = func => a => b => c => d => e => func(a, b, c, d, e);
curry(a)(b)(c)(d)(e)
- js 에서 구현한 curring 입니다
- 화살표 순서에 따라서 함수에 값을 차례로 넘겨주며 마지막에 func이 실행 됩니다.
- 지연 실행을 하기 위해서 사용함
- 람다/함수와 클로저 차이점
- 람다는 클로저 모두 익명의 특정 기능을 수행 하는 블록입니다.
- 클로저는 호출 시점이 아닌 클로저 생성 시점의 외부 context 정보를 저장 하고 있습니다. 그러므로 외부 함수를 더이상 접근 하지 못하더라도 기억하고 있는 context 정보가 사라지지 않는다.
반응형
'CS > Java' 카테고리의 다른 글
SOLID 원칙 (0) | 2022.02.06 |
---|---|
예외(Exception) (0) | 2022.01.20 |
LocalDatetime, LocalDate, LocalTime (0) | 2022.01.12 |
Thread (0) | 2022.01.01 |