평범한 영어 모나드? (FP 배경이없는 OOP 프로그래머의 경우)


743

OOP 프로그래머가 (기능적 프로그래밍 배경없이) 이해한다는 관점에서 모나드는 무엇입니까?

어떤 문제를 해결하고 가장 많이 사용되는 장소는 무엇입니까?

편집하다:

내가 찾고있는 이해를 명확히하기 위해 모나드가있는 FP 응용 프로그램을 OOP 응용 프로그램으로 변환한다고 가정 해 봅시다. 모나드의 책임을 OOP 앱으로 포팅하기 위해 무엇을 하시겠습니까?


10
이 블로그 게시물은 매우 좋습니다 : blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html
Pascal Cuoq


10
@Pavel : Eric에서 아래에 얻은 답변 은 FP 배경과 달리 OO 배경을 가진 사람들에게 제안 된 다른 Q의 것보다 훨씬 낫습니다.
Donal Fellows

5
@Donal :이 경우 입니다 속는 사람이 (이에 대해 내가 아무 생각이 없다), 좋은 답변이 원래 추가해야합니다. 즉, 좋은 대답은 복제본을 닫는 것을 배제하지 않습니다. 복제본이 충분히 근접한 경우 중재자가 병합으로 수행 할 수 있습니다.
dmckee --- 전 운영자 고양이

답변:


732

업데이트 :이 질문은 상당히 긴 블로그 시리즈의 주제였습니다.이 기사는 Monads 에서 읽을 수 있습니다 . 훌륭한 질문에 감사드립니다!

OOP 프로그래머가 (기능적 프로그래밍 배경없이) 이해한다는 관점에서 모나드는 무엇입니까?

모나드는 인 유형의 "증폭" 특정 규칙을 따르는제공된 특정 작업을 갖는다 .

먼저, "타입 증폭기"란 무엇입니까? 그것은 당신이 타입을 가져 와서 더 특별한 타입으로 바꿀 수있는 시스템을 의미합니다. 예를 들어 C #에서는을 고려하십시오 Nullable<T>. 이것은 유형의 증폭기입니다. 예를 들어 유형을 가져 와서 해당 유형 int에 새로운 기능을 추가 할 수 있습니다. 즉 이전에는 불가능했을 때 null이 될 수 있습니다.

두 번째 예로,을 고려하십시오 IEnumerable<T>. 유형의 증폭기입니다. 즉, 유형을 가져 와서 해당 유형 string에 새로운 기능을 추가 할 수 있습니다. 즉, 이제는 여러 개의 단일 문자열에서 일련의 문자열을 만들 수 있습니다.

"확실한 규칙"은 무엇입니까? 간단히 말해서, 기본 유형의 기능이 정상적인 유형의 기능 구성 규칙을 따르도록 증폭 된 유형에서 작동하는 현명한 방법이 있습니다. 예를 들어 정수에 함수가 있다면

int M(int x) { return x + N(x * 2); }

그러면 해당 기능을 Nullable<int>통해 모든 연산자와 호출이 이전과 같은 방식으로 함께 작동 할 수 있습니다.

(이것은 엄청나게 모호하고 부정확합니다. 기능 구성에 대한 지식에 대해서는 아무 것도 가정하지 않은 설명을 요청했습니다.)

"작업"은 무엇입니까?

  1. 일반 유형에서 값을 가져와 동등한 모나드 값을 생성하는 "단위"연산 (혼란 적으로 "리턴"연산이라고도 함)이 있습니다. 본질적으로 이것은 증폭되지 않은 유형의 값을 가져 와서 증폭 된 유형의 값으로 바꾸는 방법을 제공합니다. OO 언어의 생성자로 구현 될 수 있습니다.

  2. 모나드 값과 값을 변환 할 수있는 함수를 가져 와서 새 모나드 값을 반환하는 "바인드"연산이 있습니다. 바인드는 모나드의 의미를 정의하는 핵심 작업입니다. 이를 통해 증폭되지 않은 유형의 연산을 증폭 된 유형의 연산으로 변환 할 수 있으며, 이는 앞에서 언급 한 기능 구성 규칙을 따릅니다.

  3. 증폭되지 않은 유형을 증폭 된 유형에서 다시 가져 오는 방법이 종종 있습니다. 엄밀히 말하면이 작업에는 모나드가 필요하지 않습니다. ( comonad 를 원한다면 필요하지만 이 기사에서는 더 이상 고려하지 않을 것이다.)

다시 한 번 Nullable<T>예를 들어 보자. 생성자를 사용하여 int로 바꿀 수 있습니다 Nullable<int>. C # 컴파일러는 널 입력 가능 "리프팅"을 처리하지만, 그렇지 않은 경우 리프팅 변환이 간단합니다.

int M(int x) { whatever }

로 변환

Nullable<int> M(Nullable<int> x) 
{ 
    if (x == null) 
        return null; 
    else 
        return new Nullable<int>(whatever);
}

그리고 재산 Nullable<int>으로 다시 돌아가는 int것은 Value재산으로 이루어집니다 .

핵심 비트 인 함수 변환입니다. Nullable 연산의 실제 의미 ( null확산 에 대한 연산) null가 변환에서 어떻게 캡처 되는지 확인합니다 . 우리는 이것을 일반화 할 수 있습니다.

당신이에서 함수가 있다고 가정 intint우리의 원래처럼 M. nullable 생성자를 통해 결과를 실행할 수 있기 때문에 intand를 반환 하는 함수로 쉽게 만들 수 있습니다 Nullable<int>. 이제 다음과 같은 고차 방법이 있다고 가정하십시오.

static Nullable<T> Bind<T>(Nullable<T> amplified, Func<T, Nullable<T>> func)
{
    if (amplified == null) 
        return null;
    else
        return func(amplified.Value);
}

당신이 그걸로 무엇을 할 수 있습니까? an int을 반환하거나 int, inta Nullable<int>를 반환하고 a 를 반환하는 모든 메서드 는 이제 nullable 의미를 적용 할 수 있습니다.

또한 : 두 가지 방법이 있다고 가정하십시오.

Nullable<int> X(int q) { ... }
Nullable<int> Y(int r) { ... }

그리고 당신은 그것들을 작성하고 싶습니다 :

Nullable<int> Z(int s) { return X(Y(s)); }

즉, Z의 조성물 XY. 때문에하지만 당신은 할 수 없어 X을 소요 int하고, Y리턴한다 Nullable<int>. 그러나 "바인드"작업이 있으므로 다음 작업을 수행 할 수 있습니다.

Nullable<int> Z(int s) { return Bind(Y(s), X); }

모나드에 대한 바인드 작업은 증폭 된 유형의 함수 구성을 작동시키는 것입니다. 위에서 언급 한 "규칙"은 모나드가 정상적인 기능 구성 규칙을 유지한다는 것입니다. 아이덴티티 함수로 구성하면 원래의 함수가되고 그 컴포지션은 연관됩니다.

C #에서 "Bind"는 "SelectMany"라고합니다. 시퀀스 모나드에서 어떻게 작동하는지 살펴보십시오. 값을 시퀀스로 바꾸고 시퀀스에 대한 연산을 바인딩하는 두 가지가 필요합니다. 보너스로, 우리는 또한 "시퀀스를 가치로 되돌려 놓았습니다". 이러한 작업은 다음과 같습니다.

static IEnumerable<T> MakeSequence<T>(T item)
{
    yield return item;
}
// Extract a value
static T First<T>(IEnumerable<T> sequence)
{
    // let's just take the first one
    foreach(T item in sequence) return item; 
    throw new Exception("No first item");
}
// "Bind" is called "SelectMany"
static IEnumerable<T> SelectMany<T>(IEnumerable<T> seq, Func<T, IEnumerable<T>> func)
{
    foreach(T item in seq)
        foreach(T result in func(item))
            yield return result;            
}

nullable 모나드 규칙은 "nullable을 생성하는 두 함수를 결합하여 내부 함수가 null인지 확인하고, 그렇지 않으면 null을 생성하고 그렇지 않은 경우 결과와 함께 외부 함수를 호출합니다." 이것이 원하는 nullable의 의미입니다.

시퀀스 모나드 규칙은 "서열을 생성하는 두 함수를 결합하고, 내부 함수에 의해 생성 된 모든 요소에 외부 함수를 적용한 다음 모든 결과 시퀀스를 함께 연결하는 것"입니다. 모나드의 기본 의미론은 Bind/ SelectMany메소드 에서 캡처됩니다 . 이것은 모나드가 실제로 무엇을 의미 하는지 알려주는 방법입니다 .

우리는 더 잘할 수 있습니다. int 시퀀스와 int를 가져 와서 문자열 시퀀스를 생성하는 메소드가 있다고 가정하십시오. 하나의 입력이 다른 입력의 출력과 일치하는 한 다른 증폭 유형을 가져오고 반환하는 함수의 구성을 허용하도록 바인딩 작업을 일반화 할 수 있습니다.

