2009-03-23 2 views
177

요즘 모나드에 대해 많은 이야기가 있습니다. 몇 가지 기사/블로그 게시물을 읽었지 만 개념을 완전히 이해하기 위해 예제로 충분히 갈 수는 없습니다. 그 이유는 모나드가 기능적 언어 개념이기 때문에 예제는 (내가 기능적 언어를 깊이 사용하지 않았기 때문에) 함께 작업하지 않은 언어로되어 있기 때문입니다. 나는 그 문법을 충분히 이해할 수는 없지만 ... 거기에 대해 이해할만한 가치가 있다고 말할 수 있습니다.C# 개발자가 이해할 수 있도록 도와주세요. 모나드 란 무엇입니까?

그러나 람다 식과 다른 기능을 포함하여 C#을 잘 알고 있습니다. C#은 기능적 기능의 하위 세트 만 가지고 있으므로 모나드는 C#으로 표현할 수 없다는 것을 알고 있습니다.

그러나 확실히 컨셉을 전달할 수 있습니까? 적어도 나는 그렇게 희망한다. 어쩌면 C# 예제를 기초로 제시 한 다음 C# 개발자가 을 원하는지 설명 할 수 있습니다.은 언어에서 기능 프로그래밍 기능이 없기 때문에 수행 할 수 있지만 수행 할 수 없습니다. 이것은 모나드의 의도와 이점을 전달하기 때문에 환상적입니다. 그래서 여기 내 질문 : 당신이 C# 3 개발자에게 모나드에 대해 줄 수있는 가장 좋은 설명은 무엇입니까?

감사!

(편집 : 덧붙여 말하자면, 나는 적어도 3 가지 "모나드는 무엇인가"라는 질문을 이미 알고 있습니다. 그러나 나는 그들과 같은 문제에 직면 해 있습니다 ... 그래서이 질문은 필요합니다. C# - 개발자 포커스.)

+0

사실 C# 3.0 개발자입니다. .NET 3.5와 혼동하지 마십시오. 그 외에 좋은 질문입니다. – Razzie

+3

LINQ 쿼리 식은 C# 3의 모나드 동작의 예입니다. –

+1

여전히 중복 질문이라고 생각합니다. http://stackoverflow.com/questions/2366/can-anyone-explain-monads의 답변 중 하나는 http://channel9vip.orcsweb.com/shows/Going+Deep/Brian-Beckman-Dont-fear- the-Monads /, 주석 중 하나는 아주 멋진 C# 예제를 가지고 있습니다. :) – jalf

답변

9

모나드는 본질적으로 지연 처리입니다. 부작용이있는 코드 (예 : I/O)를 허용하지 않는 언어로 작성하고 순수한 계산 만 허용하려는 경우 "오케이, 부작용을 느끼지 않을 것입니다. 나를 위해,하지만 만약 당신이 무슨 일이 일어날 지 계산해 주시겠습니까? "

그것은 속임수입니다.

이제이 설명은 모나드의 큰 그림의 의도를 이해하는 데 도움이되지만 악마가 자세히 나와 있습니다. 정확히 어떻게 을 수행합니까? 당신은 결과를 계산합니까? 때때로, 그것은 예쁘지 않습니다.

명령형 프로그래밍에 익숙한 사람을 위해 가장 잘 사용되는 방법은 DSL에서 사용자가 모나드 밖에 사용했던 것과 같은 구문을 사용하는 동작이 대신 DSL을 작성하는 DSL이라고합니다. (예를 들어) 출력 파일에 쓸 수있는 경우 원하는 것을 수행 할 수있는 함수입니다. 당신이 나중에 eval 된 문자열에 코드를 작성하는 것처럼 거의 (그러나 실제로는 아닙니다).

+1

I 로봇 책과 비슷합니까? 과학자가 우주 여행을 계산하고 특정 규칙을 무시하라고 컴퓨터에 요청하는 곳? :) :) :) :) – OscarRyz

+3

