JavaScript Monads가 단순 해졌습니다.

스모크 아트 큐브 스모크 — MattysFlicks — (CC BY 2.0)
참고 :이 글은 JavaScript ES6 +의 기능 프로그래밍 및 컴포지션 소프트웨어 기술을 처음부터 배우는 "소프트웨어 구성"시리즈 (현재 책!)의 일부입니다. 계속 지켜봐 주시기 바랍니다. 앞으로 더 많은 것들이 있습니다!
<이전 | << 1 부에서 다시 시작

모나드를 배우기 전에 이미 알고 있어야합니다.

  • 함수 구성 : 작성 (f, g) (x) = (f∘ g) (x) = f (g (x))
  • Functor 기본 사항 : Array.map () 작업에 대한 이해
모나드 그린의 저주 ~ 길 라드 브라 차 (더글러스 크록 포드가 유명하게 사용)-모나드를 이해하면 즉시 다른 사람에게 설명 할 수 없게됩니다.
“박사 Hoenikker는 자신이하고있는 일을 8 살짜리에게 설명 할 수 없었던 어떤 과학자라도 목탄이라고 말했습니다.”~ Kurt Vonnegut의 소설 Cat 's Cradle

인터넷에서 "모나드"를 검색하면 흠 잡을 데없는 범주 이론 수학과 부리 토와 우주복의 측면에서 모나드를 설명하는 많은 사람들이 폭격을 당하게 될 것입니다.

모나드는 간단하다. 링고는 어렵다. 본질로 잘라 봅시다.

모나드는 계산, 분기 또는 I / O와 같은 반환 값 외에 컨텍스트가 필요한 함수를 구성하는 방법입니다. Monads는 리프팅, 플랫 화 및 맵을 작성하여 함수 a => M (b)를 리프팅하기 위해 유형이 정렬되어 구성 가능합니다. 리프트, 평탄화 및 맵의 구현 세부 사항에 숨겨진 일부 컴퓨팅 컨텍스트와 함께 일부 유형 a에서 유형 b 로의 매핑입니다.

  • 함수 맵 : a => b
  • 컨텍스트가있는 Functors 맵 : Functor (a) => Functor (b)
  • Monads가 전개되고 컨텍스트가있는 맵 : Monad (Monad (a)) => Monad (b)

그러나“평평하게”,“지도”및“문맥”은 무엇을 의미합니까?

  • Map은 "a에 함수를 적용하고 b를 반환합니다"를 의미합니다. 입력이 있으면 출력을 반환합니다.
  • 문맥은 모나드의 구성 (리프트, 평 평화 및 맵 포함)의 계산 세부 사항입니다. Functor / Monad API와 그 작동은 나머지 응용 프로그램과 함께 모나드를 구성 할 수있는 컨텍스트를 제공합니다. 펑터와 모나드의 요점은 그 맥락을 추상화하여 구성하는 동안 걱정할 필요가 없다는 것입니다. 컨텍스트 내에서 매핑하면 a => b에서 컨텍스트 내 값에 함수를 적용하고 동일한 종류의 컨텍스트 안에 래핑 된 새 값 b를 반환합니다. 왼쪽에있는 관찰 가능? 오른쪽의 Observable : Observable (a) => Observable (b). 왼쪽의 배열? 오른쪽 배열 : Array (a) => Array (b).
  • 타입 리프트는 타입을 컨텍스트로 들어 올리고, 그 값에서 계산하고, 컨텍스트 계산 등을 트리거하는 데 사용할 수있는 API로 가치를 축복하는 것을 의미합니다. a => F (a) (모나드는 일종의 펑터입니다).
  • 평탄화는 컨텍스트에서 값을 푸는 것을 의미합니다. F (a) => a.

예:

const x = 20; //`a` 유형의 일부 데이터
const f = n => n * 2; //`a`에서`b`까지의 함수
const arr = Array.of (x); // 타입 리프트.
// JS는 배열을위한 타입 리프트 슈가를 가지고 있습니다 : [x]
// .map ()은 함수 f를 값 x에 적용합니다.
// 배열의 맥락에서.
const 결과 = arr.map (f); // [40]

이 경우 Array는 컨텍스트이고 x는 매핑 할 값입니다.

이 예제에는 배열 배열이 포함되어 있지 않지만 JS에서 .concat ()을 사용하여 배열을 병합 할 수 있습니다.