static IEnumerable<U> SelectMany<T,U>(IEnumerable<T> seq, Func<T, IEnumerable<U>> func)
{
    foreach(T item in seq)
        foreach(U result in func(item))
            yield return result;            
}

이제이 개별 정수를 정수 시퀀스로 증폭합니다.이 특정 정수를 문자열 시퀀스로 증폭하고 문자열 시퀀스로 증폭합니다. 이제 두 연산을 모두 합치십시오. "문자열의 모든 순서." Monads를 사용하면 증폭 을 구성 할 수 있습니다 .

어떤 문제를 해결하고 가장 많이 사용되는 장소는 무엇입니까?

그것은 "싱글 톤 패턴이 어떤 문제를 해결합니까?"

모나드는 일반적으로 다음과 같은 문제를 해결하는 데 사용됩니다.

  • 이 유형에 대해 새로운 기능을 만들어야하지만이 유형에 대해 이전 기능을 결합하여 새로운 기능을 사용해야합니다.
  • 유형에 대한 많은 작업을 캡처하고 해당 작업을 컴포저 블 객체로 나타내어 적절한 일련의 작업을 나타낼 때까지 더 크고 더 큰 컴포지션을 구축 한 다음 결과에서 결과를 얻어야합니다.
  • 부작용을 싫어하는 언어로 부작용 작업을 깨끗하게 표현해야합니다.

C #은 디자인에서 모나드를 사용합니다. 이미 언급했듯이, nullable 패턴은 "아마도 모나드"와 매우 유사합니다. LINQ는 전적으로 모나드로 만들어졌습니다. 이 SelectMany방법은 작업 구성의 의미 론적 작업입니다. (Erik Meijer는 모든 LINQ 기능이 실제로 구현 될 수 있다는 점을 좋아 SelectMany합니다. 다른 모든 기능은 편의에 불과합니다.)

내가 찾고있는 이해를 명확히하기 위해 모나드가있는 FP 응용 프로그램을 OOP 응용 프로그램으로 변환한다고 가정 해 봅시다. 모나드의 책임을 OOP 앱에 이식하려면 어떻게해야합니까?

대부분의 OOP 언어에는 모나드 패턴 자체를 직접 표현할 수있는 충분한 유형의 시스템이 없습니다. 일반 유형보다 유형이 높은 유형을 지원하는 유형 시스템이 필요합니다. 그래서 나는 그것을 시도하지 않을 것입니다. 오히려 각 모나드를 나타내는 제네릭 형식을 구현하고 필요한 세 가지 작업을 나타내는 메서드를 구현합니다. 값을 증폭 된 값으로 변환, (아마도) 증폭 된 값을 값으로 변환 및 함수를 증폭되지 않은 값으로 변환 증폭 된 값에 대한 함수.

시작하기 좋은 곳은 C #에서 LINQ를 구현 한 방법입니다. SelectMany방법을 연구하십시오 . 시퀀스 모나드가 C #에서 어떻게 작동하는지 이해하는 것이 중요합니다. 매우 간단한 방법이지만 매우 강력합니다!


제안, 추가 읽기 :

  1. C #에서 모나드에 대한보다 심층적이고 이론적 인 설명 을 위해 주제에 대한 저의 ( Eric Lippert ) 동료 Wes Dyer의 기사를 강력히 추천 합니다. 이 기사는 모나드가 마지막으로 "클릭"했을 때 설명했습니다.
  2. 왜 모나드를 원할 수 있는지에 대한 좋은 예입니다 (예제에서 Haskell 사용) .
  3. 이전 기사의 "번역"을 JavaScript로 분류합니다.


17
이것은 좋은 대답이지만 내 머리는 난폭 해졌다. 나는 이번 주말에 그것을 따라 가고 쳐다보고 & 일이 해결되지 않고 내 머리에 의미가 있는지 질문합니다.
Paul Nathan

5
평소 Eric과 같은 훌륭한 설명. 좀 더 이론적이지만 여전히 흥미로운 토론을 위해 MinLINQ에 대한 Bart De Smet의 블로그 게시물은 일부 함수형 프로그래밍 구조를 C #과 다시 관련시키는 데 도움이되는 것으로 나타났습니다. community.bartdesmet.net/blogs/bart/archive/2010/01/01/…
Ron Warholic

41
유형을 증폭시키는 대신 기능을 강화 한다고 말하는 것이 더 합리적 입니다.
Gabe

61
@ slomojo : 그리고 나는 내가 쓰고 쓰는 의도로 다시 바꿨다. 당신과 Gabe가 당신 자신의 답변을 쓰고 싶다면 바로 진행하십시오.
Eric Lippert

24
@Eric, 물론 당신에게 달려 있지만 앰프는 기존 속성이 향상되었음을 암시합니다.
ocodo

341

왜 모나드가 필요합니까?

  1. 함수 만 사용하여 프로그램하고 싶습니다 . (결국 -FP 후에 "기능 프로그래밍").
  2. 그런 다음 첫 번째 큰 문제가 있습니다. 이것은 프로그램입니다 :

    f(x) = 2 * x

    g(x,y) = x / y

    무엇을 먼저 실행해야하는지 어떻게 알 수 있습니까? 우리는 어떻게 함수를 사용하여 정렬 된 순서의 함수 (즉 , 프로그램 )를 형성 할 수 있습니까?

    솔루션 : 작성 기능 . 당신이 먼저 원하는 g다음 f, 그냥 작성하십시오 f(g(x,y)). 좋습니다만 ...

  3. 더 많은 문제 : 일부 기능 이 실패 할 수 있습니다 (예 : g(2,0)0으로 나누기). FP에는 "예외"없습니다 . 우리는 그것을 어떻게 해결합니까?

    솔루션 : 함수가 두 종류의 것을 반환하도록 하겠습니다 : g : Real,Real -> Real(두 개의 실수에서 실제로 기능하는) 대신 , g : Real,Real -> Real | Nothing(두 개의 실수에서 (실제로 또는 아무것도)로) 함수를 허용합시다 .

  4. 그러나 함수는 (단순하게) 한 가지만 반환 해야 합니다.

    솔루션 : 반환 할 새로운 유형의 데이터, 즉 실제 또는 전혀 아무것도 포함하지 않는 " boxing type "을 만들어 봅시다 . 따라서 우리는 가질 수 있습니다 g : Real,Real -> Maybe Real. 좋습니다만 ...

  5. 이제 어떻게됩니까 f(g(x,y))? f을 (를) 사용할 준비가되지 않았습니다 Maybe Real. 그리고 우리 g는를 사용할 수있는 모든 함수를 변경하고 싶지 않습니다 Maybe Real.

    솔루션 : "connect"/ "compose"/ "link"기능을위한 특수 기능을 갖도록 합시다 . 이런 식으로, 우리는 뒤에서 한 함수의 출력을 다음 함수를 제공하도록 조정할 수 있습니다.

    우리의 경우 : g >>= f(connect / compose gto f). 우리는 출력 >>=을 얻고 g, 검사하고, Nothing단지 호출 f하고 반환 하지 않는 경우를 원합니다 Nothing. 또는 반대로, 박스를 추출하여 Real공급 f하십시오. (이 알고리즘은 유형 에 >>=대한 구현 일뿐입니다 Maybe).

  6. 동일한 패턴을 사용하여 해결할 수있는 다른 많은 문제가 발생합니다. 1. "상자"를 사용하여 다른 의미 / 값을 코드화 / 저장하고 g이러한 "상자 값"을 반환하는 것과 같은 기능을 갖습니다 . 2. 의 출력을 의 입력에 g >>= f연결하는 데 도움을주는 작곡가 / 링커 가 있으므로 전혀 변경할 필요가 없습니다 .gff

  7. 이 기술을 사용하여 해결할 수있는 놀라운 문제는 다음과 같습니다.

    • 일련의 기능 ( "프로그램")에서 모든 기능이 공유 할 수있는 전역 상태를 갖는 경우 : solution StateMonad.

    • 우리는 "불완전한 기능"을 좋아하지 않습니다 : 동일한 입력에 대해 다른 출력을 생성하는 기능 . 따라서 해당 함수를 표시하여 태그 / 박스 값을 반환합니다 : monad.IO

총 행복 !!!!


2
@DmitriZaitsev 예외는 내가 아는 한 "불완전한 코드"(IO 모나드)에서만 발생할 수 있습니다.
cibercitizen1

3
@DmitriZaitsev Nothing의 역할은 다른 유형 (예상 Real과는 다름)으로 재생할 수 있습니다. 그것은 요점이 아닙니다. 이 예에서, 문제는 체인을 연결하지 않고 이전 값이 예기치 않은 값 유형을 다음 유형으로 반환 할 수있는 경우 (실수 만 입력으로 허용) 체인에서 함수를 조정하는 방법입니다.
cibercitizen1

3
- 또 다른 혼란 포인트는 단어가 당신의 대답에 두 번 밖에 표시, 단지 다른 용어와 함께 "모나드"이다 StateIO주어지고 그들 중 누구도뿐만 아니라 "모나드"의 정확한 의미와 함께,
드미트리 Zaitsev

