이 질문을 게시 한 지 1 년이 지났습니다. 그것을 게시 한 후, 나는 몇 달 동안 Haskell을 탐구했습니다. 나는 그것을 엄청나게 즐겼지만, 나는 Monads를 조사 할 준비가 된 것처럼 그것을 옆에 두었습니다. 나는 일로 돌아가서 프로젝트에 필요한 기술에 집중했다.
이것은 꽤 멋지다. 그래도 약간 추상적입니다. 실제 사례가 없기 때문에 모나드가 무엇인지 이미 혼란스러워하는 사람들을 상상할 수 있습니다.
따라서 준수하려고 노력하고 실제로 명확하게하기 위해 C #에서 예를 들어 보겠습니다. 마지막에 동등한 하스켈을 추가하고 모나드가 실제로 유용하기 시작하는 멋진 하스켈 구문 설탕을 보여 드리겠습니다.
자, 가장 쉬운 Monads 중 하나는 Haskell에서 "Maybe monad"라고 불립니다. C #에서 Maybe 유형이 호출 Nullable<T>
됩니다. 기본적으로 유효하고 값이 있거나 "널"이며 값이없는 값의 개념을 캡슐화하는 작은 클래스입니다.
이 유형의 값을 결합하기 위해 모나드 내부에 붙어있는 유용한 것은 실패의 개념입니다. 즉, 여러 개의 nullable 값을보고 null
그 중 하나가 null이되는 즉시 반환 할 수 있기를 원합니다 . 예를 들어 사전이나 다른 항목에서 많은 키를 조회하고 결국 모든 결과를 처리하여 조합하려는 경우에 유용하지만 사전에없는 키가있는 경우, 당신 null
은 모든 것을 위해 돌아가고 싶습니다 . 각 조회를 수동으로 확인 null
하고 반환 해야하는 지루한 작업이 필요
하므로 바인드 연산자 (모나드의 일종 인 바인드 연산자 에서이 검사를 숨길 수 있습니다. 우리는 세부 사항을 잊을 수 있기 때문에 사용).
여기에 모든 것을 동기를 부여하는 프로그램이 있습니다 ( Bind
나중에 정의하겠습니다
. 이것이 좋은 이유를 보여주기 위해서입니다).
class Program
{
static Nullable<int> f(){ return 4; }
static Nullable<int> g(){ return 7; }
static Nullable<int> h(){ return 9; }
static void Main(string[] args)
{
Nullable<int> z =
f().Bind( fval =>
g().Bind( gval =>
h().Bind( hval =>
new Nullable<int>( fval + gval + hval ))));
Console.WriteLine(
"z = {0}", z.HasValue ? z.Value.ToString() : "null" );
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
이제 Nullable
C # 에서이 작업을 이미 지원하고있는 순간은 무시 하십시오 (널을 입력 할 수있는 정수를 함께 추가 할 수 있으며 둘 중 하나가 null이면 null을 얻습니다). 그러한 기능이 없다고 가정하고 특별한 마술이없는 사용자 정의 클래스 일뿐입니다. 요점은 Bind
함수를 사용하여 변수를 Nullable
값 의 내용에 바인딩 한 다음 이상한 일이 없다고 가정하고 정상적인 정수처럼 사용하고 함께 추가 할 수 있다는 것입니다. 우리는 마지막에 널 (NULL)에 결과를 포장하고, 그 널 (NULL) 중 하나 null가됩니다 (있는의 경우 f
, g
또는 h
반환 NULL)이거나 합산의 결과가 될 것입니다 f
,g
그리고h
함께. (이것은 데이터베이스의 행을 LINQ의 변수에 바인드하고 그 행을 수행하는 방법과 유사합니다. Bind
연산자가 변수에 유효한 행 값만 전달되도록 할 수 있다는 점에서 안전 합니다).
이 놀이와의를 변경할 수 있습니다 f
, g
및 h
널 (null)을 반환하면 모든 일이 널 (null)을 반환합니다 것을 볼 수 있습니다.
따라서 바인드 연산자는 분명히이 검사를 수행하고 null 값이 발생하면 null을 반환하고 Nullable
구조 내부의 값 을 따라 람다로 전달합니다.
Bind
연산자 는 다음과 같습니다 .
public static Nullable<B> Bind<A,B>( this Nullable<A> a, Func<A,Nullable<B>> f )
where B : struct
where A : struct
{
return a.HasValue ? f(a.Value) : null;
}
여기의 유형은 비디오에서와 같습니다. 그것은 소요 M a
( Nullable<A>
이 경우에 대한 C # 구)로부터의 함수 a
에
M b
( Func<A, Nullable<B>>
C #에서 구문)과는 리턴 M b
( Nullable<B>
).
이 코드는 nullable에 값이 포함되어 있는지 확인하여 추출하여 함수에 전달하면 null을 반환합니다. 이것은 Bind
운영자가 우리를 위해 모든 null 검사 로직을 처리 한다는 것을 의미합니다 . 우리가 호출 Bind
하는 값이 널이 아닌 경우에만
그 값이 람다 함수에 "통과"됩니다. 그렇지 않으면 우리는 일찍 구제되며 전체 표현식은 널입니다. 이것은 우리가, 우리가 사용하는이 널 (null) 검사 동작을 완전히 무료로 모나드를 사용하여 작성하는 것이 코드 수 Bind
(및 모나드 값 내부 값에 바인딩 변수를 얻을 fval
,
gval
그리고 hval
예제 코드를) 우리는 그들에게 안전하게 사용할 수 있습니다 Bind
전달하기 전에 null을 확인 하는 지식이 필요합니다.
모나드로 할 수있는 다른 예가 있습니다. 예를 들어 Bind
연산자가 입력 스트림을 처리하도록하고 파서 결합기를 작성하는 데 사용할 수 있습니다. 각 파서 조합기는 역 추적, 파서 실패 등과 같은 것들을 완전히 잊을 수 있으며, 작은 파서를 결합하여 문제가 발생하지 않는 것처럼 결합 할 수 Bind
있습니다. 어려운 비트. 그런 다음 나중에 누군가 모나드에 로깅을 추가 할 수 있지만 모나드를 사용 Bind
하는 코드는 변경되지 않습니다. 모든 마술은 연산자 정의에서 발생하기 때문에 나머지 코드는 변경되지 않습니다.
마지막으로, Haskell에서 동일한 코드를 구현 한 것입니다 ( --
주석 줄 시작).
-- Here's the data type, it's either nothing, or "Just" a value
-- this is in the standard library
data Maybe a = Nothing | Just a
-- The bind operator for Nothing
Nothing >>= f = Nothing
-- The bind operator for Just x
Just x >>= f = f x
-- the "unit", called "return"
return = Just
-- The sample code using the lambda syntax
-- that Brian showed
z = f >>= ( \fval ->
g >>= ( \gval ->
h >>= ( \hval -> return (fval+gval+hval ) ) ) )
-- The following is exactly the same as the three lines above
z2 = do
fval <- f
gval <- g
hval <- h
return (fval+gval+hval)
보시다시피 do
끝에 멋진 표기법을 사용하면 명령형 코드처럼 보입니다. 실제로 이것은 의도적으로 설계된 것입니다. Monads는 명령형 프로그래밍 (mutable state, IO 등)에서 유용한 모든 것들을 캡슐화하는 데 사용될 수 있으며이 멋진 명령 같은 구문을 사용하여 사용되지만 커튼 뒤에는 모나드와 바인드 연산자의 영리한 구현입니다! 멋진 점은 >>=
and를 구현하여 자신의 모나드를 구현할 수 있다는 것입니다 return
. 그리고 그렇게한다면 모나드도이 do
표기법 을 사용할 수 있습니다. 즉, 기본적으로 두 개의 함수를 정의하여 작은 언어를 작성할 수 있습니다!