분류 javascript

순수 함수

컨텐츠 정보

  • 조회 280 (작성일 )

본문

순수한 함수는 두 가지 법칙을 따르는 함수입니다.

  • 동일한 입력이 주어지면 항상 같은 출력을 반환
  • 부가 효과 없음
 

함수형 프로그래밍 패러다임은 순수한 함수를 기본 구성 단위, 즉 애플리케이션을 구축하는 기본 빌딩 블록으로 사용합니다.


JavaScript의 모든 함수가 순수 할 수 있는 것은 아니지만 가능할 때 종종 좋은 선택입니다. 순수한 함수가 필요한 곳에 불순한 함수를 사용하는 것이 일반적인 버그의 원인입니다. 예를 들어 UI 컴포넌트가 올바르게 렌더링 되도록 하려면 React 및 Redux reducer 함수가 순수해야 합니다.


https://vimeo.com/160326750 


Mapping 


함수는 입력 인수를 값을 반환하도록 매핑 합니다. 즉, 각 입력 집합에 대해 출력이 존재합니다. 함수는 입력을 받아 해당 출력을 반환합니다.


Math.max()는 숫자를 인수로 사용하여 가장 큰 숫자를 반환합니다.


Math.max(2, 8, 5); // => 8


이 예에서 2, 8, 5는 인수입니다. 그것들은 함수에 전달 된 값입니다.


Math.max()는 많은 수의 인수를 사용하여 가장 큰 인수 값을 반환하는 함수입니다. 이 경우에 우리가 전달한 가장 큰 숫자는 8이었고, 그것은 반환 된 숫자입니다.


함수는 컴퓨팅과 수학에서 정말로 중요합니다. 유용한 방식으로 데이터를 처리하는 데 도움이 됩니다. 훌륭한 프로그래머는 함수를 설명하는 이름을 지정하여 코드를 볼 때 함수 이름을 보고 함수의 기능을 이해할 수 있습니다.


수학에도 함수가 있으며 JavaScript의 함수와 매우 유사하게 작동합니다. 대수에서 함수를 보았을 것입니다. 그들은 다음과 같이 보입니다 :


f(x) = 2x 


즉, f라는 함수를 선언하고 x라는 인수를 사용하고 x에 2를 곱합니다.


이 함수를 사용하려면 간단히 x 값을 제공하십시오.


f(2) 

대수학에서 이것은 쓰기와 정확히 같은 의미입니다.


4 


따라서 f(2)가 표시되는 곳은 4를 대체 할 수 있습니다.


이제 해당 함수를 JavaScript로 변환 해 봅시다 :


const double = x => x * 2;


console.log()를 사용하여 함수의 출력을 검사 할 수 있습니다.


console.log( double(5) ); // => 10


수학 함수에서 f(2)를 4로 바꿀 수 있다고 말한 것을 기억하십니까? 이 경우 JavaScript 엔진은 double(5)을 답변 10으로 바꿉니다.


따라서 console.log(double(5)); console.log(10)와 동일합니다.


double()은 순수한 함수이기 때문에 사실이지만, double()에 값을 디스크에 저장하거나 콘솔에 로깅 하는 등의 부작용이 있는 경우에는 double(5)을 10으로 바꾸지 않고 의미.


당신이 참조 투명성을 원한다면 당신은 순수 함수를 사용해야 합니다.


순수 함수


순수한 함수는 다음과 같은 함수입니다.

  • 동일한 입력이 주어지면 항상 동일한 출력을 반환합니다.
  • 부가 효과를 갖지 않습니다.

함수가 순수하지 않다는 것의 명백한 증거는 그 함수가 반환 값을 사용하지 않으면서도 호출했을 때 올바른 동작이 가능한 지에 달렸습니다. 순수 함수는 반환 값을 사용하지 않으면 올바른 동작을 할 수 없습니다. 


순수한 함수를 선호하는 것이 좋습니다. 즉, 순수한 함수를 사용하여 프로그램 요구 사항을 구현하는 것이 실용적이라면 다른 옵션보다 사용해야 합니다. 순수한 함수는 입력을 받아 해당 입력에 따라 출력을 반환합니다. 그것들은 프로그램에서 가장 간단한 재 빌드 코드 블록입니다. 컴퓨터 과학에서 가장 중요한 디자인 원칙은 KISS (Keep It Simple, Stupid) 일 것입니다. 나는 그것을 바보처럼 유지하는 것을 선호합니다. 순수한 함수는 가장 좋은 방법으로 간단합니다.


순수한 함수에는 많은 유익한 특성이 있으며 함수형 프로그래밍의 기초를 형성합니다. 순수한 함수는 외부 상태와 완전히 독립적이므로 공유 가능한 변경 가능 상태와 관련된 모든 종류의 버그에 영향을 받지 않습니다. 또한 독립적인 특성으로 인해 많은 CPU 및 전체 분산 컴퓨팅 클러스터에서 병렬 처리를 수행 할 수 있는 훌륭한 후보가 되므로 많은 과학 및 리소스 집약적 컴퓨팅 작업에 필수적입니다.


