OOP 프로그래머가 (기능적 프로그래밍 배경없이) 이해한다는 관점에서 모나드는 무엇입니까?
어떤 문제를 해결하고 가장 많이 사용되는 장소는 무엇입니까?
편집하다:
내가 찾고있는 이해를 명확히하기 위해 모나드가있는 FP 응용 프로그램을 OOP 응용 프로그램으로 변환한다고 가정 해 봅시다. 모나드의 책임을 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 언어의 생성자로 구현 될 수 있습니다.
모나드 값과 값을 변환 할 수있는 함수를 가져 와서 새 모나드 값을 반환하는 "바인드"연산이 있습니다. 바인드는 모나드의 의미를 정의하는 핵심 작업입니다. 이를 통해 증폭되지 않은 유형의 연산을 증폭 된 유형의 연산으로 변환 할 수 있으며, 이는 앞에서 언급 한 기능 구성 규칙을 따릅니다.
증폭되지 않은 유형을 증폭 된 유형에서 다시 가져 오는 방법이 종종 있습니다. 엄밀히 말하면이 작업에는 모나드가 필요하지 않습니다. ( 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
가 변환에서 어떻게 캡처 되는지 확인합니다 . 우리는 이것을 일반화 할 수 있습니다.
당신이에서 함수가 있다고 가정 int
에 int
우리의 원래처럼 M
. nullable 생성자를 통해 결과를 실행할 수 있기 때문에 int
and를 반환 하는 함수로 쉽게 만들 수 있습니다 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
, int
a Nullable<int>
를 반환하고 a 를 반환하는 모든 메서드 는 이제 nullable 의미를 적용 할 수 있습니다.
또한 : 두 가지 방법이 있다고 가정하십시오.
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 모나드 규칙은 "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 #에서 어떻게 작동하는지 이해하는 것이 중요합니다. 매우 간단한 방법이지만 매우 강력합니다!
제안, 추가 읽기 :
그런 다음 첫 번째 큰 문제가 있습니다. 이것은 프로그램입니다 :
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
는를 사용할 수있는 모든 함수를 변경하고 싶지 않습니다 Maybe Real
.
솔루션 : "connect"/ "compose"/ "link"기능을위한 특수 기능을 갖도록 합시다 . 이런 식으로, 우리는 뒤에서 한 함수의 출력을 다음 함수를 제공하도록 조정할 수 있습니다.
우리의 경우 : g >>= f
(connect / compose g
to f
). 우리는 출력 >>=
을 얻고 g
, 검사하고, Nothing
단지 호출 f
하고 반환 하지 않는 경우를 원합니다 Nothing
. 또는 반대로, 박스를 추출하여 Real
공급 f
하십시오. (이 알고리즘은 유형 에 >>=
대한 구현 일뿐입니다 Maybe
).
동일한 패턴을 사용하여 해결할 수있는 다른 많은 문제가 발생합니다. 1. "상자"를 사용하여 다른 의미 / 값을 코드화 / 저장하고 g
이러한 "상자 값"을 반환하는 것과 같은 기능을 갖습니다 . 2. 의 출력을 의 입력에 g >>= f
연결하는 데 도움을주는 작곡가 / 링커 가 있으므로 전혀 변경할 필요가 없습니다 .g
f
f
이 기술을 사용하여 해결할 수있는 놀라운 문제는 다음과 같습니다.
일련의 기능 ( "프로그램")에서 모든 기능이 공유 할 수있는 전역 상태를 갖는 경우 : solution StateMonad
.
우리는 "불완전한 기능"을 좋아하지 않습니다 : 동일한 입력에 대해 다른 출력을 생성하는 기능 . 따라서 해당 함수를 표시하여 태그 / 박스 값을 반환합니다 : monad.IO
총 행복 !!!!
State
와 IO
주어지고 그들 중 누구도뿐만 아니라 "모나드"의 정확한 의미와 함께,
모나드와 가장 유사한 OO 비유는 " 명령 패턴 "이라고합니다.
명령 패턴에서는 일반 명령문 또는 표현식을 명령 오브젝트로 랩핑 합니다. 명령 객체 는 래핑 된 명령문을 실행 하는 실행 메소드를 노출합니다 . 따라서 문은 일급 객체로 바뀌어 마음대로 전달하고 실행할 수 있습니다. 명령 개체를 연결하고 중첩하여 프로그램 개체를 만들 수 있도록 명령을 구성 할 수 있습니다.
명령은 별도의 객체 invoker에 의해 실행됩니다 . 일련의 일반 문을 실행하는 대신 명령 패턴을 사용하면 다른 호출자가 명령 실행 방법에 다른 논리를 적용 할 수 있다는 이점이 있습니다.
명령 패턴을 사용하여 호스트 언어에서 지원하지 않는 언어 기능을 추가하거나 제거 할 수 있습니다. 예를 들어, 예외가없는 가상의 OO 언어에서 "try"및 "throw"메소드를 명령에 노출하여 예외 의미를 추가 할 수 있습니다. 명령 호출이 throw되면 호출자는 마지막 "시도"호출까지 명령 목록 (또는 트리)을 역 추적합니다. 반대로, 각 개별 명령에 의해 발생 된 모든 예외를 포착 한 다음 오류 코드로 변환하여 다음 명령으로 전달 함으로써 언어에서 예외 시맨틱을 제거 할 수 있습니다 ( 예외가 나쁜 것으로 판단되는 경우 ).
트랜잭션, 비 결정적 실행 또는 연속과 같은 훨씬 더 멋진 실행 의미는 기본적으로 지원하지 않는 언어로 이와 같이 구현 될 수 있습니다. 당신이 그것에 대해 생각하면 그것은 매우 강력한 패턴입니다.
이제 실제로는 명령 패턴이 이와 같은 일반적인 언어 기능으로 사용되지 않습니다. 각 문장을 별도의 클래스로 바꾸는 오버 헤드로 인해 참을 수없는 상용구 코드가 생길 수 있습니다. 그러나 원칙적으로 fp에서 모나드를 사용하는 것과 동일한 문제를 해결하는 데 사용할 수 있습니다.
OOP 프로그래머가 (기능적 프로그래밍 배경없이) 이해한다는 관점에서 모나드는 무엇입니까?
어떤 문제가 해결되고 가장 많이 사용되는 장소는 무엇입니까? 가장 많이 사용되는 장소는 무엇입니까?
OO 프로그래밍의 관점에서 모나드는 두 가지 방법으로 유형별로 매개 변수가 지정된 인터페이스 (또는 믹스 인 일 가능성 있음) return
이며 다음 bind
을 설명합니다.
이 문제가 해결하는 문제는 인터페이스에서 기대할 수있는 것과 같은 유형의 문제입니다. 즉, "다른 일을하는 여러 가지 클래스가 있지만 그와 비슷한 방식으로 다른 일을하는 것 같습니다." 클래스 자체가 실제로 'Object'클래스 자체보다 가까운 하위 유형이 아닌 경우에도 이들 간의 유사성을 설명 할 수 있습니까? "
보다 구체적으로, Monad
"인터페이스"는 그 자체가 타입을 취하는 타입을 취한다는 점 과 유사 IEnumerator
하거나 유사하다 IIterator
. Monad
그럼에도 불구하고 주요 "포인트" 는 메인 클래스의 정보 구조를 유지하거나 강화하면서 새로운 "내부 유형"을 갖는 점까지도 내부 유형을 기반으로 작업을 연결할 수 있습니다.
return
모나드 인스턴스를 인수로 사용하지 않기 때문에 실제로 모나드에 대한 메소드가 아닙니다. (즉, 이것 / 자체는 없다)
크리스토퍼 리그 (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에서 사용되는 계산 모델이 아닙니다.
카테고리 이론은 많은 계산 모델을 제안합니다. 그들 중
- 계산의 화살표 모델
- 모나드 계산 모델
- 적용 계산 모델
빠른 독자를 존중하기 위해 먼저 정확한 정의부터 시작하여보다 "일반적인 영어"설명을 계속 한 다음 예제로 이동하십시오.
모나드 (컴퓨터 과학)는지도가 공식적이다 :
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
필수는 아닙니다.)간단히 말해서, 모나드는 타입 확장의 규칙이며 연관성과 단위 속성이라는 두 가지 공리를 만족시키는 함수를 구성합니다.
실제로는 함수 구성을 담당하는 언어, 컴파일러 또는 프레임 워크로 모나드를 구현하기를 원합니다. 따라서 함수가 어떻게 구현되는지 걱정하지 않고 함수의 논리를 작성하는 데 집중할 수 있습니다.
그것은 본질적으로 간단합니다.
전문 수학자이기 때문에 와 h
의 "구성" 이라고 부르지 않는 것이 좋습니다. 수학적으로는 그렇지 않기 때문입니다. 그것을 "조성"이라고 부르는 것은 그것이 실제 수학적 구성 이라고 잘못 추정합니다 . 심지어 고유에 의해 결정되지 않습니다 및 . 대신 모나드의 새로운 "구성 규칙"의 결과입니다. 후자가 존재하더라도 실제 수학적 구성과는 완전히 다를 수 있습니다!f
g
h
f
g
덜 건조하게 만들기 위해 작은 섹션으로 주석을 달고 있다는 것을 예로 들어 설명해 보겠습니다. 그래서 바로 건너 뛸 수 있습니다.
두 가지 기능을 작성한다고 가정 해보십시오.
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
, 그 구성도 any로 구성되도록 규정하는 규칙이 될 것 g
입니다. 또한 E
하나의 가능한 값 ( 범주 이론의 터미널 객체) 으로 예외를 전역 적으로 고유 하게 만듭니다 . 이제 두 가지 공리를 즉시 확인할 수 있으며 매우 유용한 모나드를 얻습니다. 그리고 결과는 아마도 모나드 로 잘 알려진 것 입니다.
모나드는 값을 캡슐화하고 본질적으로 두 가지 연산을 적용 할 수있는 데이터 유형입니다.
return x
캡슐화하는 모나드 타입의 값을 생성 x
m >>= f
( "바인드 연산자"로 읽음) f
모나드의 값에 함수 를 적용m
그것이 모나드입니다. 있다 몇 가지 더 교칙는 하지만, 기본적으로이 두 작업은 모나드를 정의합니다. 실제 질문은 "모나드의 기능 은 무엇입니까 ?"이며 모나드에 따라 다릅니다. 목록은 모나드, 아마도 모나드, IO 작업은 모나드입니다. 우리가 그 것들을 모나드라고 말할 때, 모나드 인터페이스는 return
및로 구성 >>=
됩니다.
bind
는 각 모나드 유형에 대해 정의해야하는 기능에 따라 달라 집니까? 컴포지션에 대한 단일 정의가 있기 때문에 바인드와 컴포지션을 혼동하지 않는 좋은 이유입니다. 바인드 함수에 대한 단일 정의 만있을 수는 없지만 올바르게 이해하면 모나드 유형 당 하나가 있습니다.
에서 위키 피 디아 :
함수형 프로그래밍에서 모나드는 도메인 모델의 데이터 대신 계산을 나타내는 데 사용되는 일종의 추상 데이터 유형입니다. Monads는 프로그래머가 액션을 서로 연결하여 파이프 라인을 구축 할 수있게하는데, 각 액션은 모나드가 제공하는 추가 처리 규칙으로 꾸며져 있습니다. 기능적 스타일로 작성된 프로그램은 모나드를 사용하여 시퀀싱 된 작업을 포함하는 프로 시저를 구조화 하거나 , 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 및 가능한 값에 적용되는 경우
왼쪽 정체성 :
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
}
모나드가 OO에서 "자연적인"해석을 가지고 있는지 여부는 모나드에 달려 있습니다. Java와 같은 언어에서는 어쩌면 모나드를 널 포인터 검사 언어로 변환하여 실패한 계산 (예 : Haskell에서 Nothing 생성)이 결과로 널 포인터를 생성 할 수 있습니다. 변경 가능한 변수 및 상태를 변경하는 메소드를 작성하여 상태 모나드를 생성 된 언어로 변환 할 수 있습니다.
모나드는 endofunctors 범주의 단일체입니다.
문장이 정리하는 정보는 매우 깊습니다. 그리고 당신은 명령형 언어로 모나드에서 일합니다. 모나드는 "시퀀스 된"도메인 특정 언어입니다. 모나드를 "제한적 프로그래밍"의 수학적 모델로 만드는 특정한 흥미로운 특성을 만족시킵니다. Haskell을 사용하면 다양한 방식으로 결합 할 수있는 작은 (또는 큰) 명령형 언어를 쉽게 정의 할 수 있습니다.
OO 프로그래머는 언어의 클래스 계층을 사용하여 컨텍스트에서 호출 할 수있는 함수 또는 프로 시저의 종류 (객체라고 함)를 구성합니다. 모나드는 다른 모나드가 임의의 방식으로 결합되어서 모든 서브-모나드의 방법을 범위 내로 효과적으로 "가져올"수있는 한이 아이디어에 대한 추상화입니다.
그런 다음 아키텍처는 유형 서명을 사용하여 값을 계산하는 데 사용할 수있는 컨텍스트를 명시 적으로 표현합니다.
이를 위해 모나드 변압기를 사용할 수 있으며 모든 "표준"모나드의 고품질 컬렉션이 있습니다.
해당 모나드 변압기 및 유형 클래스와 함께. 형식 클래스를 사용하면 모나드의 인터페이스를 통합하여 모나드를 결합하는 보완적인 접근 방식을 통해 콘크리트 모나드가 모나드 "종류"에 대한 표준 인터페이스를 구현할 수 있습니다. 예를 들어, 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
모나드는 함수의 배열입니다
(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)
>>=
은 운영자입니다
\x -> x >>= k >>= l >>= m
함수의 배열 이라면 h . g . f
모나드를 전혀 포함하지 않는 함수 입니다.
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 수 있지만, 다른 서브 클래스를 리턴하는 무언가에 대한 메소드는 원래 유형의 컨테이너를 리턴하는 컨테이너 함수의 기준을 충족합니다.
이것이 의미를 보존하는 방법입니다. 즉 컨테이너의 의미와 작업이 변경되지 않고 컨테이너 내부의 객체를 감싸고 향상시킵니다.
일반적인 사용법의 모나드는 절차 적 프로그래밍의 예외 처리 메커니즘과 기능적으로 동일합니다.
현대 절차 언어에서는 예외 처리기를 일련의 문 주위에 배치하면 예외가 발생할 수 있습니다. 명령문 중 하나에서 예외가 발생하면 명령문 순서의 정상적인 실행이 중지되고 예외 핸들러로 전송됩니다.
그러나 함수형 프로그래밍 언어는 철학적으로 "goto"와 같은 예외 처리 기능을 피합니다. 함수형 프로그래밍 관점은 함수에 프로그램 흐름을 방해하는 예외와 같은 "부작용"이 없어야한다는 것입니다.
실제로, I / O로 인해 실제로는 부작용을 배제 할 수 없습니다. 함수형 프로그래밍의 Monad는 일련의 체인 함수 호출 (예기치 않은 결과가 발생할 수 있음)을 취하고 예기치 않은 결과를 나머지 함수 호출을 통해 안전하게 흐를 수있는 캡슐화 된 데이터로 변환하여이를 처리하는 데 사용됩니다.
제어 흐름은 유지되지만 예기치 않은 이벤트는 안전하게 캡슐화되고 처리됩니다.
Marvel의 사례 연구와 함께 간단한 Monads 설명이 여기 있습니다 .
모나드는 효과적인 종속 함수를 시퀀싱하는 데 사용되는 추상화입니다. 여기서 유효하다는 것은 F [A] 형식의 형식 (예 : Option [A])을 반환하는 것을 의미합니다. 여기서 Option은 F (유형 생성자)입니다. 이것을 간단한 2 단계로 봅시다
A => C = A => B andThen B => C
그러나 함수가 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 는 사용자가 자신을 ( 순수한 언어로) 스스로 정의 할 수없는 고유 한 불투명 한 "내장"특수 기능을 정의 할 수도 있습니다.
이론적으로 완벽하지 않을 수도있는 Monads에 대한 이해를 공유하고 있습니다. 모나드는 문맥 전파 에 관한 것 입니다. Monad는 일부 데이터 (또는 데이터 유형)에 대한 컨텍스트를 정의한 다음 처리 파이프 라인에서 해당 컨텍스트가 데이터와 함께 어떻게 전달되는지 정의합니다. 컨텍스트 전파 정의는 주로 동일한 유형의 여러 컨텍스트를 병합하는 방법을 정의하는 것입니다. Monad를 사용한다는 것은 이러한 컨텍스트가 실수로 데이터에서 제거되지 않도록하는 것을 의미합니다. 반면에, 문맥이없는 다른 데이터는 새로운 문맥이나 기존의 문맥으로 가져올 수 있습니다. 그런 다음이 간단한 개념을 사용하여 프로그램의 컴파일 시간 정확성을 보장 할 수 있습니다.
Powershell을 사용해 본 적이 있다면 Eric이 설명한 패턴이 익숙 할 것입니다. Powershell cmdlet 은 모나드입니다. 기능 구성은 파이프 라인으로 표시됩니다 .
Jeffrey Snover의 Erik Meijer와의 인터뷰에 대해 자세히 설명합니다.
"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;
}
실용적인 관점에서 (많은 이전 답변과 관련 기사에서 말한 것을 요약하여) 모나드의 기본 "목적"(또는 유용성) 중 하나는 재귀 적 메소드 호출에 내재 된 종속성을 활용하는 것 같습니다. 일명 함수 구성 (즉, f1이 f2를 호출하면 f3을 호출 할 때, f3은 f1을 f3 전에 호출해야 함), 특히 게으른 평가 모델 (즉, 평범한 순서로 순차적 인 구성)의 맥락에서 순차적 인 구성을 자연스럽게 표현해야합니다. 예를 들어 C에서 "f3 (); f2 (); f1 ();"-f3, f2 및 f1이 실제로 아무것도 반환하지 않는 경우를 생각하면 트릭이 특히 분명합니다 [f1 (f2 (f3)과 같은 체인) 인공적이며 순전히 서열을 생성하기위한 것이다]).
이는 부작용이 관련 될 때, 즉 일부 상태가 변경 될 때 (f1, f2, f3에 부작용이없는 경우 평가 순서에 상관없이) 중요합니다. 이는 순수한 특성입니다. 예를 들어 계산을 병렬화 할 수있는 기능적 언어). 순수한 기능이 많을수록 좋습니다.
그 좁은 관점에서 모나드는 게으른 평가를 선호하는 언어 (코드의 표시에 의존하지 않는 순서에 따라 절대적으로 필요한 경우에만 평가)를 선호하는 언어의 구문 설탕으로 볼 수 있다고 생각합니다. 순차적 구성을 나타내는 다른 수단. 결과적으로 "불완전한"(즉, 부작용이있는) 코드 섹션은 자연스럽게, 필수적으로 제시 될 수 있지만 (부작용없이) 순수한 기능과 깨끗하게 분리됩니다. 게으른 평가.
그러나 여기서 경고 한 것처럼 이것은 한 가지 측면 일뿐 입니다.
제가 생각할 수있는 가장 간단한 설명은 모나드가 기능을 구성하는 방법입니다 (일명 Kleisli 구성). "embelished"함수는 a -> (b, smth)
where a
및 b
형식 (think Int
, Bool
)이 서로 다르지만 반드시 smth
"context"또는 "embelishment"는 아닌 시그니처를 갖습니다 .
이 유형의 함수는 "embelishment"와 동등한 a -> m b
곳에 작성할 수도 있습니다 . 따라서 상황에 따라 값을 반환하는 함수입니다 (작업을 기록하는 함수 , 로깅 메시지가있는 위치 또는 입출력을 수행하고 결과를 IO 함수의 결과에 따라 결정하는 함수).m
smth
smth
모나드는 구현자가 그러한 기능을 구성하는 방법을 알려주는 인터페이스 ( "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 구성 함수에서 일반적인 모나 딕 함수를 도출 할 수 있습니다.