[C#] C #에서 모나드는 무엇입니까?

요즘 모나드에 대해 많은 이야기가 있습니다. 몇 가지 기사 / 블로그 게시물을 읽었지만 개념을 완전히 이해하기위한 예제를 충분히 읽을 수는 없습니다. 그 이유는 모나드가 기능적 언어 개념이므로 예제는 내가 다루지 않은 언어로되어 있기 때문입니다 (기능적 언어를 깊이 사용하지 않았기 때문에). 나는 기사를 완전히 따르기 위해 구문을 깊이 이해할 수는 없지만 이해할만한 가치가 있다고 말할 수 있습니다.

그러나 람다 식 및 기타 기능을 포함하여 C #을 잘 알고 있습니다. C #에는 기능 기능의 하위 집합 만 있으므로 모나드는 C #으로 표현할 수 없습니다.

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

감사!

(편집 : 그건 그렇고, 나는 이미 적어도 3 개의 “모나드 무엇입니까”라는 질문이 있다는 것을 알고 있습니다. 그러나 나는 그들과 같은 문제에 직면하고 있습니다. 초점. 감사합니다.)



답변

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

모나드는이 “계산의 결합”을 수행하는 방법입니다.

일반적으로 두 계산을 결합하는 가장 기본적인 “연산자”는 ;다음 과 같습니다.

a; b

당신이 무슨 뜻 말할 때 “첫째 않는 a, 다음 할 b“. 결과 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많은 시간 을 명시 적으로 지정할 필요가 없습니다 .

new socket() >>= bind(...) >>= connect(...) >>= send(...);

공식적인 정의는 한 함수의 결과를 다음 함수에 대한 입력으로 얻는 방법에 대해 걱정해야하기 때문에 해당 함수에 해당 입력이 필요한 경우와 결합한 함수가 적합한 지 확인하려는 경우 좀 더 복잡합니다. 그것들을 모나드에 결합시키는 방법. 그러나 기본 개념은 기능을 함께 결합하는 다양한 방법을 공식화한다는 것입니다.


답변

이 질문을 게시 한 지 1 년이 지났습니다. 그것을 게시 한 후, 나는 몇 달 동안 Haskell을 탐구했습니다. 나는 그것을 엄청나게 즐겼지만, 나는 Monads를 조사 할 준비가 된 것처럼 그것을 옆에 두었습니다. 나는 일로 돌아가서 프로젝트에 필요한 기술에 집중했다.

그리고 지난 밤, 나는이 응답을 다시 읽었습니다. 가장 중요한 것은 누군가 위에서 언급 한 Brian Beckman 비디오 의 텍스트 주석에서 특정 C # 예제 를 다시 읽은 것 입니다. 완전히 명확하고 밝아서 여기에 직접 게시하기로 결정했습니다.

이 댓글에,뿐만 아니라 내가 이해 같은 느낌 때문에 정확히 모나드이 무엇인지 … 저는 실제로 C #에서 몇 가지 작성했습니다 실현 이다 모나드를 … 또는 적어도 매우 가까이와 같은 문제를 해결하기 위해 노력.

그래서, 여기에 코멘트입니다 -이되는 모든 직접 인용 여기에 코멘트실번는 :

이것은 꽤 멋지다. 그래도 약간 추상적입니다. 실제 사례가 없기 때문에 모나드가 무엇인지 이미 혼란스러워하는 사람들을 상상할 수 있습니다.

따라서 준수하려고 노력하고 실제로 명확하게하기 위해 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();
        }
    }

이제 NullableC # 에서이 작업을 이미 지원하고있는 순간은 무시 하십시오 (널을 입력 할 수있는 정수를 함께 추가 할 수 있으며 둘 중 하나가 null이면 null을 얻습니다). 그러한 기능이 없다고 가정하고 특별한 마술이없는 사용자 정의 클래스 일뿐입니다. 요점은 Bind함수를 사용하여 변수를 Nullable값 의 내용에 바인딩 한 다음 이상한 일이 없다고 가정하고 정상적인 정수처럼 사용하고 함께 추가 할 수 있다는 것입니다. 우리는 마지막에 널 (NULL)에 결과를 포장하고, 그 널 (NULL) 중 하나 null가됩니다 (있는의 경우 f, g또는 h반환 NULL)이거나 합산의 결과가 될 것입니다 f,g 그리고h함께. (이것은 데이터베이스의 행을 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
( 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표기법 을 사용할 수 있습니다. 즉, 기본적으로 두 개의 함수를 정의하여 작은 언어를 작성할 수 있습니다!


답변

모나드는 본질적으로 지연 처리입니다. 허용되지 않는 언어로 부작용 (예 : I / O)이있는 코드를 작성하고 순수한 계산 만 허용하려는 경우 피해야 할 것은 “좋습니다. 부작용을하지 않을 것입니다. 나를 위해, 그러나 당신이했을 경우 어떻게되는지 계산해 주시겠습니까? “

일종의 부정 행위입니다.

이제 그 설명은 모나드의 큰 그림 의도를 이해하는 데 도움이되지만 악마는 세부 사항에 있습니다. 결과를 정확히 어떻게 계산합니까? 때로는 예쁘지 않습니다.

명령형 프로그래밍에 익숙한 사람이 사용하는 방법에 대한 개요를 제공하는 가장 좋은 방법은 DSL에 넣는 것입니다. 예를 들어 출력 파일에 쓸 수 있다면 원하는 것. 나중에 평가하기 위해 문자열로 코드를 작성하는 것처럼 거의 (실제로는 아니지만)


답변

다른 사용자가 심층적으로 게시 할 것이라고 확신하지만 이 동영상이 어느 정도 도움 되었다고 생각하지만 여전히 해결해야 할 개념에 대해 유창하지 않다고 말할 것입니다. Monads와 직관적으로 문제.


답변

클래스를 구현해야하는 C #interface 으로 모나드를 생각할 수 있습니다 . 이것은 인터페이스에서 이러한 선언을 사용하기로 선택한 이유에 대한 모든 범주 이론적 수학을 무시하고 부작용을 피하려고 시도하는 언어로 모나드를 사용하려는 모든 이유를 무시하는 실용적인 답변입니다. 그러나 (C #) 인터페이스를 이해하는 사람으로서 시작이 좋은 것으로 나타났습니다.


답변

“모나드 란 무엇입니까?”에 대한답변 을 참조하십시오.

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

함수형 프로그래밍에 대한 지식이 없다고 가정하고 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);
    }
}


답변