함수형 프로그래밍 패러다임

Dohyeon Kong·2024년 4월 16일
0

JavaScript🟡

목록 보기
12/13
post-thumbnail

함수형 프로그래밍 패러다임

  • 자바스크립트는 함수형 프로그래밍 패러다임을 추구하며 추가로 객체지향 프로그래밍 패러다임을 지원한다.
  • 자바스크립트의 핵심은 함수(함수형 프로그래밍 패러다임)과 객체(객체지향 프로그래밍 패러다임)이다.
  • 추가로 타입스크립트를 통해 인터페이스(다형성)을 도입한다.

함수형 프로그래밍이 되기 위한 조건

1. 일급함수 : 함수 변수 + 함수 파라미터 + 함수 반환

  • 함수 변수 할당 = 함수 표현식(Expression)
var expression = function() {
	console.log('hello');
}
expression();

// 출력값
// hello
  • 함수 파라미터
var array = [1, 2, 3, 4];
var parameter = (element) =>{
 	return element * 10; 
}
var calcArray = array.map(array);
console.log(calcArray);

// 출력값
// [10, 20, 30, 40]
  • 함수 반환
function returnValue(unit){
  return (parameter) =>{
   	console.log(parameter * unit); 
  }
}
var returned = returnValue(10);
returned(1230);

// 출력값
// 12300
  • 함수 반환 코드 설명
  1. 함수 returnValue 함수는 unit이라는 한 개의 매개변수를 받는다.
  2. 함수 내부에서는 또 다른 함수를 반환하고 있다. 이 반환되는 함수는 자신의 외부 함수인 returnValue로부터 unit 변수를 클로저(closure)를 통해 참조한다.
  3. 반환되는 함수: 내부 함수는 parameter라는 매개변수를 받습니다. 이 함수가 호출될 때, parameter * unit을 계산하고 그 결과를 console.log를 통해 출력한다.
  4. returnValue(10)을 호출하면, unit에 10이라는 값이 전달된다. 이 호출의 결과로 unit을 사용하는 내부 함수가 반환된다. 이때 unit 값은 이미 10으로 고정된 상태이다.
  5. 반환된 함수는 returned라는 변수에 저장된다.
    이후 returned(1230)을 호출하면, 저장된 함수가 실행되면서 매개변수 1230을 받아 1230 * 10의 계산을 수행한다.
  6. 최종적으로 console.log를 통해 12300이 출력된다.

이 코드의 핵심은 함수가 다른 함수를 반환하고, 반환된 함수가 자신이 생성될 때의 환경을 '기억'하는 클로저의 개념을 활용한다는 점, 이를 통해 unit 변수는 returned 함수가 호출될 때까지 그 값이 유지됩니다.

2. 순수함수

  • 순수함수의 두가지 특징
  1. 참조 투명성(Referentially Transparent)
    • 함수가 외부 함수에 의존하지 않는 것
    • 같은 파라미터에 같은 반환값
  2. 부수 효과 없음(No side-Effects)
    • 외부 상태를 서로 바꾸지 않는 상황

정직하게 일정 값(파라미터)을 넣으면, 일정한 값(반환값)이 나오며, 프로그램의 어느것도 건들지 않는다는 특징을 지닌다.

  • Thread-Safe : 그래서 멀티스레드를 활용한 개발 시 순수함수 특성은 매우 중요하며
    • 병렬적으로 수행되는 모든 함수들은 서로에게 영향을 주어선 안되고, 외부의 값을 변경해서도 안된다.
  • 함수형 프로그래밍 : 함수형 프로그래밍에서 우리가 사용하는 모든 함수 단위들은 순수함수여야한다.

* 순수함수의 장점

  1. Testability (테스트가 가능하다.)
    • 일정한 파라미터에 따라 일정한 반환값이 나온다면 유닛테스트가 용이하다.
  2. Debugging (디버깅이 가능하다.)
    • 문제가 생기는 함수만 보면 문제가 해결된다.
  3. memoization (메모이제이션이 가능하다.)
    • 캐싱이 가능하다.