31
OOP 배경에서 온 사람에게는이 답변이 모나드를 갖는 동기와 모나드가 실제로 무엇인지에 대해 잘 설명했습니다. 그래서 나는 그것이 매우 도움이된다고 생각합니다. 감사합니다 @ cibercitizen1 및 +1
akhilless

3
나는 약 1 년 동안 함수형 프로그래밍에 대해 읽고 있었다. 이 대답, 특히 처음 두 가지 점은 마침내 명령형 프로그래밍이 실제로 의미하는 것과 기능 프로그래밍이 다른 이유를 이해하게했습니다. 감사합니다!
jrahhali

82

모나드와 가장 유사한 OO 비유는 " 명령 패턴 "이라고합니다.

명령 패턴에서는 일반 명령문 또는 표현식을 명령 오브젝트로 랩핑 합니다. 명령 객체 는 래핑 된 명령문을 실행 하는 실행 메소드를 노출합니다 . 따라서 문은 일급 객체로 바뀌어 마음대로 전달하고 실행할 수 있습니다. 명령 개체를 연결하고 중첩하여 프로그램 개체를 만들 수 있도록 명령을 구성 할 수 있습니다.

명령은 별도의 객체 invoker에 의해 실행됩니다 . 일련의 일반 문을 실행하는 대신 명령 패턴을 사용하면 다른 호출자가 명령 실행 방법에 다른 논리를 적용 할 수 있다는 이점이 있습니다.

명령 패턴을 사용하여 호스트 언어에서 지원하지 않는 언어 기능을 추가하거나 제거 할 수 있습니다. 예를 들어, 예외가없는 가상의 OO 언어에서 "try"및 "throw"메소드를 명령에 노출하여 예외 의미를 추가 할 수 있습니다. 명령 호출이 throw되면 호출자는 마지막 "시도"호출까지 명령 목록 (또는 트리)을 역 추적합니다. 반대로, 각 개별 명령에 의해 발생 된 모든 예외를 포착 한 다음 오류 코드로 변환하여 다음 명령으로 전달 함으로써 언어에서 예외 시맨틱을 제거 할 수 있습니다 ( 예외가 나쁜 것으로 판단되는 경우 ).

트랜잭션, 비 결정적 실행 또는 연속과 같은 훨씬 더 멋진 실행 의미는 기본적으로 지원하지 않는 언어로 이와 같이 구현 될 수 있습니다. 당신이 그것에 대해 생각하면 그것은 매우 강력한 패턴입니다.

이제 실제로는 명령 패턴이 이와 같은 일반적인 언어 기능으로 사용되지 않습니다. 각 문장을 별도의 클래스로 바꾸는 오버 헤드로 인해 참을 수없는 상용구 코드가 생길 수 있습니다. 그러나 원칙적으로 fp에서 모나드를 사용하는 것과 동일한 문제를 해결하는 데 사용할 수 있습니다.


15
나는 이것이 함수형 프로그래밍 개념에 의존하지 않고 실제 OOP 용어로 넣은 최초의 모나드 설명이라고 생각합니다. 정말 좋은 대답입니다.
David K. Hess

이것은 모나드가 실제로 FP / Haskell에있는 것과 매우 가깝습니다. 단, 명령 오브젝트 자체가 어떤 "호출 로직"에 속하는지 "알고"(호환 가능한 로직 만 서로 연결될 수 있음) 제외합니다. 호출자는 첫 번째 값만 제공합니다. "인쇄"명령이 "비 결정적 실행 로직"에 의해 실행될 수있는 것과는 다릅니다. 아니요, "I / O 로직"(즉, IO 모나드)이어야합니다. 그러나 그 외에는 매우 가깝습니다. 심지어 Monads는 단지 프로그램 (나중에 실행될 코드 설명으로 구축 됨) 이라고 말할 수도 있습니다. 초기에는 "바인드"가 "프로그램 가능한 세미콜론" 으로 언급되었습니다 .
Will Ness

1
@ DavidK.Hess 필자는 기본 FP 개념을 설명하기 위해 FP를 사용하는 답변, 특히 스칼라와 같은 FP 언어를 사용하는 답변에 대해 매우 회의적입니다. 잘 했어 JacquesB!
복원 Monica Monica

62

OOP 프로그래머가 (기능적 프로그래밍 배경없이) 이해한다는 관점에서 모나드는 무엇입니까?

어떤 문제가 해결되고 가장 많이 사용되는 장소는 무엇입니까? 가장 많이 사용되는 장소는 무엇입니까?

OO 프로그래밍의 관점에서 모나드는 두 가지 방법으로 유형별로 매개 변수가 지정된 인터페이스 (또는 믹스 인 일 가능성 있음) return이며 다음 bind을 설명합니다.

  • 주입 된 값 유형의 모노 값을 얻기 위해 값을 주입하는 방법;
  • 비 모나 딕 값에서 모나 딕 값을 모나 딕 값으로 만드는 함수를 사용하는 방법.

이 문제가 해결하는 문제는 인터페이스에서 기대할 수있는 것과 같은 유형의 문제입니다. 즉, "다른 일을하는 여러 가지 클래스가 있지만 그와 비슷한 방식으로 다른 일을하는 것 같습니다." 클래스 자체가 실제로 'Object'클래스 자체보다 가까운 하위 유형이 아닌 경우에도 이들 간의 유사성을 설명 할 수 있습니까? "

보다 구체적으로, Monad"인터페이스"는 그 자체가 타입을 취하는 타입을 취한다는 점 과 유사 IEnumerator하거나 유사하다 IIterator. Monad그럼에도 불구하고 주요 "포인트" 는 메인 클래스의 정보 구조를 유지하거나 강화하면서 새로운 "내부 유형"을 갖는 점까지도 내부 유형을 기반으로 작업을 연결할 수 있습니다.


1
return모나드 인스턴스를 인수로 사용하지 않기 때문에 실제로 모나드에 대한 메소드가 아닙니다. (즉, 이것 / 자체는 없다)
Laurence Gonsalves

@LaurenceGonsalves : 현재 학사 학위 논문을 검토 중이므로 C # / Java의 인터페이스에 정적 메서드가 부족하다는 점이 가장 제한적이라고 생각합니다. 적어도 모나드 스토리를 구현하는 방향으로 최소한 타입 클래스를 기반으로하는 대신 정적으로 바인딩 된 방향으로 멀리 갈 수 있습니다. 흥미롭게도, 이것은 높은 종류의 유형이 부족하더라도 작동합니다.
Sebastian Graf

42

크리스토퍼 리그 (2010 년 7 월 12 일)의 " Monadologie-유형 불안에 대한 전문적인 도움 "이 발표 되었습니다 . 이 (슬라이드 쉐어) 프레젠테이션과 함께 진행되는 비디오는 실제로 vimeo에서 이용할 수 있습니다 . Monad 파트는이 1 시간짜리 비디오에서 약 37 분 안에 시작하여 58 개의 슬라이드 프리젠 테이션 중 슬라이드 42로 시작합니다.

"함수 프로그래밍을위한 최고의 디자인 패턴"으로 표시되지만이 예제에 사용 된 언어는 OOP와 기능성 인 Scala입니다.
당신은 블로그 게시물에서 스칼라에서 모나드에 더 읽을 수있다 " 모나드 - 스칼라 추상 계산하는 또 다른 방법 "에서, Debasish 고쉬 (3 월 (27), 2008).

형식 생성자 M은 다음 작업을 지원하는 경우 모나드입니다.

# the return function
def unit[A] (x: A): M[A]

# called "bind" in Haskell 
def flatMap[A,B] (m: M[A]) (f: A => M[B]): M[B]

# Other two can be written in term of the first two:

def map[A,B] (m: M[A]) (f: A => B): M[B] =
  flatMap(m){ x => unit(f(x)) }

def andThen[A,B] (ma: M[A]) (mb: M[B]): M[B] =
  flatMap(ma){ x => mb }

예를 들어 (스칼라에서) :

  • Option 모나드입니다
    데프 유닛 [A] (x : A) : 옵션 [A] = 약간 (x)

    def flatMap [A, B] (m : 옵션 [A]) (f : A => 옵션 [B]) : 옵션 [B] =
      m 일치 {
       case None => 없음
       case Some (x) => f (x)
      }
  • List 모나드입니다
    데프 유닛 [A] (x : A) :리스트 [A] =리스트 (x)

    def flatMap [A, B] (m : 목록 [A]) (f : A => 목록 [B]) : 목록 [B] =
      m 일치 {
        경우 Nil => Nil
        사례 x :: xs => f (x) ::: flatMap (xs) (f)
      }

Monad는 Monad 구조를 이용하기 위해 만들어진 편리한 구문 때문에 스칼라에서 큰 문제입니다.

for스칼라에 대한 이해 :

for {
  i <- 1 to 4
  j <- 1 to i
  k <- 1 to j
} yield i*j*k