[] .concat.apply ([], [[1], [2, 3], [4]]); // [1, 2, 3, 4]

이미 모나드를 사용하고있을 것입니다.

기술 수준이나 범주 이론에 대한 이해와 상관없이 모나드를 사용하면 코드를보다 쉽게 ​​사용할 수 있습니다. 모나드를 활용하지 않으면 코드를 다루기가 더 어려워 질 수 있습니다 (예 : 콜백 지옥, 중첩 된 조건부 분기, 더 자세한 정보).

소프트웨어 개발의 본질은 구성이며 모나드는 구성을보다 쉽게 ​​만들어줍니다. 모나드의 본질을 다시 살펴보십시오.

  • 함수 맵 : a => b : a => b 유형의 함수를 구성 할 수 있습니다.
  • Functor (a) => Functor (b)를 사용하여 함수를 매핑합니다. F (a) => F (b)
  • Monads는 다음과 같이 컨텍스트를 사용하여 전개 및 매핑합니다. Monad (Monad (a)) => Monad (b), 리프팅 함수를 구성 할 수 있습니다. a => F (b)

이것들은 모두 기능 구성을 표현하는 다른 방식입니다. 함수가 존재하는 모든 이유는 함수를 구성 할 수 있기 때문입니다. 함수를 사용하면 복잡한 문제를 간단한 문제로 분리하여 쉽게 해결할 수 있으므로 다양한 방법으로 응용 프로그램을 구성 할 수 있습니다.

기능을 이해하고 올바르게 사용하기위한 핵심은 기능 구성에 대한 심도있는 이해입니다.

함수 구성은 데이터가 흐르는 함수 파이프 라인을 만듭니다. 파이프 라인의 첫 번째 단계에서 일부 입력을 입력하고 일부 데이터는 파이프 라인의 마지막 단계에서 튀어 나와 변환됩니다. 그러나 이것이 작동하려면 파이프 라인의 각 단계는 이전 단계가 반환하는 데이터 유형을 예상해야합니다.

유형이 모두 쉽게 정렬되므로 간단한 기능을 쉽게 구성 할 수 있습니다. 출력 유형 b를 입력 유형 b와 일치 시키면 비즈니스에 종사 할 수 있습니다.

g : a => b
f : b => c
h = f (g (a)) : a => c

유형이 정렬되므로 F (a) => F (b)를 매핑하는 경우 펑터로 구성하기도 쉽습니다.

g : F (a) => F (b)
f : F (b) => F (c)
h = f (g (Fa)) : F (a) => F (c)

그러나 a => F (b), b => F (c) 등의 함수를 작성하려면 모나드가 필요합니다. F ()를 M ()으로 바꾸어 명확하게 해 봅시다.

g : a => M (b)
f : b => M (c)
h = 작성 M (f, g) : a => M (c)

죄송합니다. 이 예에서는 구성 요소 기능 유형이 정렬되지 않습니다! f의 입력을 위해, 우리는 타입 b를 원했지만, 우리가 얻은 것은 타입 M (b) (b의 모나드)입니다. 정렬이 잘못 되었기 때문에, composeM ()은 g가 반환하는 M (b)를 풀어서 f에 전달할 수 있도록해야합니다. f는 M (b) 유형이 아닌 b 유형을 예상하기 때문입니다. 이 프로세스 (종종 .bind () 또는 .chain ())는 평탄화 및 맵이 발생하는 곳입니다.

다음 함수로 전달하기 전에 M (b)에서 b를 풀어서 다음과 같이합니다.

g : a => M (b)가 => b로 평평 해짐
f : b는 => M (c)에 매핑됩니다.
h 구성 M (f, g) :
               a flatten (M (b)) => b => 맵 (b => M (c)) => M (c)

Monad는 리프팅 함수 a => M (b)에 대해 유형을 정렬하여 구성 할 수 있도록합니다.

위의 다이어그램에서, M (b) => b에서 평탄화되고 b => M (c)의 맵은 a => M (c)에서 체인 내부에서 발생합니다. 체인 호출은 composeM () 내에서 처리됩니다. 높은 수준에서 걱정할 필요가 없습니다. 일반 함수를 구성하는 데 사용하는 것과 동일한 종류의 API를 사용하여 모나드 리턴 함수를 작성할 수 있습니다.

많은 함수가 a => b의 간단한 매핑이 아니기 때문에 모나드가 필요합니다. 일부 함수는 부작용 (프로 미스, 스트림), 분기 처리 (Maybe), 예외 처리 (둘 중 하나) 등을 처리해야합니다.

