평범한 영어로 모나드? (FP 배경이없는 OOP 프로그래머 용)
OOP 프로그래머가 (기능적 프로그래밍 배경없이) 이해할 수 있다는 점에서 모나드는 무엇입니까?
어떤 문제가 해결되고 가장 많이 사용되는 장소는 무엇입니까?
편집하다:
내가 찾고 있던 이해의 종류를 명확히하기 위해 모나드가있는 FP 애플리케이션을 OOP 애플리케이션으로 변환했다고 가정 해 보겠습니다. 모나드의 책임을 OOP 앱으로 포팅하려면 어떻게해야합니까?
업데이트 :이 질문은 Monads 에서 읽을 수있는 매우 긴 블로그 시리즈의 주제였습니다 . 좋은 질문에 감사드립니다!
OOP 프로그래머가 (기능적 프로그래밍 배경없이) 이해할 수 있다는 점에서 모나드는 무엇입니까?
모나드는 인 유형의 "증폭" 특정 규칙을 따르는 및 제공된 특정 작업을 갖는다 .
첫째, "유형 증폭기"란 무엇입니까? 즉, 유형을 가져 와서 더 특별한 유형으로 바꿀 수있는 시스템을 의미합니다. 예를 들어 C #에서는 Nullable<T>
. 이것은 유형의 증폭기입니다. 예를 들어 유형을 취하고 해당 유형 int
에 새 기능을 추가 할 수 있습니다. 즉, 이전에는 불가능했을 때 null이 될 수 있습니다.
두 번째 예로서 IEnumerable<T>
. 유형의 증폭기입니다. 예를 들어 유형을 취하고 해당 유형 string
에 새로운 기능을 추가 할 수 있습니다. 즉, 이제 임의의 수의 단일 문자열에서 문자열 시퀀스를 만들 수 있습니다.
"특정 규칙"이란 무엇입니까? 간단히 말해서, 기본 유형의 함수가 기능 구성의 정상적인 규칙을 따르도록 증폭 된 유형에 대해 작동하는 합리적인 방법이 있습니다. 예를 들어 정수에 대한 함수가있는 경우 다음과 같이 말하십시오.
int M(int x) { return x + N(x * 2); }
그러면 해당 함수 Nullable<int>
가 모든 연산자와 호출이 이전과 "동일한 방식"으로 함께 작동하도록 할 수 있습니다.
(엄청나게 모호하고 부정확합니다. 기능적 구성에 대한 지식을 가정하지 않은 설명을 요청했습니다.)
"작업"이란 무엇입니까?
일반 유형에서 값을 가져와 동등한 모나드 값을 생성하는 "단위"연산 (혼동하기는 "반환"연산이라고도 함)이 있습니다. 이것은 본질적으로 증폭되지 않은 유형의 값을 가져 와서 증폭 된 유형의 값으로 바꾸는 방법을 제공합니다. OO 언어의 생성자로 구현할 수 있습니다.
모나드 값과 값을 변환 할 수있는 함수를 사용하여 새 모나드 값을 반환하는 "바인드"연산이 있습니다. Bind는 모나드의 의미를 정의하는 주요 작업입니다. 증폭되지 않은 유형에 대한 연산을 이전에 언급 한 기능적 구성의 규칙을 준수하는 증폭 된 유형에 대한 연산으로 변환 할 수 있습니다.
증폭되지 않은 유형을 증폭 된 유형에서 되 돌리는 방법이 종종 있습니다. 엄밀히 말하면이 작업은 모나드를 가질 필요가 없습니다. ( 코 모나드 를 갖고 싶다면 필요하지만 이 기사에서는 더 이상 고려하지 않을 것입니다.)
다시 한 번 Nullable<T>
예를 들어 보겠습니다. 생성자 int
를 사용하여 Nullable<int>
를 로 바꿀 수 있습니다 . C # 컴파일러는 대부분의 nullable "리프팅"을 처리하지만 그렇지 않은 경우 리프팅 변환은 간단합니다.
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
가 변환에서 어떻게 캡처 되는지 확인하십시오 . 이것을 일반화 할 수 있습니다.
원본과 같은 from int
to 의 함수가 있다고 가정합니다 . nullable 생성자를 통해 결과를 실행할 수 있기 때문에 를 취하고 a 를 반환 하는 함수로 쉽게 만들 수 있습니다 . 이제 다음과 같은 고차 방법이 있다고 가정합니다.int
M
int
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);
}
그것으로 무엇을 할 수 있는지 보십니까? 를 받아 처리하는 방법 int
과를 반환 int
하거나 취하는 int
반환은 Nullable<int>
이제 널 (NULL) 의미가 적용될 수 있습니다 .
또한 : 두 가지 방법이 있다고 가정합니다.
Nullable<int> X(int q) { ... }
Nullable<int> Y(int r) { ... }
작성하고 싶습니다.
Nullable<int> Z(int s) { return X(Y(s)); }
즉, 및 Z
의 구성입니다 . 그러나를 취하고 를 반환 하기 때문에 그렇게 할 수 없습니다 . 그러나 "바인딩"작업이 있으므로이 작업을 수행 할 수 있습니다.X
Y
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 모나드 규칙은 "널 러블을 생성하는 두 함수를 결합하고, 내부 함수가 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;
}
이제 우리는 "이 개별 정수 묶음을 정수 시퀀스로 증폭합니다.이 특정 정수를 문자열 묶음으로 변환하여 문자열 시퀀스로 증폭합니다. 이제 두 연산을 모두 합칩니다.이 정수 묶음을 정수 시퀀스로 증폭합니다. 모든 문자열 시퀀스. " 모나드는 증폭 을 구성 할 수 있습니다 .
어떤 문제가 해결되고 가장 많이 사용되는 장소는 무엇입니까?
그것은 오히려 "싱글턴 패턴이 어떤 문제를 해결 하는가?"라고 묻는 것과 같지만 한번 시도해 볼게요.
모나드는 일반적으로 다음과 같은 문제를 해결하는 데 사용됩니다.
- 이 유형에 대한 새 기능을 만들고이 유형의 이전 기능을 결합하여 새 기능을 사용해야합니다.
- 유형에 대한 일련의 작업을 캡처하고 해당 작업을 구성 가능한 개체로 표현하고 올바른 일련의 작업이 표시 될 때까지 더 크고 더 큰 구성을 구축 한 다음 그 결과를 얻기 시작해야합니다.
- 부작용을 싫어하는 언어로 부작용이있는 작업을 깔끔하게 표현해야합니다.
C #은 디자인에 모나드를 사용합니다. 이미 언급했듯이 nullable 패턴은 "maybe monad"와 매우 유사합니다. LINQ는 전적으로 모나드로 구축되었습니다. SelectMany
방법은 작업의 구성의 의미 작업을 수행하는 것이다. (Erik Meijer는 모든 LINQ 함수가 실제로 구현 될 수 있다는 점을 지적하는 것을 좋아합니다 SelectMany
. 그 밖의 모든 것은 단지 편의입니다.)
내가 찾고 있던 이해의 종류를 명확히하기 위해 모나드가있는 FP 애플리케이션을 OOP 애플리케이션으로 변환했다고 가정 해 보겠습니다. 모나드의 책임을 OOP 앱으로 이식하려면 어떻게해야합니까?
대부분의 OOP 언어에는 모나드 패턴 자체를 직접 표현할 수있는 풍부한 유형 시스템이 없습니다. 일반 유형보다 높은 유형을 지원하는 유형 시스템이 필요합니다. 그래서 그렇게하려고하지 않을 것입니다. 오히려 각 모나드를 나타내는 제네릭 유형을 구현하고 필요한 세 가지 작업을 나타내는 메서드를 구현합니다. 값을 증폭 된 값으로 변환, (아마도) 증폭 된 값을 값으로 변환, 증폭되지 않은 값의 함수를 다음으로 변환 증폭 된 값에 대한 함수.
시작하기 좋은 곳은 C #에서 LINQ를 구현 한 방법입니다. SelectMany
방법을 연구하십시오 . 시퀀스 모나드가 C #에서 어떻게 작동하는지 이해하는 열쇠입니다. 매우 간단한 방법이지만 매우 강력합니다!
권장되는 추가 자료 :
- C #의 모나드에 대한보다 심도 있고 이론적으로 건전한 설명을 원하면 주제에 대한 제 ( Eric Lippert 's) 동료 Wes Dyer의 기사를 적극 권장 합니다. 이 기사는 그들이 나를 위해 마침내 "클릭"했을 때 모나드를 설명 해준 것이다.
- 모나드가 필요한 이유에 대한 좋은 예시입니다 (예제에서 Haskell 사용) .
- 당신은 모나드를 발명 할 수있었습니다! (그리고 아마도 당신은 이미 가지고 있을지도 모릅니다.) by Dan Piponi
- 이전 기사를 JavaScript로 "번역"하는 것과 같습니다.
- James Coglan 이 읽은 모나드 소개의 일부를 Haskell에서 JavaScript로 번역
왜 모나드가 필요한가요?
- 함수 만 사용하여 프로그래밍하고 싶습니다 . ( "기능적 프로그래밍"은 결국 -FP).
그러면 첫 번째 큰 문제가 있습니다. 이것은 프로그램입니다 :
f(x) = 2 * x
g(x,y) = x / y
무엇을 먼저 실행할지 어떻게 말할 수 있습니까? 함수 만 사용하여 순서화 된 함수 시퀀스 (예 : 프로그램 )를 어떻게 구성 할 수 있습니까?
솔루션 : 함수 작성 . 먼저 원하는 경우
g
다음f
바로 쓰기f(g(x,y))
. 오케이, 그런데 ...더 많은 문제 : 일부 기능 이 실패 할 수 있습니다 (예 :
g(2,0)
0으로 나누기). FP에는 "예외" 가 없습니다 . 어떻게 해결합니까?솔루션 : 함수가 두 가지 종류의 것을 반환하도록 합시다 :
g : Real,Real -> Real
(두 개의 실수에서 실수로의 함수) 대신g : Real,Real -> Real | Nothing
(두 개의 실수에서 (실수 또는 아무것도)로 함수)를 허용합시다 .그러나 함수는 (더 간단 해지려면) 한 가지만 반환 해야 합니다.
솔루션 : 반환 할 새로운 유형의 데이터를 생성 해 보겠습니다. " boxing type "은 실제 데이터를 포함하거나 단순히 아무것도 아닙니다. 따라서 우리는
g : Real,Real -> Maybe Real
. 오케이, 그런데 ...이제 어떻게됩니까
f(g(x,y))
?f
을 (를) 사용할 준비가되지 않았습니다Maybe Real
. 또한g
.NET을 사용하기 위해 연결할 수있는 모든 함수를 변경하고 싶지는 않습니다Maybe Real
.솔루션 : "연결"/ "구성"/ "연결"기능을위한 특수 기능을 만들어 보겠습니다 . 이런 식으로, 우리는 뒤에서 한 함수의 출력을 다음 함수에 공급하도록 조정할 수 있습니다.
우리의 경우 :
g >>= f
(connect / composeg
tof
). 우리는 의 출력>>=
을 얻고g
, 검사하고, 만약 그것이Nothing
단지 호출f
하고 반환 하지 않는 경우를 원합니다Nothing
; 또는 반대로, 상자를 추출하고 그것으로Real
먹이f
십시오. (이 알고리즘은 유형 에>>=
대한 구현 일뿐입니다Maybe
).이 동일한 패턴을 사용하여 해결할 수있는 다른 많은 문제가 발생합니다. 1. "상자"를 사용하여 다른 의미 / 값을 코드화 / 저장하고
g
이러한 "상자 화 된 값"을 반환하는 기능을 갖습니다 . 2. 의 출력을 의 입력에g >>= f
연결하는 데 도움 이되는 작곡가 / 링커 가 있으므로 전혀 변경할 필요가 없습니다 .g
f
f
이 기술을 사용하여 해결할 수있는 주목할만한 문제는 다음과 같습니다.
일련의 함수 ( "프로그램")의 모든 함수가 공유 할 수있는 전역 상태 : solution
StateMonad
.우리는 동일한 입력에 대해 다른 출력을 생성하는 함수 인 "불순한 함수"를 좋아하지 않습니다 . 따라서 해당 함수를 표시하여 태그 / 박스 값인 모나드 를 반환하도록 합니다.
IO
총 행복 !!!!
모나드와 가장 가까운 OO 비유는 " 명령 패턴 " 이라고 말할 수 있습니다.
명령 패턴에서 명령 개체 에 일반 문이나 식을 래핑 합니다. 명령 개체 는 래핑 된 문을 실행 하는 실행 메서드를 노출합니다 . 따라서 문은 자유롭게 전달되고 실행될 수있는 일급 객체로 변환됩니다. 명령 개체를 연결하고 중첩하여 프로그램 개체를 만들 수 있도록 명령을 구성 할 수 있습니다.
명령은 별도의 개체 인 invoker에 의해 실행됩니다 . 일련의 일반 명령문을 실행하는 대신 명령 패턴을 사용하는 이점은 다른 호출자가 명령을 실행하는 방법에 다른 논리를 적용 할 수 있다는 것입니다.
명령 패턴을 사용하여 호스트 언어에서 지원하지 않는 언어 기능을 추가 (또는 제거) 할 수 있습니다. 예를 들어, 예외가없는 가상의 OO 언어에서 "try"및 "throw"메소드를 명령에 노출하여 예외 의미를 추가 할 수 있습니다. 명령이 throw를 호출하면 호출자는 마지막 "try"호출까지 명령 목록 (또는 트리)을 역 추적합니다. 반대로, 각 개별 명령에서 발생하는 모든 예외를 포착하고 다음 명령으로 전달되는 오류 코드로 변환 하여 언어에서 예외 의미 체계를 제거 할 수 있습니다 ( 예외가 나쁘다고 생각하는 경우 ).
트랜잭션, 비 결정적 실행 또는 연속과 같은 훨씬 더 멋진 실행 의미를 기본적으로 지원하지 않는 언어로 이와 같이 구현할 수 있습니다. 생각해 보면 꽤 강력한 패턴입니다.
이제 실제로는 명령 패턴이 이와 같은 일반적인 언어 기능으로 사용되지 않습니다. 각 문을 별도의 클래스로 바꾸는 오버 헤드로 인해 견딜 수없는 양의 상용구 코드가 생성됩니다. 그러나 원칙적으로 모나드가 fp에서 해결하는 데 사용되는 것과 동일한 문제를 해결하는 데 사용할 수 있습니다.
OOP 프로그래머가 (기능적 프로그래밍 배경없이) 이해할 수 있다는 점에서 모나드는 무엇입니까?
어떤 문제가 해결되고 가장 많이 사용되는 장소는 무엇이며 가장 많이 사용되는 장소는 무엇입니까?
OO 프로그래밍의 관점에서, 모나드는 두 가지 방법으로, 유형에 의해 파라미터 인터페이스 (또는 더 많은 가능성 믹스 인)이며, return
그리고 bind
설명한다 :
- 주입 된 값 유형의 모나 딕 값을 얻기 위해 값을 주입하는 방법;
- 모나드 값에 대해 비 모나드 값에서 모나드 값을 만드는 함수를 사용하는 방법.
이것이 해결하는 문제는 모든 인터페이스에서 기대할 수있는 동일한 유형의 문제입니다. 즉, "저는 서로 다른 작업을 수행하는 여러 클래스가 있지만 근본적인 유사성이있는 방식으로 서로 다른 작업을 수행하는 것 같습니다. 어떻게 클래스 자체가 '객체'클래스 자체보다 더 가까운 하위 유형이 아니더라도 이들 간의 유사성을 설명 할 수 있습니까? "
보다 구체적으로 Monad
"인터페이스"는 그 자체가 유형을 취하는 유형 과 유사 IEnumerator
하거나 IIterator
취한다는 점에서 유사 합니다. 그러나 주요 "포인트" Monad
는 메인 클래스의 정보 구조를 유지하거나 심지어 향상시키면서 새로운 "내부 유형"을 갖는 지점까지 내부 유형에 따라 작업을 연결할 수 있다는 것입니다.
최근 에 Christopher League (2010 년 7 월 12 일)의 " Monadologie-유형 불안에 대한 전문적인 도움 " 이라는 프레젠테이션이 있는데, 이는 연속 및 모나드 주제에 대해 매우 흥미 롭습니다. 이 (slideshare) 프레젠테이션과 함께 진행되는 비디오는 실제로 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 }
예를 들어 (Scala에서) :
Option
모나드입니다
정의 단위 [A] (x : A) : 옵션 [A] = Some (x) def flatMap [A, B] (m : 옵션 [A]) (f : A => 옵션 [B]) : 옵션 [B] = m 일치 { 케이스 없음 => 없음 케이스 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 구조를 활용하기 위해 구축 된 편리한 구문 때문에 Scala에서 큰 문제입니다.
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이 입력과 동일한 클로저 유형을 취하고 출력과 동일한 유형을 반환한다는 것입니다.
이것이 계산 스레드를 "바인딩"하는 것입니다. for-comprehension의 시퀀스의 모든 항목은이 동일한 유형 제약을 준수해야합니다.
다음과 같이 두 가지 작업을 수행하고 (실패 할 수 있음) 결과를 세 번째 작업에 전달하는 경우 :
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
}
yield 부분은 세 가지 기능이 모두있는 경우에만 실행됩니다 Some[X]
. any None
는로 직접 반환됩니다 confirm
.
그래서:
모나드는 Functional Programing 내에서 정렬 된 계산을 허용하여 DSL과 같은 멋진 구조화 된 형태로 동작의 순서를 모델링 할 수 있습니다.
그리고 가장 큰 힘은 애플리케이션 내에서 확장 가능한 추상화로 다양한 목적을 제공하는 모나드를 구성하는 능력과 함께 제공됩니다.
모나드에 의한 액션의 시퀀싱과 스레딩은 클로저의 마법을 통해 변환을 수행하는 언어 컴파일러에 의해 수행됩니다.
그건 그렇고, Monad는 FP에서 사용되는 계산 모델이 아닙니다.
범주 이론은 많은 계산 모델을 제안합니다. 그들 중
- 계산의 Arrow 모델
- 모나드 계산 모델
- 계산의 적용 모델
빠른 독자를 존중하기 위해 먼저 정확한 정의로 시작하여보다 빠른 "일반 영어"설명을 계속 한 다음 예제로 이동합니다.
모나드 (컴퓨터 과학)는지도가 공식적이다 :
X
주어진 프로그래밍 언어의 모든 유형 을 새로운 유형T(X)
( "T
에 값이있는 계산 유형"이라고 함)으로 보냅니다X
.형식
f:X->T(Y)
과 기능의 두 가지 기능을 구성하는 규칙을 갖추고g:Y->T(Z)
있습니다g∘f:X->T(Z)
.
pure_X:X->T(X)
그 값을 단순히 반환하는 순수한 계산에 값을 취하는 것으로 생각하기 위해 라는 주어진 단위 함수와 관련하여 명백한 의미에서 연관되고 단일 한 방식으로 .
그래서 간단한 즉, 모나드는 A는 모든 종류의에서 통과 규칙 X
다른 유형은T(X)
, 그리고 규칙은 두 가지 기능에서 통과 f:X->T(Y)
및 g:Y->T(Z)
새로운 기능 (사용자가 작성하고 싶다고하지만 수 없습니다)h:X->T(Z)
. 그러나 이것은 엄격한 수학적 의미 의 구성이 아닙니다 . 우리는 기본적으로 함수의 구성을 "굽힘"하거나 함수가 구성되는 방식을 재정의합니다.
또한 "명백한"수학적 공리를 충족시키기 위해 모나드의 구성 규칙이 필요합니다.
- 연관성 : 작곡
f
으로g
다음으로는h
(외부) 구성과 동일해야g
함께h
다음으로f
(안쪽으로). - Unital property : 양쪽
f
의 identity 함수를 사용하여 구성하면 양보해야합니다f
.
다시 말해서, 간단히 말해서 우리가 원하는대로 함수 구성을 재정의 할 수는 없습니다.
- 먼저 여러 함수를 연속으로 구성 할 수 있고
f(g(h(k(x)))
함수 쌍을 구성하는 순서를 지정하는 것에 대해 걱정할 필요가없는 연관성이 필요합니다 . 모나드 규칙은 그 공리없이 함수 쌍 을 구성하는 방법 만 규정 하므로 어떤 쌍이 먼저 구성되는지 등을 알아야합니다. (그 교환 법칙 속성 상이한 참고f
로 구성은g
동일한이었다g
로 이루어지는f
필요하지 않다). - 둘째, 우리는 정체성이 우리가 기대하는 방식으로 사소하게 구성된다는 단순한 속성이 필요합니다. 따라서 이러한 ID를 추출 할 수있을 때마다 함수를 안전하게 리팩터링 할 수 있습니다.
다시 요약하자면, 모나드는 유형 확장의 규칙이며 결합 성과 단일 속성이라는 두 가지 공리를 충족하는 함수를 구성합니다.
실용적인 측면에서 모나드가 언어, 컴파일러 또는 프레임 워크에 의해 구현되기를 원합니다. 따라서 실행이 어떻게 구현되는지 걱정하지 않고 함수의 논리를 작성하는 데 집중할 수 있습니다.
요컨대 그것은 본질적으로 그것입니다.
전문 수학자이기 때문에 및 h
의 "구성" 이라고 부르지 않는 것이 좋습니다. 수학적으로는 그렇지 않기 때문입니다. 그것을 "구성"이라고 부르는 것은 그것이 진정한 수학적 구성 이라고 잘못 가정 하는 것입니다. 및에 의해 고유하게 결정되지도 않습니다 . 대신, 그것은 우리 모나드의 새로운 함수 "구성 규칙"의 결과입니다. 후자가 존재하더라도 실제 수학적 구성과 완전히 다를 수 있습니다!f
g
h
f
g
모나드는 펑터 가 아닙니다 ! 펑터이 F
유형에서 갈 수있는 규칙입니다 X
입력 할 수 F(X)
및 유형 간의 기능 (morphism에) X
와 Y
사이에 기능 F(X)
과 F(Y)
(카테고리 이론에 morphisms에 개체 및 morphisms에 객체를 전송). 대신 모나드는 한 쌍 의 함수 f
와 g
새로운 함수를 보냅니다 h
.
덜 건조하게 만들기 위해 작은 섹션으로 주석을 달고있는 예를 들어 설명해 보겠습니다. 그러면 요점으로 바로 건너 뛸 수 있습니다.
Monad 예제로 예외 발생
두 개의 함수를 구성한다고 가정 해 보겠습니다.
f: x -> 1 / x
g: y -> 2 * y
그러나 f(0)
정의되지 않았으므로 예외 e
가 발생합니다. 그렇다면 구성 가치를 g(f(0))
어떻게 정의 할 수 있습니까? 물론 예외를 다시 던져라! 아마도 동일 e
합니다. 새로 업데이트 된 예외 일 수 e1
있습니다.
여기서 정확히 무슨 일이 발생합니까? 첫째, 새로운 예외 값 (다르거 나 같음)이 필요합니다. 당신이 그들을 호출 할 수 있습니다 nothing
또는 null
또는 무엇이든을하지만 본질은 동일하게 유지 - 그들은 그것이 안, 예를 들어, 새로운 값을해야 number
우리의 예에서 여기. 특정 언어로 구현할 수있는 null
방법과 혼동을 피하기 위해 전화를 걸지 않습니다 null
. 마찬가지로 나는 원칙적으로 해야 할 일인와 nothing
관련이 있기 때문에 피하는 것을 선호 하지만, 그 원칙은 실제적인 이유가 무엇이든간에 종종 구부러집니다.null
null
예외 란 정확히 무엇입니까?
이것은 숙련 된 프로그래머에게는 사소한 문제이지만 혼란의 웜을 없애기 위해 몇 마디를 생략하고 싶습니다.
예외는 잘못된 실행 결과가 발생한 방법에 대한 정보를 캡슐화하는 개체입니다.
여기에는 세부 정보를 버리고 단일 전역 값 (예 : NaN
또는 null
)을 반환 하거나 긴 로그 목록 또는 정확히 발생한 일을 생성하여 데이터베이스로 전송하고 분산 데이터 저장소 계층 전체에 복제하는 것까지 다양 합니다.)
이 두 가지 극단적 인 예외 사례의 중요한 차이점은 첫 번째 경우에는 부작용 이 없다는 것 입니다. 두 번째에는 있습니다. 이것은 (천 달러) 질문으로 이어집니다.
순수 함수에서 예외가 허용됩니까?
짧은 답변 : 예,하지만 부작용이 없을 때만 가능합니다.
더 긴 대답. 순수하게하려면 함수의 출력이 입력에 의해 고유하게 결정되어야합니다. 그래서 우리 는 예외라고 부르는 새로운 추상 값 을 f
보내서 우리의 함수 를 수정합니다 . 값 에 입력에 의해 고유하게 결정되지 않은 외부 정보가 포함되어 있지 않은지 확인합니다 . 다음은 부작용이없는 예외의 예입니다.0
e
e
x
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 | E
의 number
과 E
. 특히, 그것은 우리가 어떻게 구성하고 싶은지에 달려 E
있으며, 이름에 제안되거나 반영되지 않습니다 maybe number
.
기능성 구성이란?
이것은 수학적 연산 촬영 기능이다 f: X -> Y
및 g: Y -> Z
함수로서의 조성물을 구성하고 h: X -> Z
만족 h(x) = g(f(x))
. 이 정의의 문제는 결과 f(x)
가의 인수로 허용되지 않을 때 발생합니다 g
.
수학에서 이러한 함수는 추가 작업없이 구성 할 수 없습니다. 위의 f
및의 예에 대한 엄격한 수학적 해결책은 의 정의 집합에서 g
제거 0
하는 것입니다 f
. 새로운 정의 세트 (새로운 더 제한적인 유형의 x
)를 사용 f
하여 g
.
그러나 f
이와 같은 정의 집합을 제한하는 것은 프로그래밍에서 그리 실용적이지 않습니다 . 대신 예외를 사용할 수 있습니다.
또는 다른 방법으로, 인공 값은 같은 생성 NaN
, undefined
, null
, Infinity
평가할 그래서 등 1/0
으로 Infinity
과 1/-0
에 -Infinity
. 그런 다음 예외를 던지는 대신 새 값을 표현식에 다시 적용하십시오. 예측 가능한 결과를 얻거나 찾지 못할 수 있습니다.
1/0 // => Infinity
parseInt(Infinity) // => NaN
NaN < 0 // => false
false + 1 // => 1
그리고 우리는 계속 진행할 준비가 된 일반 번호로 돌아 왔습니다.)
JavaScript를 사용하면 위의 예에서와 같이 오류를 발생시키지 않고 어떤 비용 으로든 숫자 표현식을 계속 실행할 수 있습니다. 즉, 기능을 구성 할 수도 있습니다. 이것이 바로 모나드에 관한 것입니다.이 답변의 시작 부분에 정의 된 공리를 충족하는 함수를 구성하는 것이 규칙입니다.
그러나 숫자 오류를 처리하기위한 JavaScript의 구현에서 발생하는 함수 작성 규칙, 모나드입니까?
이 질문에 답하기 위해 필요한 것은 공리를 확인하는 것입니다 (여기에서 질문의 일부가 아닌 운동으로 남겨 두었습니다.).
모나드를 생성하기 위해 예외를 던질 수 있습니까?
실제로, 더 유용한 모나드는 f
일부 x
에 대해 예외가 발생 하면 g
. 또한 E
단 하나의 가능한 값으로 예외를 전역 적으로 고유 하게 만드십시오 ( 카테고리 이론의 최종 객체 ). 이제 두 개의 공리가 즉시 확인 가능하며 매우 유용한 모나드를 얻습니다. 결과는 아마도 모나드 로 잘 알려진 것 입니다.
모나드는 값을 캡슐화하는 데이터 유형이며 기본적으로 두 가지 작업을 적용 할 수 있습니다.
return x
캡슐화하는 모나드 유형의 값을 생성합니다.x
m >>= f
( "바인드 연산자"로 읽음)f
모나드의 값에 함수 를 적용합니다.m
그것이 바로 모나드입니다. 있다 몇 가지 더 교칙는 하지만, 기본적으로이 두 작업은 모나드를 정의합니다. 진짜 질문은 "모나드 가 무엇을 하는가?"입니다. 이것은 모나드에 따라 달라집니다.리스트는 모나드이고, Maybe는 모나드이고, IO 연산은 모나드입니다. 그것이 모나드라고 말할 때 의미하는 것은 모두 return
와 모나드 인터페이스를 가지고 있다는 것 >>=
입니다.
에서 위키 피 디아 :
함수형 프로그래밍에서 모나드는 계산을 나타내는 데 사용되는 일종의 추상 데이터 유형입니다 (도메인 모델의 데이터 대신). 모나드는 프로그래머가 작업을 함께 연결하여 파이프 라인을 구축 할 수 있도록합니다. 각 작업은 모나드에서 제공하는 추가 처리 규칙으로 장식됩니다. 기능적 스타일로 작성된 프로그램은 모나드를 사용하여 순차 연산을 포함하는 절차를 구성 하거나 1 [2] 임의의 제어 흐름을 정의 할 수 있습니다 (동시성, 연속성 또는 예외 처리와 같은).
공식적으로, 모나드는 모나드 함수 (즉, 모나드의 값을 인수로 사용하는 함수)의 올바른 구성을 허용하기 위해 여러 속성을 충족해야하는 두 개의 연산 (바인딩 및 반환)과 유형 생성자 M을 정의하여 구성됩니다. 반환 작업은 일반 유형에서 값을 가져와 M 유형의 모나드 컨테이너에 넣습니다. 바인드 작업은 컨테이너에서 원래 값을 추출하여 파이프 라인의 연결된 다음 함수로 전달하는 역 프로세스를 수행합니다.
프로그래머는 데이터 처리 파이프 라인을 정의하기 위해 모나 딕 함수를 작성합니다. 모나드는 파이프 라인의 특정 모나드 함수가 호출되는 순서를 결정하고 계산에 필요한 모든 비밀 작업을 관리하는 재사용 가능한 동작이기 때문에 프레임 워크 역할을합니다. [3] 파이프 라인에서 인터리브 된 바인드 및 리턴 연산자는 각 모나드 함수가 제어를 반환 한 후에 실행되며 모나드가 처리하는 특정 측면을 처리합니다.
나는 그것이 그것을 아주 잘 설명한다고 믿습니다.
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 및 가능한 값 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는 Promise 및 Tasks뿐만 아니라 Monadic으로 만들 수도 있습니다.
논평 :
모나드는 그렇게 복잡하지 않습니다. 이 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())
이런 종류의 제네릭 클래스는 수많은 것들에 대한 기본 모델로 유용합니다. 이것이 (범주 이론 전문 용어와 함께) 모나드가 이해하거나 설명하기가 너무 어려워 보이는 이유입니다. 그것들은 매우 추상적 인 것이고 일단 전문화되면 분명히 유용 해집니다.
예를 들어 모나 딕 컨테이너를 사용하여 예외를 모델링 할 수 있습니다. 각 컨테이너에는 작업의 결과 또는 발생한 오류가 포함됩니다. 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
}
모나드가 OO에서 "자연스러운"해석을 가지고 있는지 여부는 모나드에 따라 다릅니다. Java와 같은 언어에서는 아마도 모나드를 널 포인터를 확인하는 언어로 변환하여 실패한 (즉, Haskell에서 Nothing을 생성하는) 계산이 결과로 널 포인터를 방출하도록 할 수 있습니다. 상태 모나드를 변경 가능한 변수와 상태를 변경하는 메서드를 만들어 생성 된 언어로 변환 할 수 있습니다.
모나드는 endofunctor 범주의 모노 이드입니다.
문장이 모이는 정보는 매우 깊습니다. 그리고 명령형 언어로 모나드에서 작업합니다. 모나드는 "순차 화 된"도메인 특정 언어입니다. 모나드를 "명령형 프로그래밍"의 수학적 모델로 만드는 흥미로운 속성을 충족합니다. Haskell을 사용하면 다양한 방법으로 결합 할 수있는 작거나 큰 명령형 언어를 쉽게 정의 할 수 있습니다.
OO 프로그래머는 언어의 클래스 계층을 사용하여 컨텍스트에서 호출 할 수있는 함수 또는 프로 시저의 종류를 구성합니다. 모나드는 또한 다른 모나드가 임의의 방식으로 결합되어 모든 서브 모나드의 메서드를 스코프로 "임포트"할 수 있다는 점에서이 아이디어에 대한 추상화입니다.
구조적으로는 유형 서명을 사용하여 값을 계산하는 데 사용할 수있는 컨텍스트를 명시 적으로 표현합니다.
이 목적으로 모나드 변환기를 사용할 수 있으며 모든 "표준"모나드의 고품질 컬렉션이 있습니다.
- 목록 (목록을 도메인으로 처리하여 비 결정적 계산)
- 아마도 (실패 할 수 있지만보고가 중요하지 않은 계산)
- 오류 (실패 할 수 있고 예외 처리가 필요한 계산
- Reader (평범한 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 표기법"사용
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
더 중요한 것은 "레이어 스택"작업에서 모나드가 닫 혔음을 의미합니다. 이것이 모나드 변환기가 작동하는 방식입니다. 그들은 다음과 같은 유형에 대해 "join-like"메소드를 제공하여 모나드를 결합합니다.
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
(MaybeT m)의 액션을 m의 액션으로 변환하여 효과적으로 레이어를 축소 할 수 있습니다. 이 경우 runMaybeT :: MaybeT ma-> m (Maybe a)는 조인과 유사한 방법입니다. (MaybeT m)은 모나드이고 MaybeT :: m (아마도 a)-> MaybeT ma는 사실상 m에서 새로운 유형의 모나드 액션을위한 생성자입니다.
펑터에 대한 자유 모나드는 f를 쌓아서 생성 된 모나드입니다. f에 대한 모든 생성자 시퀀스는 자유 모나드의 요소 (또는 더 정확하게는 생성자 시퀀스 트리와 같은 모양을 가진 것입니다) 에프). 무료 모나드는 최소한의 보일러 플레이트로 유연한 모나드를 구성하는 데 유용한 기술입니다. 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))
모나 디즘은 모든 모나드 계산이 최소한 사소하게 "실행"되어야하기 때문에 가장 명확한 형태로 추상화 된 "통역사"또는 "명령"패턴이라고 부르는 기본 아키텍처입니다. (런타임 시스템은 우리를 위해 IO 모나드를 실행하며 모든 Haskell 프로그램의 진입 점입니다. IO는 IO 작업을 순서대로 실행하여 나머지 계산을 "구동"합니다.)
조인의 유형은 모나드가 엔도 펑터 범주에서 모노 이드라는 진술을 얻는 곳이기도합니다. 조인은 일반적으로 유형 때문에 이론적 목적에 더 중요합니다. 그러나 유형을 이해한다는 것은 모나드를 이해하는 것을 의미합니다. 조인 및 모나드 변환기의 조인 유사 유형은 함수 구성의 의미에서 효과적으로 endofunctor의 구성입니다. Haskell과 유사한 의사 언어로 표현하려면
Foo :: m (ma) <-> (m. m) a
모나드는 함수의 배열입니다.
(Pst : 함수 배열은 단지 계산 일뿐입니다).
실제로 실제 배열 (하나의 셀형 배열에있는 하나의 함수) 대신 다른 함수 >> =로 연결된 함수가 있습니다. >> =는 함수 i의 결과를 함수 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)
일반적인 사용에서 모나드는 절차 적 프로그래밍의 예외 처리 메커니즘과 기능적으로 동일합니다.
현대의 절차 적 언어에서는 일련의 명령문 주위에 예외 처리기를 배치합니다.이 중 어느 것이 든 예외가 발생할 수 있습니다. 명령문에서 예외가 발생하면 명령문 시퀀스의 정상적인 실행이 중지되고 예외 처리기로 전송됩니다.
그러나 함수형 프로그래밍 언어는 철학적으로 "goto"와 같은 특성 때문에 예외 처리 기능을 피합니다. 함수형 프로그래밍 관점은 함수가 프로그램 흐름을 방해하는 예외와 같은 "부작용"을 가져서는 안된다는 것입니다.
실제로는 주로 I / O로 인해 실제 세계에서 부작용을 배제 할 수 없습니다. 함수형 프로그래밍의 모나드는 일련의 연결된 함수 호출 (예기치 않은 결과를 생성 할 수 있음)을 취하고 예상치 못한 결과를 나머지 함수 호출을 통해 안전하게 흐를 수있는 캡슐화 된 데이터로 변환하여이를 처리하는 데 사용됩니다.
제어 흐름은 보존되지만 예상치 못한 이벤트는 안전하게 캡슐화되고 처리됩니다.
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)
문자열로 시작하고 정수로 끝납니다. 정말 멋진.
OO에서는 약간의 손 흔들기가 필요할 수 있지만 Something의 다른 하위 클래스를 반환하는 Something의 모든 메서드는 원래 유형의 컨테이너를 반환하는 컨테이너 함수의 기준을 충족합니다.
이것이 의미론을 보존하는 방법입니다. 즉, 컨테이너의 의미와 작업은 변경되지 않고 컨테이너 내부의 객체를 감싸고 향상시킵니다.
Marvel 사례 연구와 함께 간단한 Monads 설명이 여기에 있습니다 .
모나드는 효과적인 종속 함수를 시퀀스하는 데 사용되는 추상화입니다. 여기서 유효하다는 것은 F [A] 형식의 형식을 반환한다는 것을 의미합니다. 예를 들어 Option [A] 여기서 Option은 F 형식 생성자라고합니다. 간단한 2 단계로 살펴 보겠습니다.
- 아래 기능 구성은 전 이적입니다. 따라서 A에서 CI로 이동하려면 A => B 및 B => C를 구성 할 수 있습니다.
A => C = A => B andThen B => C
그러나 함수가 Option [A], 즉 A => F [B]와 같은 효과 유형을 반환하는 경우 B로 이동하려면 구성이 작동하지 않습니다. A => B가 필요하지만 A => F [B]가 있습니다.
F [A]를 반환하는 이러한 함수를 융합하는 방법을 알고있는 특수 연산자 "bind"가 필요합니다.
A => F[C] = A => F[B] bind B => F[C]
"바인딩" 기능은 특정에 대해 정의 된 F .
또한 특정 F에 대해 정의 된 A에 대한 유형 A => F [A] 의 "return" 도 있습니다. 모나드가 되려면 F 에이 두 가지 함수가 정의되어 있어야합니다.
따라서 순수한 함수 A => B 에서 효과적인 함수 A => F [B] 를 구성 할 수 있습니다 .
A => F[B] = A => B andThen return
하지만 주어진 F는 또한 사용자의 (a에서 스스로가 정의 할 수 없습니다 "내장"같은 유형의 특수 기능 자체 불투명를 정의 할 수 있습니다 순수 같은 언어)
- "random"( 범위 => Random [Int] )
- "인쇄"( 문자열 => IO [()] )
- "시도 ... 캐치"등
Powershell을 사용해 본 적이 있다면 Eric이 설명한 패턴이 친숙하게 들릴 것입니다. Powershell cmdlet 은 모나드입니다. 기능적 구성은 파이프 라인으로 표시됩니다 .
Jeffrey Snover와 Erik Meijer의 인터뷰는 더 자세히 설명되어 있습니다.
"모나드는 무엇입니까?"에 대한 내 대답 을 참조하십시오.
동기 부여 예제로 시작하여 예제를 통해 작업하고 모나드의 예제를 도출하고 공식적으로 "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;
}
실용적인 관점에서 (많은 이전 답변 및 관련 기사에서 말한 내용을 요약) 모나드의 기본 "목적"(또는 유용성) 중 하나는 재귀 메서드 호출에 내재 된 종속성을 활용하는 것 같습니다. 일명 함수 구성 (즉, f1이 f2가 f3을 호출 할 때 f3은 f1 이전에 f2보다 먼저 평가되어야 함), 특히 지연 평가 모델의 맥락에서 (즉, 일반 시퀀스로서의 순차적 구성 , 예를 들어 "f3 (); f2 (); f1 ();"in C-f3, f2 및 f1이 실제로 아무것도 반환하지 않는 경우를 생각하면 트릭이 특히 분명합니다 [그들의 연결은 f1 (f2 (f3)). 순전히 시퀀스를 생성하기위한 의도입니다]).
이것은 특히 부작용이 수반되는 경우, 즉 일부 상태가 변경 될 때 관련이 있습니다 (f1, f2, f3에 부작용이없는 경우 어떤 순서로 평가되는지는 중요하지 않습니다. 이것은 pure의 훌륭한 속성입니다). 예를 들어 이러한 계산을 병렬화 할 수있는 기능적 언어). 더 순수한 기능 일수록 좋습니다.
좁은 관점에서 보면 모나드는 게으른 평가를 선호하고 (코드 표현에 의존하지 않는 순서에 따라 절대적으로 필요한 경우에만 평가하는) 언어에 대한 구문 설탕으로 볼 수 있다고 생각합니다. 순차적 구성을 나타내는 다른 수단. 최종 결과는 "불순한"(즉, 부작용이있는) 코드 섹션이 자연스럽고 명령 적 방식으로 표시 될 수 있지만 순수 함수 (부작용없이)와는 명확하게 분리 될 수 있습니다. 게으른 평가.
그러나 이것은 여기 에서 경고 한 것처럼 한 가지 측면 일뿐 입니다.
이론적으로 완벽하지 않을 수도있는 모나드에 대한 나의 이해를 공유하고 있습니다. 모나드는 컨텍스트 전파 에 관한 것 입니다. Monad는 일부 데이터에 대한 컨텍스트를 정의한 다음 컨텍스트가 처리 파이프 라인 전체에서 데이터와 함께 전달되는 방식을 정의합니다. 컨텍스트 전파를 정의하는 것은 주로 여러 컨텍스트를 병합하는 방법을 정의하는 것입니다. 또한 컨텍스트가 데이터에서 실수로 제거되지 않도록합니다. 이 간단한 개념은 프로그램의 컴파일 시간 정확성을 보장하는 데 사용할 수 있습니다.
'code' 카테고리의 다른 글
Twitter Bootstrap에서 navbar 색상 변경 (0) | 2020.09.30 |
---|---|
맹인이라면 어떻게 프로그래밍 할 수 있습니까? (0) | 2020.09.30 |
Python 2.X에서 범위와 xrange 함수의 차이점은 무엇입니까? (0) | 2020.09.30 |
IEnumerable의 foreach에 해당하는 LINQ (0) | 2020.09.30 |
Git에서 로컬 분기를 원격 분기로 완전히 바꾸는 방법은 무엇입니까? (0) | 2020.09.30 |