흠, 모나드는 지연 처리와 부수적 효과 기능을 캡슐화하는 데 사용할 수 있습니다. 실제로 실제로 하스켈에서 처음 적용되었지만 실제로는 훨씬 일반적인 패턴입니다. 다른 일반적인 용도로는 오류 처리 및 상태 관리가 있습니다. 구문 론적 설탕 (하스켈, F #의 계산식, C#의 Linq 구문)은 모나드처럼 근본적인 것입니다. –

+0

@MikeHadlow : 오류 처리 ('아마도'와 '이드')와 상태 관리 ('상태', 'ST s')의 모나드 인스턴스가 " 나를위한 부작용] ". 또 다른 예는 비 결정론 ('[]')입니다. – pyon

4

나는 다른 사용자가 심층적으로 게시 할 것이라고 확신하지만 어느 정도까지는 this video이 유용하다고 생각하지만, 나는 내가 할 수있는 개념과 같이 유창하게 말할 필요가 없다고 말할 것이다.) Monads와 직관적으로 문제를 해결하기 시작합니다.

+1

내가 찾은 것은 비디오 아래에 C# 예제가 들어있는 주석이었습니다. – jalf

+0

나는 더 이상 도움이되지 않지만 실제로 아이디어를 실천합니다. – TheMissingLINQ

0

모나드는 a C# interface that classes have to implement으로 생각할 수 있습니다. 이것은 인터페이스에서 이러한 선언을 선택하려는 이유 뒤에있는 모든 범주의 이론적 인 수학을 무시하고 부작용을 피하려고하는 언어로 모나드를 사용하려는 모든 이유를 무시한 실용적인 답입니다. 하지만 (C#) 인터페이스를 이해하는 사람 으로서는 좋은 시작이라고 생각했습니다.

+0

당신은 정교 할 수 있습니까? 그것을 모나드와 관련시키는 인터페이스는 무엇입니까? –

+1

나는 블로그 포스트가 그 질문에 헌신하는 여러 단락을 소비한다고 생각한다. – hao

141

하루 종일 프로그래밍에서하는 대부분의 작업은 일부 기능을 결합하여 더 큰 기능을 구축하는 것입니다. 일반적으로 툴박스의 기능뿐만 아니라 연산자, 변수 할당 등과 같은 다른 기능도 있지만, 일반적으로 프로그램은 더 많은 계산을 결합하여 더 많은 계산을 결합합니다.

모나드는이 "계산 결합"을 수행하는 몇 가지 방법입니다.

는 보통 두 개의 연산을 결합하는 가장 기본적인 "연산자는";입니다 :

a; b 

당신이 무슨 뜻이 "먼저 b을 수행 a을"말할 때. 결과 a; b은 기본적으로 다시 더 많은 것들과 결합 될 수있는 계산입니다. 이것은 간단한 모나드입니다. 작은 계산을 큰 것으로 빗질하는 방법입니다. ;은 "왼쪽에있는 것을 한 다음 오른쪽에있는 것을하십시오"라고 말합니다.

객체 지향 언어에서 모나드로 볼 수있는 또 다른 사항은 .입니다. 종종이 같은 일을 찾을 :

a.b().c().d() 

.은 기본적으로 "그 결과의 오른쪽에있는 메소드를 호출 한 후 왼쪽에있는 계산을 평가하고,"를 의미한다. 함수/계산을 결합하는 다른 방법은 ;보다 조금 더 복잡합니다. .과 함께 사슬을 매는 개념은 모나드입니다. 두 계산을 합쳐 새로운 계산에 결합하는 방법이기 때문입니다.

특별한 구문이없는 또 다른 매우 일반적인 모나드는이 패턴 :

rv = socket.bind(address, port); 
if (rv == -1) 
    return -1; 

rv = socket.connect(...); 
if (rv == -1) 
    return -1; 

rv = socket.send(...); 
if (rv == -1) 
    return -1; 