보다 구체적인 예는 다음과 같습니다. 비동기 API에서 사용자를 가져온 다음 해당 사용자 데이터를 다른 비동기 API로 전달하여 계산을 수행하려면 어떻게해야합니까? :

getUserById (id : String) => 약속 (사용자)
hasPermision (User) => 약속 (부울)

문제를 설명하기위한 함수를 작성해 봅시다. 먼저 유틸리티 compose () 및 trace () :

const compose = (... fns) => x => fns.reduceRight ((y, f) => f (y), x);
const 추적 = 레이블 => 값 => {
  console.log (`$ {label} : $ {value}`);
  반환 값;
};

그런 다음 몇 가지 기능을 작성하십시오.

{
  const label = 'API 호출 구성';
  // a => 약속 (b)
  const getUserById = id => id === 3?
    Promise.resolve ({이름 : 'Kurt', 역할 : 'Author'}) :
    미정
  ;
  // b => 약속 (c)
  const hasPermission = ({역할}) => (
    Promise.resolve (역할 === '저자')
  );
  // 작성해보십시오. 경고 : 실패합니다.
  const authUser = compose (hasPermission, getUserById);
  // 죄송합니다! 항상 거짓!
  authUser (3) .then (trace (label));
}

hasPermission ()을 getUserById ()로 작성하여 authUser ()를 만들려고 할 때 hasPermission ()이 User 객체를 기대하고 대신 Promise (User)를 가져 오기 때문에 큰 문제가 발생합니다. 이 문제를 해결하려면 compose ()를 composePromises ()로 교체해야합니다. 함수 구성을 달성하기 위해 .then ()을 사용해야한다는 것을 알고있는 compose의 특수 버전입니다.

{
  const composeM = chainMethod => (... ms) => (
    ms.reduce ((f, g) => x => g (x) [chainMethod] (f))
  );
  const composePromises = composeM ( 'then');
  const label = 'API 호출 구성';
  // a => 약속 (b)
  const getUserById = id => id === 3?
    Promise.resolve ({이름 : 'Kurt', 역할 : 'Author'}) :
    미정
  ;
  // b => 약속 (c)
  const hasPermission = ({역할}) => (
    Promise.resolve (역할 === '저자')
  );
  // 함수를 작성합니다 (작동합니다).
  const authUser = composePromises (hasPermission, getUserById);
  authUser (3) .then (trace (label)); // 참
}

나중에 composeM ()이하는 일을 살펴 보겠습니다.

모나드의 본질을 기억하십시오 :

  • 함수 맵 : a => b
  • 컨텍스트가있는 Functors 맵 : Functor (a) => Functor (b)
  • Monads가 전개되고 컨텍스트가있는 맵 : Monad (Monad (a)) => Monad (b)

이 경우 모나드는 실제로 약속이므로 약속 반환 함수를 작성할 때 hasPermission ()이 기대하는 User 대신 Promise (User)가 있습니다. Monad (Monad (a))에서 외부 Monad () 래퍼를 제거했다면 Monad (a) => Monad (b)가 남게되며 이는 일반적인 functor .map ()입니다. Monad (x) => x를 평평하게 할 수있는 것이 있다면 사업을 시작한 것입니다.

모나드의 구성

모나드는 간단한 대칭을 기반으로합니다. 값을 컨텍스트에 래핑하는 방법과 컨텍스트에서 값을 래핑하는 방법입니다.

  • Lift / Unit : 어떤 타입에서 모나드 문맥으로 타입 리프트 : a => M (a)
  • 병합 / 결합 : 컨텍스트에서 유형 풀기 : M (a) => a

모나드는 또한 펑터이기 때문에 다음과 같이 매핑 할 수도 있습니다.

  • 맵 : 컨텍스트가 유지 된 맵 : M (a)-> M (b)

평탄화를 맵과 결합하면, 하인리히 클라이 슬리 (Hinrich Kleisli)의 이름을 따서 명명 된 모나드-리프팅 기능을위한 함수 구성, 일명 Kleisli 구성 :

  • FlatMap / Chain : Flatten + 맵 : M (M (a)) => M (b)