순수 함수는 매우 독립적이므로 코드에서 쉽게 이동하고 리팩터링하고 재구성 할 수 있어 프로그램을 보다 유연하고 향후 변경에 적용 할 수 있습니다.


공유 상태의 문제점 


몇 년 전 저는 사용자가 음악 아티스트를 위한 데이터베이스를 검색하고 아티스트의 음악 재생 목록을 웹 플레이어에 로드 할 수 있는 앱을 개발하고있었습니다. Google 순간 검색이 시작된 시점이었으며 검색어를 입력하면 즉시 검색 결과가 표시됩니다. AJAX 기반 자동 완성은 갑자기 유행이 되었습니다.


유일한 문제는 사용자가 API 자동 완성 검색 응답을 반환 할 수 있는 것보다 빠르게 입력하는 경우가 많기 때문에 이상한 버그가 발생한다는 것입니다. 새로운 제안이 오래된 제안으로 대체되는 경쟁 조건을 유발합니다.


왜 그런 일이 일어 났습니까? 각 AJAX 성공 핸들러에는 사용자에게 표시된 제안 목록을 직접 업데이트 할 수 있는 액세스 권한이 부여되었습니다. 가장 느린 AJAX 요청은 대체 된 결과가 더 최신인 경우에도 결과를 맹목적으로 대체하여 사용자의 주의를 끌게 됩니다.


문제를 해결하기 위해 쿼리 제안 상태를 관리하는 단일 소스인 제안 관리자를 만들었습니다. 현재 보류중인 AJAX 요청을 알고 있었고 사용자가 새로운 것을 입력하면 새 요청이 발행되기 전에 보류 중인 AJAX 요청이 취소되므로 한 번에 단일 응답 핸들러만 UI 상태를 트리거할 수 있습니다.


모든 종류의 비동기 작업 또는 동시성으로 인해 경쟁 조건이 발생할 수 있습니다. 출력이 제어 할 수 없는 이벤트 시퀀스 (예 : 네트워크, 장치 대기 시간, 사용자 입력, 임의성 등)에 의존하는 경우 경쟁 조건이 발생합니다. 사실, 공유 상태를 사용하고 있고 그 상태가 모든 의도와 목적으로 결정적 요인에 따라 달라지는 시퀀스에 의존하는 경우 출력을 예측할 수 없으므로 제대로 테스트하거나 완전히 이해하는 것이 불가능합니다. Martin Odersky (스칼라의 창조자)는 다음과 같이 말합니다.


> 비결정성 = 병렬 처리 + 변경 가능 상태


프로그램 결정성은 일반적으로 컴퓨팅에서 바람직한 속성입니다. JS가 단일 스레드에서 실행되므로 병렬 처리 문제에 영향을 받지 않기 때문에 괜찮다고 생각할 수도 있지만 AJAX 예제에서 알 수 있듯이 단일 스레드 JS 엔진은 동시성이 없음을 의미하지 않습니다. 반대로 JavaScript에는 많은 동시성 소스가 있습니다. API I / O, 이벤트 리스너, 웹 워커, iframe 및 시간 초과는 모두 프로그램에 불확실성을 유발할 수 있습니다. 이것을 공유 상태와 결합하면 버그에 대한 좋은 레시피가 있습니다.


순수한 함수를 사용하면 이러한 종류의 버그를 피할 수 있습니다.


동일한 입력이 주어지면 항상 같은 출력을 반환 


double() 함수를 사용하면 함수 호출을 결과로 바꿀 수 있으며 프로그램은 같은 것을 의미합니다. double(5)는 컨텍스트에 상관없이 프로그램의 수와 상관없이 항상 10과 동일한 것을 의미합니다. 컨텍스트와 상관 없이, 얼마나 많이 호출하든지 상관 없습니다.


그러나 모든 함수에 대해 똑같은 것을 말할 수는 없습니다. 일부 함수는 결과를 생성하기 위해 전달하는 인수 이외의 정보에 의존합니다.


이 예제를 고려하십시오.


Math.random(); // => 0.4011148700956255
Math.random(); // => 0.8533405303023756
Math.random(); // => 0.3550692005082965


함수 호출에 인수를 전달하지는 않았지만 모두 다른 출력을 생성했습니다.


Math.random()은 실행할 때마다 0과 1 사이의 새로운 난수를 생성하므로 프로그램의 의미를 변경하지 않고 0.4011148700956255로 대체 할 수 없었습니다.


매번 같은 결과가 나옵니다. 컴퓨터에 임의의 숫자를 요청할 때 일반적으로 마지막 시간과 다른 결과를 원한다는 의미입니다. 모든 면에 같은 수의 주사위가 인쇄되어 있는 점은 무엇입니까?


때때로 우리는 컴퓨터에 현재 시간을 요구해야 합니다. 시간 함수의 작동 방식에 대해서는 자세히 다루지 않겠습니다. 지금은 이 코드를 복사하십시오.