컴파일러는 다음과 같이 번역합니다.

(1 to 4).flatMap { i =>
  (1 to i).flatMap { j =>
    (1 to j).map { k =>
      i*j*k }}}

주요 추상화는 flatMap체인을 통해 계산을 바인딩하는입니다.
각 호출은 flatMap체인의 다음 명령에 대한 입력으로 사용되는 동일한 데이터 구조 유형 (그러나 다른 값) 을 리턴합니다.

위의 스 니펫에서 flatMap은 클로저를 입력으로 사용 (SomeType) => List[AnotherType]하고를 반환합니다 List[AnotherType]. 주목해야 할 중요한 점은 모든 flatMap은 입력과 동일한 클로저 유형을 사용하고 출력과 동일한 유형을 반환한다는 것입니다.

이것이 계산 스레드를 "바인딩"하는 것입니다. 이해를위한 시퀀스의 모든 항목은 동일한 유형 제약 조건을 준수해야합니다.


두 가지 작업을 수행하면 (실패 할 수 있음) 다음과 같이 세 번째 작업에 결과를 전달합니다.

lookupVenue: String => Option[Venue]
getLoggedInUser: SessionID => Option[User]
reserveTable: (Venue, User) => Option[ConfNo]

그러나 Monad를 사용하지 않으면 다음과 같은 복잡한 OOP 코드를 얻을 수 있습니다.

val user = getLoggedInUser(session)
val confirm =
  if(!user.isDefined) None
  else lookupVenue(name) match {
    case None => None
    case Some(venue) =>
      val confno = reserveTable(venue, user.get)
      if(confno.isDefined)
        mailTo(confno.get, user.get)
      confno
  }

반면 Monad를 사용하면 모든 연산 작업과 같은 실제 유형 ( Venue, User)으로 작업하고 for 구문의 플랫 맵으로 인해 옵션 확인 항목을 숨길 수 있습니다.

val confirm = for {
  venue <- lookupVenue(name)
  user <- getLoggedInUser(session)
  confno <- reserveTable(venue, user)
} yield {
  mailTo(confno, user)
  confno
}

수익률 부분은 세 함수가 모두있는 경우에만 실행됩니다 Some[X]. 어떤 None직접 반환 될 것이다 confirm.


그래서:

Monads는 Functional Programing 내에서 정렬 된 계산을 허용합니다.이를 통해 DSL과 같은 멋진 구조화 된 형태로 동작 순서를 모델링 할 수 있습니다.

그리고 가장 큰 힘은 다양한 목적으로 사용되는 모나드를 응용 프로그램 내에서 확장 가능한 추상화로 구성 할 수 있다는 것입니다.

이 모나드에 의한 동작의 시퀀싱 및 스레딩은 폐쇄의 마법을 통해 변환을 수행하는 언어 컴파일러에 의해 수행됩니다.


그런데 Monad는 FP에서 사용되는 계산 모델이 아닙니다.

카테고리 이론은 많은 계산 모델을 제안합니다. 그들 중

  • 계산의 화살표 모델
  • 모나드 계산 모델
  • 적용 계산 모델

2
나는이 설명을 좋아한다! 제시 한 예제는 개념을 아름답게 보여주고 SelectMany ()가 Monad라는 Eric의 티저에서 IMHO가 빠뜨린 부분을 추가합니다. 이것에 대한 Thx!
aoven

1
IMHO 이것은 가장 우아한 답변입니다
Polymerase

그리고 다른 모든 것보다 먼저 Functor.
윌 네스

34

빠른 독자를 존중하기 위해 먼저 정확한 정의부터 시작하여보다 "일반적인 영어"설명을 계속 한 다음 예제로 이동하십시오.

간결하고 정확한 정의는 다음과 같습니다 .

모나드 (컴퓨터 과학)는지도가 공식적이다 :

  • X주어진 프로그래밍 언어의 모든 유형 을 새로운 유형 T(X)( " T에 값이있는 -computations "이라고 함)으로 보냅니다 X.

  • 두 가지 형태의 기능 구성에 대한 규칙 구비 f:X->T(Y)하고 g:Y->T(Z)기능을 행 g∘f:X->T(Z);

  • 명백한 의미로 연관되고이라는 주어진 단위 함수와 관련하여, pure_X:X->T(X)그 값을 단순히 반환하는 순수한 계산에 값을 취하는 것으로 생각됩니다.

간단히 말해서 모나드모든 유형 X에서 다른 유형 T(X)으로 전달되는 규칙이며 두 함수 f:X->T(Y)에서 g:Y->T(Z)(구성하고 싶지만 작성할 수는 없음) 새로운 함수 로 전달h:X->T(Z) 하는 규칙 입니다. 그러나 엄격한 수학적 의미 의 구성은 아닙니다 . 우리는 기본적으로 함수의 구성을 "굽힘"하거나 함수 구성 방법을 재정의하고 있습니다.

또한, "명백한"수학 공리를 만족시키기 위해 모나드의 작성 규칙이 필요합니다.

  • 연관성 : 작곡 f으로 g다음으로는 h(외부) 구성과 동일해야 g함께 h다음으로 f(안쪽으로).
  • 단위 속성 : 양쪽 f항등 함수로 구성 하면 양보해야합니다 f.