모나드의 경우 .map () 메소드는 종종 공개 API에서 생략됩니다. Lift + flatten은 .map ()을 명시 적으로 철자하지는 않지만 만드는 데 필요한 모든 재료를 갖추고 있습니다. (일명 / 단위)와 체인 (일명 bind / flatMap)을 들어 올릴 수 있다면 .map ()을 만들 수 있습니다.

const MyMonad = 값 => ({
  // <... 임의의 체인을 삽입하고 여기에 ...>
  지도 (f) {
    this.chain (a => this.constructor.of (f (a)))를 돌려줍니다;
  }
});

따라서 모나드에 대해 .of () 및 .chain () /. join ()을 정의하면 .map ()의 정의를 유추 할 수 있습니다.

리프트는 팩토리 / 생성자 및 / 또는 constructor.of () 메서드입니다. 범주 이론에서는이를 "단위"라고합니다. 모나드의 맥락으로 타입을 들어 올리기 만하면됩니다. a를 a의 Monad로 바꿉니다.

Haskell에서는 (매우 혼란스럽게도) return이라고 불립니다. 거의 모든 사람들이 함수 반환과 혼동하기 때문에 큰 소리로 이야기하려고 할 때 매우 혼란 스럽습니다. 나는 거의 항상 그것을 "리프트"또는 "타입 리프트"라고 부르고 코드에서는 .of ()라고 부릅니다.

.chain ()에 맵이없는 평탄화 프로세스를 일반적으로 flatten () 또는 join ()이라고합니다. flatten () / join ()은 .chain () /. flatMap ()에 내장되어 있기 때문에 (항상 그런 것은 아님) 자주 생략됩니다. 병합은 종종 컴포지션과 관련되므로 매핑과 결합되는 경우가 많습니다. unwrapping + map은 모두 => M (a) 함수를 구성하는 데 필요합니다.

처리중인 모나드 종류에 따라 래핑 해제 프로세스는 매우 간단 할 수 있습니다. identity 모나드의 경우 결과 값을 모나드 컨텍스트로 다시 올리지 않는다는 점을 제외하면 .map ()과 같습니다. 그것은 하나의 포장 레이어를 버리는 효과가 있습니다.