-1 반환 값은 실패를 표시하지만, 밖으로 추상적이 오류 검사에 실제 방법은 경우에도 없다 이 방식으로 결합해야하는 많은 API 호출이 있습니다. 이것은 기본적으로 함수 호출을 "왼쪽에있는 함수가 -1을 반환하면 -1을 반환하고, 그렇지 않으면 오른쪽에 함수를 호출하는"함수 호출을 결합하는 또 다른 모나드입니다. 우리가이 일을 한 운영자 >>=이 있다면 우리는 간단하게 작성할 수

socket.bind(...) >>= socket.connect(...) >>= socket.send(...) 
우리가 우리 자신을 반복 할 필요가 없습니다 있도록이 일을 더 쉽게 읽을 수 있도록 및 결합 기능 중 추상적 인 우리의 특별한 방법에 도움이 될

다시 반복하여.

그리고 일반적인 패턴으로 유용하고 모나드에서 추상화 될 수있는 함수/계산을 결합하는 더 많은 방법이 있습니다. 모나드 사용자는 훨씬 더 간결하고 명확한 코드를 작성할 수 있습니다. 사용 된 기능의 유지 및 관리는 모나드에서 수행됩니다.

: 예 위의 >>= 우리가 명시 적으로 시간의 socket 많이 지정할 필요가 없습니다 있도록 "오류 검사를 수행하고 우리가 입력으로받은 소켓의 오른쪽에 전화"로 확장 할 수 있습니다 들어

공식 정의는 함수가 입력을 필요로하는 경우 다음 함수에 대한 입력으로 한 함수의 결과를 얻는 방법에 대해 염려해야하기 때문에 조금 더 복잡합니다. 결합하는 기능은 모나드에서 결합하려는 방식에 적합합니다. 그러나 기본 개념은 함수를 함께 결합하는 여러 가지 방법을 공식화한다는 것입니다.

+26

좋은 답변입니다! 올리버 스틸 (Oliver Steele)의 인용문을 던지겠습니다. Monads를 C++ 또는 C#으로 오버로드하는 연산자로 연결하려고합니다. Monads를 사용하면 '; 운영자. –

+5

@ JörgWMittag 이전에 그 인용문을 읽었지 만 지나친 말도 안되는 소리처럼 들렸습니다. 이제 나는 모나드를 이해하고 ';' 나는 하나입니다. 그러나 저는 이것이 대부분의 명령적인 개발자들에게는 비합리적인 성명서라고 생각합니다. ';' // 더 이상은 연산자가 아닙니다. –

+0

모나드가 무엇인지 아시나요? 모나드는 "함수"또는 계산이 아니며 모나드에 대한 규칙이 있습니다. – Luis

41

이 질문을 올린 지 1 년이 지났습니다. 그것을 게시 한 후, 나는 몇 달 동안 하스켈을 탐구했다. 나는 그것을 대단히 즐겼다. 그러나 나는 내가 모나드를 탐구 할 준비가 된 것처럼 그것을 옆으로 두었다.나는 일하러 돌아 왔고 프로젝트에 필요한 기술에 집중했다.

어젯밤에 내가 와서이 답변을 다시 읽었습니다. 가장 중요한 것은이고 사람 mentions above의 텍스트 설명에서 the specific C# example을 다시 읽었습니다. 제가 여기에 직접 게시하기로 결정한 것은 아주 분명하고 밝습니다. 이 때문에 주석의

뿐만 아니라 내가 무엇 모나드 정확히을 을 이해 같은 느낌 할 ... 나는 실제로 모나드 ... 또는 것을 C#에서 몇 가지를 작성했습니다 실현 적어도 매우 가까이, 그리고에 노력 동일한 문제를 해결하십시오.

그래서, 여기에 코멘트입니다 -이 sylvan으로 the comment here의 모든 직접 인용입니다 :

이 꽤 멋지다. 그래도 조금 추상적입니다. 나는 어떤 모나드가 실제 예가 부족하기 때문에 이미 혼란스러워하는지 모르는 사람들을 상상할 수 있습니다.