다시 말하지만, 간단히 말해서, 우리가 원하는대로 함수 구성을 재정의하는 것에 열중 할 수는 없습니다.

  • 예를 들어 여러 함수를 한 행에 구성 할 수 있고 함수 f(g(h(k(x)))구성 순서를 지정하는 것에 대해 걱정하지 않으려면 먼저 연관성이 필요합니다 . 모나드 규칙은 그 공리없이 한 쌍의 함수 를 구성하는 방법 만 규정 하기 때문에 어떤 쌍이 먼저 구성되는지 등을 알아야합니다. ( f로 구성된 commutativity 특성과 다른 점은 g으로 g구성된 것과 같으므로 f필수는 아닙니다.)
  • 둘째, 우리는 단위 속성이 필요합니다. 이는 단순히 정체성이 우리가 기대하는 방식으로 사소하게 구성된다고 말하는 것입니다. 따라서 해당 ID를 추출 할 수있을 때마다 함수를 안전하게 리팩토링 할 수 있습니다.

간단히 말해서, 모나드는 타입 확장의 규칙이며 연관성과 단위 속성이라는 두 가지 공리를 만족시키는 함수를 구성합니다.

실제로는 함수 구성을 담당하는 언어, 컴파일러 또는 프레임 워크로 모나드를 구현하기를 원합니다. 따라서 함수가 어떻게 구현되는지 걱정하지 않고 함수의 논리를 작성하는 데 집중할 수 있습니다.

그것은 본질적으로 간단합니다.


전문 수학자이기 때문에 와 h의 "구성" 이라고 부르지 않는 것이 좋습니다. 수학적으로는 그렇지 않기 때문입니다. 그것을 "조성"이라고 부르는 것은 그것이 실제 수학적 구성 이라고 잘못 추정합니다 . 심지어 고유에 의해 결정되지 않습니다 및 . 대신 모나드의 새로운 "구성 규칙"의 결과입니다. 후자가 존재하더라도 실제 수학적 구성과는 완전히 다를 수 있습니다!fghfg


덜 건조하게 만들기 위해 작은 섹션으로 주석을 달고 있다는 것을 예로 들어 설명해 보겠습니다. 그래서 바로 건너 뛸 수 있습니다.

Monad 예제로 예외 발생

두 가지 기능을 작성한다고 가정 해보십시오.

f: x -> 1 / x
g: y -> 2 * y

그러나 f(0)정의되지 않았으므로 예외 e가 발생합니다. 그러면 작곡가를 어떻게 정의 할 수 g(f(0))있습니까? 물론 예외를 다시 던져라! 아마 같은 것 e입니다. 새로운 업데이트 된 예외 일 수 e1있습니다.

여기서 정확히 무슨 일이? 먼저 새로운 예외 값이 필요합니다 (다른 또는 동일). 당신이 그들을 호출 할 수 있습니다 nothing또는 null또는 무엇이든을하지만 본질은 동일하게 유지 - 그들은 그것이 안, 예를 들어, 새로운 값을해야 number우리의 예에서 여기. 특정 언어로 구현 null하는 방법과 혼동을 피하기 위해 전화하지 않는 것이 null좋습니다. 마찬가지로, 나는 원칙적으로 해야 할 일과 nothing관련이 있기 때문에 피하는 것을 선호 하지만, 그 원칙은 실제적인 이유에서 종종 구부러집니다.nullnull

예외 란 무엇입니까?

이것은 숙련 된 프로그래머에게는 사소한 문제이지만 혼란의 벌레를 없애기 위해 몇 마디 만 포기하고 싶습니다.

예외는 잘못된 실행 결과가 발생한 방법에 대한 정보를 캡슐화하는 개체입니다.

여기에는 세부 사항을 버리고 단일 전역 값 (예 : NaN또는 null)을 반환 하거나 긴 로그 목록을 생성하거나 정확히 무슨 일이 있었는지,이를 데이터베이스로 보내 분산 된 데이터 스토리지 계층 전체에 복제하는 것 등이 있습니다.

이 두 가지 예외 사례의 중요한 차이점은 첫 번째 경우 부작용없다는 것 입니다. 두 번째에는 있습니다. 이것은 우리에게 (천 달러) 질문을 가져옵니다.

순수한 기능에서 예외가 허용됩니까?

짧은 대답 : 예, 그러나 부작용을 일으키지 않을 때만 가능합니다.

더 긴 대답. 순수하게 함수의 출력은 입력에 의해 고유하게 결정되어야합니다. 따라서 예외라고 하는 새로운 추상 값 f으로 보내서 함수 를 수정합니다 . 입력 값에 의해 고유하게 결정되지 않은 외부 정보 가 값에 포함되지 않도록합니다 . 부작용이없는 예외의 예는 다음과 같습니다.0eex

e = {
  type: error, 
  message: 'I got error trying to divide 1 by 0'
}

그리고 여기 부작용이 있습니다 :

e = {
  type: error, 
  message: 'Our committee to decide what is 1/0 is currently away'
}

실제로 나중에 해당 메시지가 변경 될 수있는 경우에만 부작용이 있습니다. 그러나 절대로 변하지 않는다고 보장되면 그 가치는 유일하게 예측 가능 해져 부작용이 없습니다.

더 조용하게 만들기 위해. 돌아 오는 함수 42는 분명히 순수합니다. 그러나 누군가 미친 42값으로 변수를 변경 하기로 결정 하면 새로운 조건에서 동일한 기능이 순수하게 중단됩니다.

간단하게 본질을 설명하기 위해 객체 리터럴 표기법을 사용하고 있습니다. 불행히도 상황은 JavaScript와 같은 언어로 엉망입니다. 여기서 error함수 구성과 관련하여 우리가 원하는 방식으로 행동하는 유형은 아니지만 실제 유형은 이런 방식으로 행동 null하거나 NaN행동하지 않고 오히려 인공적이고 항상 직관적 인 것은 아닙니다. 유형 변환.

타입 확장

예외 내에서 메시지를 변경하고 싶을 E때 전체 예외 객체에 대해 새 유형 을 선언 한 다음 maybe number혼란스러운 이름을 제외하고는 유형 number또는 새 예외 유형 중 하나입니다. E그래서 정말 합집합 number | EnumberE. 특히, 그것은 우리가 어떻게 구성하고 싶은지에 달려 E있는데, 이것은 이름에 제안되거나 반영되지 않습니다 maybe number.

기능 구성이란 무엇입니까?

이것은 수학적 연산 촬영 기능이다 f: X -> Yg: Y -> Z함수로서의 조성물을 구성하고 h: X -> Z만족 h(x) = g(f(x)). 이 정의의 문제점은 결과 f(x)가의 인수로 허용되지 않는 경우에 발생합니다 g.

수학에서는 이러한 기능을 추가 작업없이 구성 할 수 없습니다. 우리 위 예 엄격 수학 용액 fg제거하는 0정의의 세트로부터 f. 새로운 정의 세트 (보다 제한적인 새 유형 x)를 사용 f하여와 (과 ) 조합 할 수 g있습니다.

그러나 프로그래밍에서 f이와 같은 정의를 제한하는 것은 그리 실용적이지 않습니다 . 대신 예외를 사용할 수 있습니다.

또는 다른 방법으로, 인공 값은 같은 생성 NaN, undefined, null, Infinity평가할 그래서 등 1/0으로 Infinity1/-0-Infinity. 그런 다음 예외를 발생시키지 않고 새로운 값을 표현식으로 되돌립니다. 예측 가능한 결과가 나오거나 그렇지 않을 수 있습니다.

1/0                // => Infinity
parseInt(Infinity) // => NaN
NaN < 0            // => false
false + 1          // => 1

그리고 우리는 계속 이동할 준비가 된 정규 숫자로 돌아갑니다.)

JavaScript를 사용하면 위의 예와 같이 오류를 발생시키지 않고 어떤 식 으로든 숫자 식을 계속 실행할 수 있습니다. 즉, 기능을 구성 할 수도 있습니다. 정확히 모나드에 관한 것입니다-이 답변의 시작 부분에 정의 된 공리를 만족시키는 함수를 작성하는 규칙입니다.

그러나 숫자 오류를 처리하기위한 JavaScript의 구현에서 발생하는 함수 구성 규칙은 모나드입니까?

이 질문에 답하기 위해 필요한 것은 공리를 확인하는 것입니다 (여기서는 질문의 일부가 아닌 운동으로 남습니다).

예외 처리를 사용하여 모나드를 구성 할 수 있습니까?

실제로,보다 유용한 모나드는 대신 f일부 예외를 던질 경우 x, 그 구성도 any로 구성되도록 규정하는 규칙이 될 것 g입니다. 또한 E하나의 가능한 값 ( 범주 이론의 터미널 객체) 으로 예외를 전역 적으로 고유 하게 만듭니다 . 이제 두 가지 공리를 즉시 확인할 수 있으며 매우 유용한 모나드를 얻습니다. 그리고 결과는 아마도 모나드 로 잘 알려진 입니다.


3
좋은 기여. +1 그러나 "너무 긴 설명이 너무 깁니다."를 삭제하고 싶을 수도 있습니다. 다른 사람들은 "필수 영어"가 "단순한 단어로 간단하게"라는 질문에 따라 "일반 영어"인지 판단 할 것입니다.
cibercitizen1

감사합니다! 예제를 세지 않으면 실제로 짧습니다. 요점은 정의를 이해하기 위해 예제를 읽을 필요 가 없다는 것 입니다. 불행히도 많은 설명 은 필자가 예제를 먼저 읽도록 강요 하는데, 이는 종종 불필요하지만 물론 작가를 위해 추가 작업이 필요할 수 있습니다. 특정 예에 지나치게 의존하면 중요하지 않은 세부 사항으로 인해 그림이 모호 해져 이해하기 어려워 질 위험이 있습니다. 당신은 유효한 포인트를 가지고 있다고 말했지만 업데이트를 참조하십시오.
Dmitri Zaitsev

2
너무 길고 혼란스러워
sawimurugan

1
@seenimurugan 개선 제안은 환영합니다;)
Dmitri Zaitsev

26

모나드는 값을 캡슐화하고 본질적으로 두 가지 연산을 적용 할 수있는 데이터 유형입니다.

  • return x 캡슐화하는 모나드 타입의 값을 생성 x
  • m >>= f( "바인드 연산자"로 읽음) f모나드의 값에 함수 를 적용m

그것이 모나드입니다. 있다 몇 가지 더 교칙는 하지만, 기본적으로이 두 작업은 모나드를 정의합니다. 실제 질문은 "모나드의 기능 무엇입니까 ?"이며 모나드에 따라 다릅니다. 목록은 모나드, 아마도 모나드, IO 작업은 모나드입니다. 우리가 그 것들을 모나드라고 말할 때, 모나드 인터페이스는 return및로 구성 >>=됩니다.


"모나드의 기능은 모나드에 따라 다릅니다":보다 정확하게 bind는 각 모나드 유형에 대해 정의해야하는 기능에 따라 달라 집니까? 컴포지션에 대한 단일 정의가 있기 때문에 바인드와 컴포지션을 혼동하지 않는 좋은 이유입니다. 바인드 함수에 대한 단일 정의 만있을 수는 없지만 올바르게 이해하면 모나드 유형 당 하나가 있습니다.
Hibou57

14

에서 위키 피 디아 :

함수형 프로그래밍에서 모나드는 도메인 모델의 데이터 대신 계산을 나타내는 데 사용되는 일종의 추상 데이터 유형입니다. Monads는 프로그래머가 액션을 서로 연결하여 파이프 라인을 구축 할 수있게하는데, 각 액션은 모나드가 제공하는 추가 처리 규칙으로 꾸며져 있습니다. 기능적 스타일로 작성된 프로그램은 모나드를 사용하여 시퀀싱 된 작업을 포함하는 프로 시저를 구조화 하거나 , 1 [2] 또는 임의의 제어 흐름 (예 : 동시성 처리, 연속성 또는 예외 처리)을 정의 할 수 있습니다.

공식적으로 모나드는 두 개의 연산 (바인드 및 리턴)과 모나드 함수의 올바른 구성을 허용하기 위해 여러 속성을 충족해야하는 형식 생성자 M (모나드의 값을 인수로 사용하는 함수)을 정의하여 구성됩니다. 리턴 조작은 일반 유형에서 값을 가져 와서 M 유형의 모나드 컨테이너에 넣습니다. 바인드 조작은 역 프로세스를 수행하여 컨테이너에서 원래 값을 추출하여 파이프 라인의 연관된 다음 함수에 전달합니다.