const time = () => new Date().toLocaleTimeString();

time(); // => "5:15:45 PM"


time() 함수 호출을 현재 시간으로 바꾸면 어떻게 됩니까?


항상 같은 시간이라고 말합니다. 함수 호출이 교체 된 시간. 다시 말해, 하루에 한 번만 올바른 출력을 생성 할 수 있으며 함수가 교체 된 순간에 프로그램을 실행 한 경우에만 가능합니다.


분명히 time()은 double() 함수와 다릅니다.


함수는 동일한 입력이 주어지면 항상 동일한 출력을 생성하는 경우에만 순수합니다. 대수 클래스 에서 이 규칙을 기억할 수 있습니다. 동일한 입력 값은 항상 동일한 출력 값에 매핑됩니다. 그러나 많은 입력 값이 동일한 출력 값에 매핑 될 수 있습니다. 예를 들어 다음 함수는 순수합니다.


const highpass = (cutoff, value) => value >= cutoff;


동일한 입력 값은 항상 동일한 출력 값에 매핑 됩니다.


highpass(5, 5); // => true
highpass(5, 5); // => true
highpass(5, 5); // => true


많은 입력 값이 동일한 출력 값에 매핑 될 수 있습니다.


highpass(5, 123); // true
highpass(5, 6);   // true
highpass(5, 18);  // true

highpass(5, 1);   // false
highpass(5, 3);   // false
highpass(5, 4);   // false


순수 함수는 어떠한 부가 효과도 만들지 않습니다. 


순수한 함수는 부작용을 일으키지 않으므로 외부 상태를 변경할 수 없습니다.


부가 효과는 다음과 같습니다.

  • 디스크에 쓰기 (Writing to the disk)
  • 네트워크에 쓰기 (Writing to the network)
  • 화면에 그리기 (Drawing to the screen)
  • 콘솔에 로깅 (Logging to the console)
  • 던지는 오류 (Throwing errors)
  • DOM 변경 (Mutating the DOM)
  • 인수로 전달 된 객체 또는 배열 변경 (Mutating objects or arrays passed in as arguments)

부가 효과를 피하는 한 가지 방법은 불변성을 사용하는 것입니다.


불변성 (Immutability) 


JavaScript 인수는 참조로 전달됩니다. 즉, 함수가 객체 또는 배열 매개 변수의 속성을 변경하면 함수 외부에서 액세스 할 수 있는 상태가 변경됩니다. 외부 상태의 변경을 피하려면 모든 대치 값을 변경할 수 없는 것으로 처리해야 합니다.


이 변경을 고려하여 addToCart() 함수를 손상 시키십시오.


const addToCart = (cart, item, quantity) => {
  cart.items.push({
    item,
    quantity
  });
  return cart;
};


장바구니를 전달하고 해당 장바구니에 추가 할 품목과 품목 수량을 전달합니다. 그런 다음 이 함수는 항목이 추가 된 동일한 카트를 반환합니다. 이것의 문제는 우리가 공유 상태를 변경했다는 것입니다. 다른 함수는 함수가 호출되기 전의 카트 객체 상태에 의존 할 수 있으며, 이제 공유 상태를 변경 했으므로 변경하면 프로그램 논리에 미치는 영향에 대해 걱정해야 합니다. 함수가 호출 된 순서. 코드를 리팩토링하면 버그가 발생하여 주문이 망가질 수 있으며 고객이 불만족 할 수 있습니다.


이제 이 버전을 고려하십시오.


// Pure addToCart() returns a new cart.
// It does not mutate the original.
const addToCart = (cart, item, quantity) => ({
  ...cart,
  items: cart.items.concat([{
    item,
    quantity
  }])
});


기존 장바구니 상태에서 혼합하기 위해 객체 스프레드 구문을 사용하고 변경된 속성 만 재정의 할 수 있습니다. 이 경우 items 키를 새 버전으로 바꾸고 새 항목과 연결합니다. Concat은 원본을 변경하지 않고 새로운 배열을 반환합니다.


비 변경 메서드를 접근자 메서드라고 합니다.


코드를 테스트하고 실행할 데이터를 제공하여 직접 확인할 수 있습니다.


const myCart = {
  id: 'cart123',
  items: [
    { item: 'Apple', quantity: 3 }
  ]
};

console.log(
  addToCart(myCart, 'Orange', 2), // the new cart
  myCart // myCart is unchanged
);


이 예에서는 객체에 배열이 중첩되어 있습니다. 이와 같은 복잡한 상태의 경우 때로는 순수한 함수를 단순화하기 위해 Immer와 같은 도구를 사용합니다.


Redux를 사용하면 각 리듀서 내부의 전체 앱 상태를 처리하지 않고 리듀서를 구성 할 수 있습니다. 결과적으로 작은 부분 만 업데이트 할 때마다 전체 앱 상태를 깊게 복제 할 필요가 없습니다. 대신 비파괴 배열 메서드 또는 Object.assign()을 사용하여 앱 상태의 작은 부분을 업데이트 할 수 있습니다.