그래서 내가 따르도록 노력하겠습니다.보기 흉하게 보일지라도 C#에서 예제를 할 것입니다. 최종적으로 상응하는 하스켈을 추가하고 멋진 하스켈 신택스 설탕을 보여 드리겠습니다. 모조품이 실제로 유용 해지기 시작하는 곳입니다.

좋아, 그래서 가장 쉬운 Monads 중 하나는 하스켈에서 "아마 모나드"라고합니다. C#에서는 Maybe 형식을 Nullable<T>이라고합니다. 이것은 기본적으로 유효하고 값이 있거나 값이 "null"이고 값이없는 값의 개념을 캡슐화하는 아주 작은 클래스입니다.

이 유형의 값을 결합하기 위해 모나드 내부에 붙이는 데 유용한 것이 실패의 개념입니다. 나는. 우리는 여러 nullable 값을보고 그들 중 하나가 null이 되 자마자 null을 반환 할 수 있기를 원합니다. 예를 들어, 사전이나 무언가에서 많은 키를 찾으면 결국 모든 결과를 처리하고 어떻게 든 결합하기를 원하지만 어떤 키가 사전에없는 경우 유용 할 수 있습니다. 당신은 모든 것을 위해 null을 돌려주고 싶다. null에 대한 각 조회를 수동으로 확인하고 리턴해야하기 때문에 바인드 연산자 내에서이 검사를 숨길 수 있으므로 (이 문제는 모나드 (monads)와 비슷합니다. 코드를 더 쉽게 만드는 바인드 연산자에서 장부 보관을 숨 깁니다. 우리가 세부 사항을 잊을 수 있기 때문에 사용하는 것).

여기에 모든 것을 유발하는 프로그램이 있습니다 (나중에 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(); 
     } 
    } 

지금, 이미 (당신이 함께 널 (NULL)의 int를 추가 할 수 있습니다 중 하나가 null의 경우는 null를 얻을) C#으로 Nullable이 작업을 수행하기위한 지원이 있음을 잠시 무시합니다. 그런 기능이 없다고 가정하고 특수 마법이없는 사용자 정의 클래스 일뿐입니다. 요점은 Bind 함수를 사용하여 변수를 Nullable 값의 내용에 바인딩 한 다음 이상한 일이없는 것처럼 가장하고 정상적인 int처럼 사용하고 그냥 함께 추가하는 것입니다. 결과는 nullable로 끝나며 nullable은 null이됩니다 (f, g 또는 h이 null을 반환하는 경우). f, gh을 합산 한 결과가됩니다. (이것은 데이터베이스의 한 행을 LINQ의 변수에 바인드하는 방법과 비슷합니다. Bind 연산자가 변수에 유효한 행 값만 전달된다는 것을 알고 안전합니다).