프로그래머는 모나 딕 함수를 작성하여 데이터 처리 파이프 라인을 정의합니다. 모나드는 파이프 라인의 특정 모나 딕 함수가 호출되는 순서를 결정하고 계산에 필요한 모든 비밀 작업을 관리하는 재사용 가능한 동작이므로 프레임 워크 역할을합니다. [3] 파이프 라인에 인터리브 된 바인드 및 리턴 연산자는 각 모나드 함수가 제어를 리턴 한 후에 실행되며 모나드가 처리하는 특정 측면을 처리합니다.

나는 그것이 그것을 잘 설명한다고 믿는다.


12

OOP 용어를 사용하여 관리 할 수있는 가장 짧은 정의를 만들려고합니다.

일반 클래스 CMonadic<T>는 최소한 다음 메소드를 정의하는 경우 모나드입니다.

class CMonadic<T> { 
    static CMonadic<T> create(T t);  // a.k.a., "return" in Haskell
    public CMonadic<U> flatMap<U>(Func<T, CMonadic<U>> f); // a.k.a. "bind" in Haskell
}

그리고 다음 법이 모든 유형 T 및 가능한 값에 적용되는 경우

왼쪽 정체성 :

CMonadic<T>.create(t).flatMap(f) == f(t)

올바른 정체성

instance.flatMap(CMonadic<T>.create) == instance

연관성 :

instance.flatMap(f).flatMap(g) == instance.flatMap(t => f(t).flatMap(g))

:

List 모나드는 다음을 가질 수 있습니다.

List<int>.create(1) --> [1]

그리고리스트 [1,2,3]의 flatMap은 다음과 같이 작동합니다 :

intList.flatMap(x => List<int>.makeFromTwoItems(x, x*10)) --> [1,10,2,20,3,30]

Iterables와 Observables는 약속과 작업뿐만 아니라 모나 딕으로 만들 수 있습니다.

해설 :

모나드는 그렇게 복잡하지 않습니다. 이 flatMap기능은 더 일반적으로 발생하는 것과 비슷합니다 map. 함수 인수 (대리자라고도 함)를 수신하며, 일반 인수에서 오는 값으로 즉시 또는 나중에 0 번 이상 호출 할 수 있습니다. 전달 된 함수는 반환 값을 동일한 종류의 일반 클래스로 래핑 할 것으로 예상합니다. 이를 돕기 create위해 값에서 해당 일반 클래스의 인스턴스를 작성할 수있는 생성자를 제공 합니다. flatMap의 리턴 결과는 동일한 유형의 일반 클래스이며, 종종 flatMap의 하나 이상의 애플리케이션의 리턴 결과에 포함 된 동일한 값을 이전에 포함 된 값으로 압축합니다. 이를 통해 flatMap을 원하는만큼 연결할 수 있습니다.

intList.flatMap(x => List<int>.makeFromTwo(x, x*10))
       .flatMap(x => x % 3 == 0 
                   ? List<string>.create("x = " + x.toString()) 
                   : List<string>.empty())

이런 종류의 제네릭 클래스는 수많은 것들에 대한 기본 모델로 유용합니다. 이것은 (범주 이론 전문 용어와 함께) Monads가 이해하거나 설명하기가 어려운 것처럼 보이는 이유입니다. 그것들은 매우 추상적이며 전문화 된 후에 만 ​​분명히 유용합니다.

예를 들어, 모나드 컨테이너를 사용하여 예외를 모델링 할 수 있습니다. 각 컨테이너에는 작업 결과 또는 발생한 오류가 포함됩니다. flatMap 콜백 체인의 다음 함수 (위임자)는 이전 함수가 컨테이너에 값을 묶은 경우에만 호출됩니다. 그렇지 않으면 오류가 압축 된 경우 오류 처리기 기능이있는 메서드가 호출 된 메서드를 통해 컨테이너가 발견 될 때까지 체인 컨테이너를 통해 오류가 계속 전파됩니다 .orElse()(이러한 메서드는 허용되는 확장 임).

참고 : 기능적 언어를 사용하면 모든 종류의 모나 딕 제네릭 클래스에서 작동 할 수있는 함수를 작성할 수 있습니다. 이것이 작동하려면 모나드에 대한 일반 인터페이스를 작성해야합니다. C #에서 그러한 인터페이스를 작성할 수 있는지는 알지 못하지만 아는 한 :

interface IMonad<T> { 
    static IMonad<T> create(T t); // not allowed
    public IMonad<U> flatMap<U>(Func<T, IMonad<U>> f); // not specific enough,
    // because the function must return the same kind of monad, not just any monad
}

7

모나드가 OO에서 "자연적인"해석을 가지고 있는지 여부는 모나드에 달려 있습니다. Java와 같은 언어에서는 어쩌면 모나드를 널 포인터 검사 언어로 변환하여 실패한 계산 (예 : Haskell에서 Nothing 생성)이 결과로 널 포인터를 생성 할 수 있습니다. 변경 가능한 변수 및 상태를 변경하는 메소드를 작성하여 상태 모나드를 생성 된 언어로 변환 할 수 있습니다.

모나드는 endofunctors 범주의 단일체입니다.

문장이 정리하는 정보는 매우 깊습니다. 그리고 당신은 명령형 언어로 모나드에서 일합니다. 모나드는 "시퀀스 된"도메인 특정 언어입니다. 모나드를 "제한적 프로그래밍"의 수학적 모델로 만드는 특정한 흥미로운 특성을 만족시킵니다. Haskell을 사용하면 다양한 방식으로 결합 할 수있는 작은 (또는 큰) 명령형 언어를 쉽게 정의 할 수 있습니다.

OO 프로그래머는 언어의 클래스 계층을 사용하여 컨텍스트에서 호출 할 수있는 함수 또는 프로 시저의 종류 (객체라고 함)를 구성합니다. 모나드는 다른 모나드가 임의의 방식으로 결합되어서 모든 서브-모나드의 방법을 범위 내로 효과적으로 "가져올"수있는 한이 아이디어에 대한 추상화입니다.

그런 다음 아키텍처는 유형 서명을 사용하여 값을 계산하는 데 사용할 수있는 컨텍스트를 명시 적으로 표현합니다.