* 비순수함수의 단점

  1. Side Effect (부수효과 발생 : 하나의 함수를 고쳤는데, 전혀 상관없는 옆 함수가 에러가 발생한다.
  2. Unpredictable Output (예상치 못한 결과) : 같은 파라미터값인데 다른 값이 출력된다.
  3. Difficult to Reason (디버깅 및 프로그램 흐름 분석이 어려워짐) : 함수호출을 하지 않았는데 객체가 바뀐다.

참조투명성 지키지 않은 사례와 Side-Effect가 발생한 사례

참조 투명성을 지키지 않은 사례

  1. 비일관성의 결과 (Inconsistent Results) : 같은 파라미터에 다른 값
    • 외부 상태에 의존
var c = 3;
function sideEffect(a,b){
  console.log(a + b + c);
}
sideEffect(1, 2);
var c = 5;
sideEffect(1, 2);

// 출력값
// 6
// 8

사이드 이펙트가 발생한 사례

  1. Difficult to Reason 디버깅 및 프로그램 흐름 분석이 어려워진다.
    • 외부 상태를 변경하게 된다. => 추적하기 힘듦
var object = {
  name : "Kong",
  age : 10
}
function sideEffect(){
  object.name = "Baron"
}
sideEffect();
console.log(object);

// 출력값
// {name : "Kong", age : 10}
  • 어떤 개발자가 자신의 의도대로 외부 객체를 변경할 시 어떤 누군가는 의도치않게 변경된 객체를 사용하게 될 수도 있다.
// object는 참조주소를 가지고 있으므로 
// name 속성의 값이 변환된다.
var object = {
  name : "Kong",
  age : 10
}
function sideEffect(object){
  object.name = "Caron"
}
sideEffect(object);
console.log(object);
// 출력값
// {name : "Caron", age : 10}
// object는 참조주소를 가지고 있으므로 
// copied는 object와 주소가 같다.
// name 속성의 값이 변환된다.

var object = {
  name : "Kong",
  age : 10
}
function sideEffect(object){
  var copied = object
  copied.name = "Daron"
  return copied
}
var manipulated = sideeffect(object);
console.log(manipulated);
console.log(object);
manipulated == object

// 출력값
// {name : "Daron", age : 10}
// {name : "Daron", age : 10}
// true

Side-Effect를 해결할 방법 : immutability

  • immputability 불변 객체를 사용한다. : 파라미터(인풋)은 외부 상태이기에 변경이 없어야 한다.
    - 즉, 무조건 새로운 값이 반환되어야 한다는 의미이다.
// object는 참조주소를 가지고 있으므로 
// copied는 object와 주소가 같다.
// name 속성의 값이 변환된다.

var object = {
  name : "Kong",
  age : 10
}
function noSideEffect(object){
  var copied = Object.assign({}, object)
  copied.name = "Daron"
  return copied
}
var manipulated = noSideEffect(object);
console.log(manipulated);
console.log(object);
manipulated == object

// 출력값
// {name : "Daron", age : 10}
// {name : "Kong", age : 10}
// false

// Object.assign()메서드를 사용하여 
// 값을 복사하는 얕은 복사를 진행한다.

얕은 복사(Shallow Copy) vs 깊은 복사(Deep Copy)

  • Object.assign() 는 얕은 복사(Shallow Copy)에 해당한다.
  • 얕은 복사는 1계층만 복사하기 때문에 address의 속성은 참조값만이 복사된다.
    즉 아래 예시에서 Earth->Moon으로 바꿨으나 모두에서 변경이된다.
var object = {
  name : "Kong",
  age : 10,
  address : 'Earth',
}
var copied = Object.assign({}, object)
copied.name = "Baron"
console.log(object);
console.log(copied);
object.address.location = 'Moon'
console.log(object);
console.log(copied);

// 출력값
// { name: 'Aaron', age: 10, address: { location: 'Earth' } }
// { name: 'Baron', age: 10, address: { location: 'Earth' } }
// { name: 'Aaron', age: 10, address: { location: 'Moon' } }
// { name: 'Baron', age: 10, address: { location: 'Moon' } }
  • 깊은 복사는 모든 계층을 복사하며, 깊은 복사를 사용할 수 있는 방법은 다음과 같다.
  1. 어려운 방법 : typeof를 활용한 재귀 함수를 직접 개발한다.
  2. 쉬운 방법 : Stringify하여 Object -> String으로 만들었다가 다시 역으로 String -> Object로 변환한다.
profile
천천히, 꾸준히, 그리고 끝까지

0개의 댓글