f, gh 중 하나를 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 (이 경우 C# 구문에서는 Nullable<A>) 및 a에서 M b (, C# 구문의 함수)을 사용하고 M b (Nullable<B>)을 반환합니다.

코드는 단순히 nullable에 값이 들어 있는지 확인한 다음 그 값을 추출하여 함수에 전달합니다. 그렇지 않으면 null 만 반환합니다. 즉, Bind 연산자가 모든 null 검사 논리를 처리합니다. 만약 우리가 Bind이라고 부르는 값이 null이 아니면 그 값은 람다 함수에 "전달"될 것이고, 그렇지 않으면 우리는 일찍 빠져 나가고 전체 표현식은 null이된다. 이렇게하면 모나드를 사용하여 작성한 코드가이 null 검사 동작에서 완전히 자유 롭습니다. 단지 Bind을 사용하고 모나드 값 (예제 코드에서는 fval, gvalhval) 내의 값에 바인딩 된 변수를 얻고 우리는 Bind이 그것들을 전달하기 전에 null을 체크하는 것을 처리 할 것이라는 지식에서 그들을 안전하게 사용할 수 있습니다.

모나드로 할 수있는 다른 예가 있습니다. 예를 들어 Bind 연산자가 문자의 입력 스트림을 처리하고 파서 연결자를 쓰는 데 사용할 수 있습니다. 각각의 파서 연결자는 역 추적, 파서 실패 등과 같은 것을 완전히 무시할 수 있습니다. 더 작은 파서를 함께 결합하면 Bind의 영리한 구현이 뒤에있는 모든 논리를 정렬합니다. 어려운 비트. 그러면 나중에 누군가가 모나드에 로깅을 추가하지만 모나드를 사용하는 코드는 변경되지 않습니다. 왜냐하면 모든 마법은 Bind 연산자의 정의에서 발생하기 때문에 코드의 나머지 부분은 변경되지 않습니다.

마지막으로 하스켈에서 동일한 코드를 구현 한 것입니다 (--은 주석 행을 시작 함). 당신이 볼 수 있듯이

-- 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 표기는 바로 필수 코드처럼 보이게. 실제로 이것은 의도적으로 설계된 것입니다. 모나드는 명령형 프로그래밍 (mutable state, IO 등)에서 모든 유용한 것들을 캡슐화하는 데 사용할 수 있으며 명령형 문법과 같이 사용하면 좋지만 커튼 뒤에는 모나드뿐 아니라 바인드 연산자를 영리하게 구현할 수 있습니다! 멋진 점은 >>=return을 구현하여 자신의 모나드를 구현할 수 있다는 것입니다. 그렇게하면 해당 모나드도 do 표기법을 사용할 수있게됩니다. 즉, 기본적으로 두 가지 기능을 정의하여 자신의 작은 언어를 작성할 수 있습니다!

+2

개인적으로 나는 모나드의 F # 버전을 선호하지만, 두 경우 모두 훌륭합니다. – ChaosPandion

+3

여기로 돌아와서 게시물을 업데이트 해 주셔서 감사합니다. 특정 지역을 조사하는 프로그래머가 동료 프로그래머가 궁극적으로 어떻게이 영역을 고려 하는지를 이해하는 데 도움이되는 후속 조치입니다. "내가 x 기술에서 어떻게해야합니까?" 너 다 남자 야! – kappasims

+0

나는 당신이 기본적으로 가지고있는 것과 동일한 길을 택했고 모나드를 이해하는 동일한 장소에 도착했다. 이것은 명령형 개발자를 위해 본 적이있는 모나드의 구속 동작에 대한 가장 좋은 설명이라고 말했다. 비록 당신이 모나드에 대한 모든 것을 다루지는 않았지만 위의 설명에 좀더 자세히 설명되어 있습니다. –

0

내 모토 answer을 참조하십시오."

그것은, 동기를 예로 시작하는 예를 통해 작동은 모나드의 예를 도출하고, 공식적으로 정의"모나드를 ". 그것은 함수형 프로그래밍에 대한 지식을지지 않습니다

그것은 function(argument) := expression 구문과 의사 코드를 사용 가장 간단한 표현

이 C# 프로그램은 슈도 모나드의 구현이다.. (참고 :. M는 타입 생성자, feed은 "바인딩"동작하며, wrap가 "복귀"동작이다)

using System.IO; 
using System; 

class Program 
{ 
    public class M<A> 
    { 
     public A val; 
     public string messages; 
    } 

    public static M<B> feed<A, B>(Func<A, M<B>> f, M<A> x) 
    { 
     M<B> m = f(x.val); 
     m.messages = x.messages + m.messages; 
     return m; 
    } 

    public static M<A> wrap<A>(A x) 
    { 
     M<A> m = new M<A>(); 
     m.val = x; 
     m.messages = ""; 
     return m; 
    } 

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

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

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

    static void Main() 
    { 
     V x = new V(); 
     M<T> m = feed<U, T>(f, feed(g, wrap<V>(x))); 
     Console.Write(m.messages); 
    } 
} 
관련 문제