이를 위해 모나드 변압기를 사용할 수 있으며 모든 "표준"모나드의 고품질 컬렉션이 있습니다.

  • 목록 (목록을 도메인으로 취급하여 비 결정적 계산)
  • 실패 (실패 할 수 있지만보고가 중요하지 않은 계산)
  • 오류 (실패 할 수 있고 예외 처리가 필요한 계산
  • 리더 (일반 Haskell 기능의 구성으로 표현할 수있는 계산)
  • 기록기 (순차적 "렌더링"/ "로깅"이있는 계산 (문자열, HTML 등)
  • 계속 (계속)
  • IO (기본 컴퓨터 시스템에 따라 계산)
  • 상태 (컨텍스트에 수정 가능한 값이 포함 된 계산)

해당 모나드 변압기 및 유형 클래스와 함께. 형식 클래스를 사용하면 모나드의 인터페이스를 통합하여 모나드를 결합하는 보완적인 접근 방식을 통해 콘크리트 모나드가 모나드 "종류"에 대한 표준 인터페이스를 구현할 수 있습니다. 예를 들어, Control.Monad.State 모듈은 MonadState sm 클래스를 포함하고 (State s)는 형식의 인스턴스입니다.

instance MonadState s (State s) where
    put = ...
    get = ...

긴 이야기는 모나드는 "컨텍스트"를 값에 첨부하는 펑터이며, 모나드에 값을 주입하는 방법이 있으며 적어도 그에 연결된 컨텍스트와 관련하여 값을 평가할 수있는 방법입니다. 제한된 방식으로.

그래서:

return :: a -> m a

a 유형의 값을 m a 유형의 모나드 "조치"에 주입하는 함수입니다.

(>>=) :: m a -> (a -> m b) -> m b

모나드 조치를 취하고 결과를 평가 한 후 함수를 결과에 적용하는 함수입니다. (>> =)에 대한 깔끔한 점은 결과가 동일한 모나드에 있다는 것입니다. 즉, m >> = f에서 (>> =)는 m에서 결과를 가져 와서 f에 바인딩하여 결과가 모나드에있게합니다. (또는 (>> =)는 f를 m으로 가져와 결과에 적용한다고 말할 수 있습니다.) 결과적으로 f :: a-> mb와 g :: b-> mc가 있으면 "시퀀스"동작 :

m >>= f >>= g

또는 "do notation"을 사용하여

do x <- m
   y <- f x
   g y

(>>)의 유형이 밝게 표시 될 수 있습니다. 그것은

(>>) :: m a -> m b -> m b

C와 같은 절차 적 언어에서 (;) 연산자에 해당합니다. 다음과 같은 표기법을 사용할 수 있습니다.

m = do x <- someQuery
       someAction x
       theNextAction
       andSoOn

수학적, 철학적 논리에는 모나드 즘으로 "자연스럽게"모델링 된 프레임과 모델이 있습니다. 해석은 모델의 영역을 조사하고 제안 (또는 일반화 하의 공식)의 진리 값 (또는 일반화)을 계산하는 함수입니다. 필요성에 대한 모달 논리에서, 우리는 "가능한 모든 세계"에서 사실이라면 모든 제안 가능한 영역에서 사실이라면 제안이 필요하다고 말할 수 있습니다. 이것은 제안을위한 언어로 된 모델이 고유 한 모델 (각각의 가능한 세계에 해당하는)의 컬렉션으로 구성되는 모델로 구체화 될 수 있음을 의미합니다. 모든 모나드는 레이어를 평평하게하는 "join"이라는 메소드를 가지고 있으며, 결과가 모나드 액션 인 모든 모나드 액션이 모나드에 임베드 될 수 있음을 의미합니다.

join :: m (m a) -> m a

더 중요한 것은 모나드가 "레이어 스태킹"작업에서 닫혀 있음을 의미합니다. 이것이 모나드 변압기의 작동 방식입니다. 모나드 변압기는

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

(MaybeT m)의 액션을 m의 액션으로 변환하여 레이어를 효과적으로 축소 할 수 있습니다. 이 경우 runMaybeT :: MaybeT ma-> m (Maybe a)는 결합 방식입니다. (MaybeT m)은 모나드이고, MaybeT :: m (Maybe a)-> MaybeT ma는 사실상 m의 새로운 유형의 모나드 동작을위한 생성자입니다.

functor를위한 free monad는 f를 쌓아서 생성 된 monad입니다. f에 대한 모든 생성자 시퀀스는 free monad의 요소입니다.보다 정확하게는 생성자의 시퀀스 트리와 같은 모양을 가진 것입니다. 에프). 자유 모나드는 최소량의 보일러 플레이트로 유연한 모나드를 구성하는 데 유용한 기술입니다. Haskell 프로그램에서, 무료 모나드를 사용하여 "높은 수준의 시스템 프로그래밍"에 대한 간단한 모나드를 정의하여 형식 안전성을 유지하는 데 도움을줍니다 (유형과 선언 만 사용하고 있습니다. 구현은 콤비 네이터를 사용하여 간단합니다).

data RandomF r a = GetRandom (r -> a) deriving Functor
type Random r a = Free (RandomF r) a


type RandomT m a = Random (m a) (m a) -- model randomness in a monad by computing random monad elements.
getRandom     :: Random r r
runRandomIO   :: Random r a -> IO a (use some kind of IO-based backend to run)
runRandomIO'  :: Random r a -> IO a (use some other kind of IO-based backend)
runRandomList :: Random r a -> [a]  (some kind of list-based backend (for pseudo-randoms))

Monadism은 모든 모나드 계산이 최소한 사소하게 "실행"되어야하기 때문에 가장 명확한 형식으로 추상화 된 "통역사"또는 "명령"패턴이라고 할 수있는 기본 아키텍처입니다. (런타임 시스템은 우리를 위해 IO 모나드를 실행하며 Haskell 프로그램의 시작점입니다. IO는 IO 액션을 순서대로 실행하여 나머지 계산을 "드라이브"합니다.

조인 유형은 모나드가 endofunctors 범주의 단일체라는 진술을 얻는 곳이기도합니다. 조인은 일반적으로 유형에 따라 이론적 목적에 더 중요합니다. 그러나 형식을 이해한다는 것은 모나드를 이해한다는 의미입니다. 결합 및 모나드 변환기의 결합 유사 유형은 기능 구성의 의미에서 효과적으로 endofunctor의 구성입니다. 하스켈 같은 의사 언어에 넣으려면

푸 :: m (ma) <-> (m. m) a


3

모나드는 함수의 배열입니다

(Pst : 함수 배열은 단지 계산 일뿐입니다).

실제로, 실제 배열 (한 셀 배열의 한 함수) 대신 다른 함수 >> =로 연결된 함수가 있습니다. >> =는 함수 i의 결과를 함수 i + 1에 공급하거나, 함수 i + 1 사이에 계산을 수행하거나, 함수 i + 1을 호출하지 않을 수 있습니다.

여기에 사용 된 유형은 "컨텍스트가있는 유형"입니다. 이것은 "태그"가있는 값입니다. 연결되는 함수는 "네이 킹 된 값"을 가져 와서 태그 된 결과를 반환해야합니다. >> =의 의무 중 하나는 컨텍스트에서 나체 값을 추출하는 것입니다. "return"기능도 있습니다.이 기능은 기본 값을 가져 와서 태그와 함께 사용합니다.

Maybe의 예입니다 . 이것을 사용하여 계산을 수행하는 간단한 정수를 저장합시다.

-- a * b
multiply :: Int -> Int -> Maybe Int
multiply a b = return  (a*b)

-- divideBy 5 100 = 100 / 5
divideBy :: Int -> Int -> Maybe Int
divideBy 0 _ = Nothing -- dividing by 0 gives NOTHING
divideBy denom num = return (quot num denom) -- quotient of num / denom

-- tagged value
val1 = Just 160 

-- array of functions feeded with val1
array1 = val1 >>= divideBy 2  >>= multiply 3 >>= divideBy  4 >>= multiply 3

-- array of funcionts created with the do notation
-- equals array1 but for the feeded val1
array2 :: Int -> Maybe Int
array2 n = do
       v <- divideBy 2  n
       v <- multiply 3 v
       v <- divideBy 4 v
       v <- multiply 3 v
       return v

-- array of functions, 
-- the first >>= performs 160 / 0, returning Nothing
-- the second >>= has to perform Nothing >>= multiply 3 ....
-- and simply returns Nothing without calling multiply 3 ....
array3 = val1 >>= divideBy 0  >>= multiply 3 >>= divideBy  4 >>= multiply 3

main = do
     print array1
     print (array2 160)
     print array3

모나드가 헬퍼 연산을 갖는 함수의 배열임을 보여주기 위해, 실제 함수 배열을 사용하여 위의 예와 동등한 것을 고려하십시오.

type MyMonad = [Int -> Maybe Int] -- my monad as a real array of functions

myArray1 = [divideBy 2, multiply 3, divideBy 4, multiply 3]

-- function for the machinery of executing each function i with the result provided by function i-1
runMyMonad :: Maybe Int -> MyMonad -> Maybe Int
runMyMonad val [] = val
runMyMonad Nothing _ = Nothing
runMyMonad (Just val) (f:fs) = runMyMonad (f val) fs

그리고 그것은 다음과 같이 사용될 것입니다 :

print (runMyMonad (Just 160) myArray1)

1
깔끔한! 바인딩은 : 컨텍스트 입력에 순서대로 컨텍스트 기능의 배열, 평가하는 단지 방법입니다 그래서
무사 알 - hassy

>>=은 운영자입니다
user2418306

1
"함수 배열"의 비유는 그다지 명확하지 않다고 생각합니다. \x -> x >>= k >>= l >>= m함수의 배열 이라면 h . g . f모나드를 전혀 포함하지 않는 함수 입니다.
duplode dec

우리는 말할 수 펑은 , 모나드 실용적 또는 일반 여부에 대해있는 "더합니다 응용 프로그램" . 'applicative'는 체인을 추가하고 'monad'는 종속성을 추가합니다 (즉, 이전 계산 단계의 결과에 따라 다음 계산 단계 작성).
Will Ness

3

OO 용어로, 모나드는 유창한 컨테이너입니다.

최소 요구 사항은 class <A> Something생성자 Something(A a)와 최소한 하나의 메소드 를 지원 하는 정의입니다.Something<B> flatMap(Function<A, Something<B>>)

아마도 모나드 클래스 Something<B> work()에 클래스의 규칙을 유지하는 서명이있는 메소드가 있는지 계산합니다 . 컴파일러는 컴파일 타임에 flatMap에서 베이킹합니다.

모나드는 왜 유용한가요? 의미를 보존하는 체인 가능 작업을 허용하는 컨테이너이기 때문입니다. 예를 들어, Optional<?>대 isPresent의 의미 보존 Optional<String>, Optional<Integer>, Optional<MyClass>

대략적인 예로서

Something<Integer> i = new Something("a")
  .flatMap(doOneThing)
  .flatMap(doAnother)
  .flatMap(toInt)

문자열로 시작하여 정수로 끝납니다. 정말 멋진.

오픈 오피스에서는 약간 손을 wav 수 있지만, 다른 서브 클래스를 리턴하는 무언가에 대한 메소드는 원래 유형의 컨테이너를 리턴하는 컨테이너 함수의 기준을 충족합니다.

이것이 의미를 보존하는 방법입니다. 즉 컨테이너의 의미와 작업이 변경되지 않고 컨테이너 내부의 객체를 감싸고 향상시킵니다.


2

일반적인 사용법의 모나드는 절차 적 프로그래밍의 예외 처리 메커니즘과 기능적으로 동일합니다.

현대 절차 언어에서는 예외 처리기를 일련의 문 주위에 배치하면 예외가 발생할 수 있습니다. 명령문 중 하나에서 예외가 발생하면 명령문 순서의 정상적인 실행이 중지되고 예외 핸들러로 전송됩니다.

그러나 함수형 프로그래밍 언어는 철학적으로 "goto"와 같은 예외 처리 기능을 피합니다. 함수형 프로그래밍 관점은 함수에 프로그램 흐름을 방해하는 예외와 같은 "부작용"이 없어야한다는 것입니다.

실제로, I / O로 인해 실제로는 부작용을 배제 할 수 없습니다. 함수형 프로그래밍의 Monad는 일련의 체인 함수 호출 (예기치 않은 결과가 발생할 수 있음)을 취하고 예기치 않은 결과를 나머지 함수 호출을 통해 안전하게 흐를 수있는 캡슐화 된 데이터로 변환하여이를 처리하는 데 사용됩니다.

제어 흐름은 유지되지만 예기치 않은 이벤트는 안전하게 캡슐화되고 처리됩니다.


2

Marvel의 사례 연구와 함께 간단한 Monads 설명이 여기 있습니다 .

모나드는 효과적인 종속 함수를 시퀀싱하는 데 사용되는 추상화입니다. 여기서 유효하다는 것은 F [A] 형식의 형식 (예 : Option [A])을 반환하는 것을 의미합니다. 여기서 Option은 F (유형 생성자)입니다. 이것을 간단한 2 단계로 봅시다

  1. 아래 기능 구성은 전 이적입니다. 따라서 A에서 CI로 이동하려면 A => B 및 B => C를 작성할 수 있습니다.
 A => C   =   A => B  andThen  B => C

여기에 이미지 설명을 입력하십시오

  1. 그러나 함수가 Option [A], 즉 A => F [B]와 같은 효과 유형을 반환하면 컴포지션이 B로 이동하는 것처럼 작동하지 않지만 A => B가 필요하지만 A => F [B]가 있습니다.
    여기에 이미지 설명을 입력하십시오

    F [A]를 반환하는 이러한 함수를 통합하는 방법을 알고있는 특수 연산자 "바인드"가 필요합니다.

 A => F[C]   =   A => F[B]  bind  B => F[C]

"바인딩" 기능은 특정에 대해 정의 된 F .

도있다 "복귀" 형의, A는 => F [A] 어떤 용 특정 정의, F 도. Monad가 되려면 F 에는이 두 함수가 정의되어 있어야합니다.

따라서 순수한 함수 A => B 에서 효과적인 함수 A => F [B] 를 구성 할 수 있습니다 .

 A => F[B]   =   A => B  andThen  return

그러나 주어진 F 는 사용자가 자신을 ( 순수한 언어로) 스스로 정의 할 수없는 고유 한 불투명 한 "내장"특수 기능을 정의 할 수도 있습니다.

  • "random"( 범위 => 랜덤 [Int] )
  • "print"( 문자열 => IO [()] )
  • 「시도 ・ 캐치」등

2

이론적으로 완벽하지 않을 수도있는 Monads에 대한 이해를 공유하고 있습니다. 모나드는 문맥 전파 에 관한 것 입니다. Monad는 일부 데이터 (또는 데이터 유형)에 대한 컨텍스트를 정의한 다음 처리 파이프 라인에서 해당 컨텍스트가 데이터와 함께 어떻게 전달되는지 정의합니다. 컨텍스트 전파 정의는 주로 동일한 유형의 여러 컨텍스트를 병합하는 방법을 정의하는 것입니다. Monad를 사용한다는 것은 이러한 컨텍스트가 실수로 데이터에서 제거되지 않도록하는 것을 의미합니다. 반면에, 문맥이없는 다른 데이터는 새로운 문맥이나 기존의 문맥으로 가져올 수 있습니다. 그런 다음이 간단한 개념을 사용하여 프로그램의 컴파일 시간 정확성을 보장 할 수 있습니다.



1

"Monad 란 무엇입니까?"에 대한답변 을 참조하십시오.

동기 부여 예제로 시작하여 예제를 통해 작업하고 모나드의 예제를 도출하며 공식적으로 "monad"를 정의합니다.

함수형 프로그래밍에 대한 지식이 없다고 가정하고 function(argument) := expression가능한 가장 간단한 표현식을 가진 구문과 함께 의사 코드를 사용 합니다.

이 C ++ 프로그램은 의사 코드 모나드의 구현입니다. (참조 : M형식 생성자, feed"바인드"작업 및 wrap"반환"작업입니다.)

#include <iostream>
#include <string>

template <class A> class M
{
public:
    A val;
    std::string messages;
};

template <class A, class B>
M<B> feed(M<B> (*f)(A), M<A> x)
{
    M<B> m = f(x.val);
    m.messages = x.messages + m.messages;
    return m;
}

template <class A>
M<A> wrap(A x)
{
    M<A> m;
    m.val = x;
    m.messages = "";
    return m;
}

class T {};
class U {};
class V {};

M<U> g(V x)
{
    M<U> m;
    m.messages = "called g.\n";
    return m;
}

M<T> f(U x)
{
    M<T> m;
    m.messages = "called f.\n";
    return m;
}

int main()
{
    V x;
    M<T> m = feed(f, feed(g, wrap(x)));
    std::cout << m.messages;
}

0

실용적인 관점에서 (많은 이전 답변과 관련 기사에서 말한 것을 요약하여) 모나드의 기본 "목적"(또는 유용성) 중 하나는 재귀 적 메소드 호출에 내재 된 종속성을 활용하는 것 같습니다. 일명 함수 구성 (즉, f1이 f2를 호출하면 f3을 호출 할 때, f3은 f1을 f3 전에 호출해야 함), 특히 게으른 평가 모델 (즉, 평범한 순서로 순차적 인 구성)의 맥락에서 순차적 인 구성을 자연스럽게 표현해야합니다. 예를 들어 C에서 "f3 (); f2 (); f1 ();"-f3, f2 및 f1이 실제로 아무것도 반환하지 않는 경우를 생각하면 트릭이 특히 분명합니다 [f1 (f2 (f3)과 같은 체인) 인공적이며 순전히 서열을 생성하기위한 것이다]).

이는 부작용이 관련 될 때, 즉 일부 상태가 변경 될 때 (f1, f2, f3에 부작용이없는 경우 평가 순서에 상관없이) 중요합니다. 이는 순수한 특성입니다. 예를 들어 계산을 병렬화 할 수있는 기능적 언어). 순수한 기능이 많을수록 좋습니다.