{// 신원 모나드
const Id = value => ({
  // Functor 매핑
  // .map ()의 줄 바꿈을 유지
  // 매핑 된 값을 형식에 전달
  // 리프트 :
  지도 : f => Id.of (f (값)),
  // 모나드 체인
  // 1 단계 래핑 폐기
  // .of () 타입 리프트를 생략함으로써 :
  체인 : f => f (value),
  // 편리한 편리한 검사 방법
  // 값 :
  toString : () =>`Id ($ {value})`
});
//이 모나드의 타입 리프트는
// 팩토리에 대한 참조.
Id.of = Id;

그러나 언 래핑 부분은 부작용, 오류 분기 또는 비동기 I / O 대기와 같은 이상한 것들이 일반적으로 숨기는 곳이기도합니다. 모든 소프트웨어 개발에서 컴포지션은 모든 흥미로운 흥미로운 일이 일어나는 곳입니다.

예를 들어, .chain ()은 .then ()을 약속했습니다. promise.then (f)를 호출하면 f ()가 즉시 호출되지 않습니다. 대신 약속이 해결 될 때까지 기다린 다음 f () (따라서 이름)를 호출합니다.

예:

{
  const x = 20; // 가치
  const p = Promise.resolve (x); // 문맥
  const f = n =>
    Promise.resolve (n * 2); // 함수
  const 결과 = p.then (f); // 응용 프로그램
  result.then (
    r => console.log (r) // 40
  );
}

약속과 함께 .chain () 대신 .then ()이 사용되지만 거의 동일합니다.

당신은 약속이 엄격하게 모나드가 아니라고 들었을 것입니다. 가치가 처음부터 약속이라면 외부 약속을 풀어야하기 때문입니다. 그렇지 않으면 .then ()은 .map ()처럼 동작합니다.

그러나 약속 값과 다른 값에 대해 다르게 동작하기 때문에 .then ()은 모든 함수 및 / 또는 모나드가 모든 주어진 값에 대해 만족해야하는 모든 수학적 법칙을 엄격하게 준수하지는 않습니다. 실제로, 그 행동 분기를 알고있는 한, 보통 그것들을 둘 중 하나로 취급 할 수 있습니다. 약속에 따라 일부 일반적인 구성 도구가 예상대로 작동하지 않을 수 있습니다.

건물 수도원 (일명 Kleisli) 구성

약속 이행 기능을 구성하는 데 사용한 composeM 기능에 대해 자세히 살펴 보겠습니다.

const composeM = 방법 => (... ms) => (
  ms.reduce ((f, g) => x => g (x) [방법] (f))
);

그 이상한 감속기에 숨겨진 것은 함수 구성의 대수적 정의입니다 : f (g (x)). 보다 쉽게 ​​찾을 수 있도록하겠습니다 :

{
  // 함수 구성의 대수적 정의 :
  // (f ∘ g) (x) = f (g (x))
  const compose = (f, g) => x => f (g (x));
  const x = 20; // 가치
  const arr = [x]; // 컨테이너
  // 작성하는 일부 함수
  const g = n => n + 1;
  const f = n => n * 2;
  // .map ()이 함수 구성을 수행한다는 것을 증명합니다.
  // 맵에 대한 연결 호출은 함수 구성입니다.
  자취 ( '지도 작성') ([
    arr.map (g) .map (f),
    arr.map (구성 (f, g))
  ]);
  // => [42], [42]
}

이것이 의미하는 바는 .map () 메서드 (예 : 배열)를 제공하는 모든 functor에서 작동하는 일반화 된 작성 유틸리티를 작성할 수 있다는 것입니다.

const composeMap = (... ms) => (
  ms.reduce ((f, g) => x => g (x) .map (f))
);

이것은 표준 f (g (x))의 약간의 재구성입니다. a-> Functor (b) 유형의 함수가 여러 개인 경우 각 함수를 반복하고 각 함수를 입력 값 x에 적용합니다. .reduce () 메서드는 누산기 (이 경우 f)와 배열의 현재 항목 (g)이라는 두 가지 입력 값이있는 함수를 사용합니다.

다음 함수에서 f가되는 새로운 함수 x => g (x) .map (f)를 반환합니다. 우리는 위에서 x => g (x) .map (f)가 ​​funose (f)의 맥락으로 compose (f, g) (x)를 들어 올리는 것과 동등하다는 것을 이미 증명했습니다. 즉, 컨테이너의 값에 f (g (x))를 적용하는 것과 같습니다.이 경우 배열 내부의 값에 컴포지션을 적용합니다.

성능 경고 : 어레이에는 권장하지 않습니다. 이러한 방식으로 함수를 구성하려면 전체 배열 (수십만 개의 항목을 포함 할 수 있음)에 대해 여러 번 반복해야합니다. 배열에 대한 맵의 경우 간단한 a-> b 함수를 먼저 작성한 다음 배열에 한 번 맵핑하거나 .reduce () 또는 변환기를 사용하여 반복을 최적화하십시오.

어레이 데이터에 대한 동기적이고 열성적인 기능 응용 프로그램의 경우 이는 과도합니다. 그러나 많은 것들이 비동기 적이거나 게으르고, 많은 함수들은 예외 나 빈 값을위한 분기와 같은 지저분한 것들을 처리해야합니다.

모나드는 컴포지션 체인에서 이전의 비동기 또는 분기 작업에 의존하는 값에 의존 할 수 있습니다. 이 경우 간단한 기능 구성에 대한 간단한 가치를 얻을 수 없습니다. 모나드 리턴 동작은 a => b 대신 a => Monad (b) 형식입니다.

일부 데이터를 가져 와서 API에 도달하고 해당 값을 리턴하는 함수와 해당 데이터를 가져 와서 다른 API에 도달하고 해당 데이터에 대한 계산 결과를 리턴하는 함수가있을 때마다 함수를 작성하려고합니다. a => Monad (b) 유형입니다. API 호출은 비동기식이므로 반환 값을 promise 또는 observable과 같은 형식으로 래핑해야합니다. 즉, 해당 함수의 서명은 각각 a-> Monad (b) 및 b-> Monad (c)입니다.

유형 g : a-> b, f : b-> c의 구성 함수는 유형이 정렬되기 때문에 쉽습니다. h : a-> c는 a => f (g (a))입니다.

g 타입의 함수 구성 : a-> Monad (b), f : b-> Monad (c)는 조금 더 어렵다 : h : a-> Monad (c)는 a => f (g (a)가 아님) f는 Monad (b)가 아닌 b를 기대하기 때문입니다.

좀 더 구체적으로 설명하고 각각 약속을 돌려주는 한 쌍의 비동기 함수를 구성 해 봅시다.

{
  const label = '약속 구성';
  const g = n => Promise.resolve (n + 1);
  const f = n => Promise.resolve (n * 2);
  const h = composePromises (f, g);
  h (20)
    .then (추적 (라벨))
  ;
  // 약속 구성 : 42
}

결과가 올바르게 기록되도록 composePromises ()를 어떻게 작성합니까? 힌트 : 이미 보았습니다.

composeMap () 함수를 기억하십니까? .map () 호출을 .then ()으로 변경하기 만하면됩니다. Promise.then ()은 기본적으로 비동기 .map ()입니다.

{
  const composePromises = (... ms) => (
    ms.reduce ((f, g) => x => g (x) .n (f))
  );
  const label = '약속 구성';
  const g = n => Promise.resolve (n + 1);
  const f = n => Promise.resolve (n * 2);
  const h = composePromises (f, g);
  h (20)
    .then (추적 (라벨))
  ;
  // 약속 구성 : 42
}

이상한 부분은 두 번째 함수 f를 기억하면 입력 값이 약속이라는 것입니다. b 형이 아니라 Promise (b) 형이지만 f는 랩핑되지 않은 b 형을 취합니다. 무슨 일이야?

.then () 안에는 Promise (b)-> b에서 언 래핑 프로세스가 있습니다. 이 작업을 조인 또는 병합이라고합니다.

composeMap () 및 composePromises ()는 거의 동일한 함수임을 알 수 있습니다. 이것은 둘 다 처리 할 수있는 고차 함수에 대한 완벽한 유스 케이스입니다. 체인 방법을 카레 함수에 혼합 한 다음 대괄호 표기법을 사용하십시오.

const composeM = 방법 => (... ms) => (
  ms.reduce ((f, g) => x => g (x) [방법] (f))
);

이제 다음과 같은 특수 구현을 작성할 수 있습니다.

const composePromises = composeM ( 'then');
const composeMap = composeM ( 'map');
const composeFlatMap = composeM ( 'flatMap');

모나드 법률

자신의 모나드를 구축하기 전에 모든 모나드가 충족해야하는 세 가지 법칙이 있다는 것을 알아야합니다.

  1. 왼쪽 동일성 : unit (x) .chain (f) ==== f (x)
  2. 올바른 정체성 : m.chain (unit) ==== m
  3. 연관성 : m.chain (f) .chain (g) ==== m.chain (x => f (x) .chain (g))

신원 법

좌우 정체성

모나드는 functor입니다. 펑 터는 A-> B 범주 사이의 형태입니다. 형태는 화살표로 표시됩니다. 개체 사이에 화살표가있는 것 외에도 범주의 각 개체에는 화살표가 있습니다. 다시 말해, 범주의 모든 객체 X에 대해 화살표 X-> X가 있습니다.이 화살표는 식별 화살표로 알려져 있으며 일반적으로 객체에서 가리키는 작은 원형 화살표로 그려져 동일한 객체로 반복됩니다. .

정체성의 형태

연관성

연관성은 단지 작성할 때 괄호를 어디에 두는 것이 중요하지 않다는 것을 의미합니다. 예를 들어 추가하는 경우 a + (b + c)는 (a + b) + c와 같습니다. 기능 조성에 대해서도 동일하다 : (f ° g) ° h = f ° (g ° h).

Kleisli 구성에 대해서도 마찬가지입니다. 당신은 그것을 뒤로 읽어야합니다. 작성 연산자 (체인)가 표시되면 다음을 고려하십시오.

h (x) .chain (x => g (x) .chain (f)) ==== (h (x) .chain (g)). chain (f)

모나드 법칙 증명

아이덴티티 모나드가 모나드 법칙을 충족한다는 것을 증명합시다 :

{// 신원 모나드
  const Id = value => ({
    // Functor 매핑
    // .map ()의 줄 바꿈을 유지
    // 매핑 된 값을 형식에 전달
    // 리프트 :
    지도 : f => Id.of (f (값)),
    // 모나드 체인
    // 1 단계 래핑 폐기
    // .of () 타입 리프트를 생략함으로써 :
    체인 : f => f (value),
    // 편리한 편리한 검사 방법
    // 값 :
    toString : () =>`Id ($ {value})`
  });
  //이 모나드의 타입 리프트는
  // 팩토리에 대한 참조.
  Id.of = Id;
  const g = n => Id (n + 1);
  const f = n => Id (n * 2);
  // 왼쪽 정체성
  // 단위 (x) .chain (f) ==== f (x)
  trace ( 'Id monad left identity') ([
    Id (x) .chain (f),
    에프 엑스
  ]);
  // 아이디 모나드 ID : ID (40), ID (40)
  // 올바른 정체성
  // m. 체인 (단위) ==== m
  trace ( 'Id 모나드 권리 아이덴티티') ([
    Id (x) .chain (Id.of),
    Id (x)
  ]);
  // ID 모나드 권리 ID : Id (20), Id (20)
  // 연관성
  // m.chain (f) .chain (g) ====
  // m.chain (x => f (x) .chain (g)
  자취 ( 'Id 모나드 연관성') ([
    Id (x) .chain (g) .chain (f),
    Id (x) .chain (x => g (x) .chain (f))
  ]);
  // Id 모나드 연관성 : Id (42), Id (42)
}

결론

모나드는 타입 리프팅 함수를 구성하는 방법입니다 : g : a => M (b), f : b => M (c). 이를 위해 모나드는 f ()를 적용하기 전에 M (b)를 b로 평평해야합니다. 다시 말해 펑 터는 매핑 할 수있는 것입니다. 모나드는 당신이 평평하게 할 수있는 것들입니다.

  • 함수 맵 : a => b
  • 컨텍스트가있는 Functors 맵 : Functor (a) => Functor (b)
  • Monads가 전개되고 컨텍스트가있는 맵 : Monad (Monad (a)) => Monad (b)

모나드는 간단한 대칭을 기반으로합니다. 값을 컨텍스트에 래핑하는 방법과 컨텍스트에서 값을 래핑하는 방법입니다.

  • Lift / Unit : 어떤 타입에서 모나드 문맥으로 타입 리프트 : a => M (a)
  • 병합 / 결합 : 컨텍스트에서 유형 풀기 : M (a) => a

모나드는 또한 펑터이기 때문에 다음과 같이 매핑 할 수도 있습니다.

  • 맵 : 컨텍스트가 유지 된 맵 : M (a)-> M (b)

Flatten을 맵과 결합하면 Kleisli 컴포지션 인 리프팅 함수를위한 함수 컴포지션을 얻을 수 있습니다.

  • FlatMap / Chain Flatten +지도 : M (M (a)) => M (b)

모나드는 모나드 법칙으로 알려진 세 가지 법칙 (축)을 충족해야합니다.

  • 왼쪽 동일성 : unit (x) .chain (f) ==== f (x)
  • 올바른 정체성 : m.chain (unit) ==== m
  • 연관성 : m.chain (f) .chain (g) ==== m.chain (x => f (x) .chain (g)

매일 JavaScript 코드에서 발생할 수있는 모나드의 예에는 약속 및 관찰 가능 항목이 포함됩니다. Kleisli 구성을 사용하면 데이터 유형 API의 특정 사항에 대해 걱정하지 않고 가능한 부작용, 조건부 분기 또는 chain () 작업에 숨겨진 언 래핑 계산의 기타 세부 사항에 대한 걱정없이 데이터 흐름 논리를 작성할 수 있습니다.

이를 통해 모나드는 코드를 단순화 할 수있는 매우 강력한 도구입니다. 모나드가 제공 할 수있는 단순화 된 이점을 얻기 위해 모나드 내부에서 일어나는 일을 이해하거나 걱정할 필요는 없지만 이제는 후드 아래에있는 것에 대해 더 많이 알기 때문에 후드 아래에서 엿봄을 취하는 것이 그렇게 무서운 전망은 아닙니다. .

모나드 그린의 저주를 두려워 할 필요가 없습니다.

라이브 1 : 1 멘토링으로 기술 수준을 높이십시오

DevAnywhere는 고급 JavaScript 기술 수준을 향상시키는 가장 빠른 방법입니다.

  • 라이브 레슨
  • 유연한 시간
  • 1 : 1 멘토링
  • 실제 프로덕션 앱 구축
https://devanywhere.io/

Eric Elliott는“JavaScript 응용 프로그램 프로그래밍”(O'Reilly)의 저자이며 DevAnywhere.io의 공동 설립자입니다. 그는 Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC 및 Usher, Frank Ocean, Metallica 등을 포함한 최고의 레코딩 아티스트를위한 소프트웨어 경험에 기여했습니다.

그는 세계에서 가장 아름다운 여성과 함께 원하는 곳 어디에서나 일합니다.