그 좁은 관점에서 모나드는 게으른 평가를 선호하는 언어 (코드의 표시에 의존하지 않는 순서에 따라 절대적으로 필요한 경우에만 평가)를 선호하는 언어의 구문 설탕으로 볼 수 있다고 생각합니다. 순차적 구성을 나타내는 다른 수단. 결과적으로 "불완전한"(즉, 부작용이있는) 코드 섹션은 자연스럽게, 필수적으로 제시 될 수 있지만 (부작용없이) 순수한 기능과 깨끗하게 분리됩니다. 게으른 평가.

그러나 여기서 경고 한 것처럼 이것은 한 가지 측면 일뿐 입니다.


0

제가 생각할 수있는 가장 간단한 설명은 모나드가 기능을 구성하는 방법입니다 (일명 Kleisli 구성). "embelished"함수는 a -> (b, smth)where ab형식 (think Int, Bool)이 서로 다르지만 반드시 smth"context"또는 "embelishment"는 아닌 시그니처를 갖습니다 .

이 유형의 함수는 "embelishment"와 동등한 a -> m b곳에 작성할 수도 있습니다 . 따라서 상황에 따라 값을 반환하는 함수입니다 (작업을 기록하는 함수 , 로깅 메시지가있는 위치 또는 입출력을 수행하고 결과를 IO 함수의 결과에 따라 결정하는 함수).msmthsmth

모나드는 구현자가 그러한 기능을 구성하는 방법을 알려주는 인터페이스 ( "typeclass")입니다. 구현자는 인터페이스를 구현하려는 (a -> m b) -> (b -> m c) -> (a -> m c)모든 유형 의 컴포지션 함수를 정의해야합니다 m(Kleisli 컴포지션).

그래서 우리는 우리가 튜플 유형이 있다고 가정하는 경우 (Int, String)에 계산의 결과를 나타내는 Int도 함께, 자신의 행동을 기록들 (_, String)은 "embelishment"인 - 액션의 로그 - 두 개의 기능을 increment :: Int -> (Int, String)하고 twoTimes :: Int -> (Int, String)우리는 함수를 가져올 incrementThenDouble :: Int -> (Int, String)구성은 로그를 고려한 두 기능 중 하나입니다.

주어진 예에서, 두 함수의 모나드 구현은 정수 값 2 incrementThenDouble 2( 와 같음)에 적용 되며 같거나 같은 중간 결과를 twoTimes (increment 2)반환 합니다.(6, " Adding 1. Doubling 3.")increment 2(3, " Adding 1.")twoTimes 3(6, " Doubling 3.")

이 Kleisli 구성 함수에서 일반적인 모나 딕 함수를 도출 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.