2014-01-26 3 views
13

독서와 사고에 많은 시간을 소비 한 후에 마침내 모나드가 무엇인지, 어떻게 작동하는지, 그리고 유용하다는 점을 마침내 파악한 것 같습니다. 내 주요 목표는 모나드가 C#으로 일상 업무에 적용 할 수있는 것인지 판단하는 것이 었습니다.IO 모나드는 C와 같은 언어로 이해할 수 있습니다.

내가 모나드에 대해 배우기 시작했을 때, 나는 그들이 마법적이고, IO와 다른 비 순수 함수를 어떻게해서든지 순수하게한다는 인상을 받았다.

나는 .NET에서 LINQ와 같은 것들에 대한 모나드의 중요성을 알고 있으며, 아마도 유효한 값을 반환하지 않는 함수를 다루는 데 매우 유용 할 것입니다. 또한 코드의 상태 유지를 제한하고 외부 종속성을 격리해야 할 필요성에 대해 감사 드리며 모나드가 해당 상황을 도울 수 있기를 기대했습니다.

하지만 하스켈은 다른 방법이 없기 때문에 마침내 IO와 처리 상태에 대한 모나드가 필요하다는 결론을 내 렸습니다 (그렇지 않으면 순서를 보장 할 수 없으며 일부 호출은 그러나 더 많은 주류 언어에 대해, 모나드는 이러한 요구에 잘 맞지 않습니다. 대부분의 언어가 이미 처리하고 상태 및 IO를 쉽게 처리하기 때문입니다.

제 질문은, IO Monad가 실제로 하스켈에서만 유용하다고 말하는 것입니까? C#에서 IO 모나드를 구현할 좋은 이유가 있습니까?

+0

아니요. 어떤 함수도 IO를 수행하는 것이 자유롭기 때문에 자신의 코드에서 모나드 밖에서 입출력을 수행하지 않도록하고 외부 코드를 다음과 같이 표시해야하기 때문에 극도로 징계해야합니다. IO에있다. – Lee

답변

12

저는 하스켈과 F #을 정기적으로 사용하며 F #에서 입출력 또는 상태 모나드를 사용하는 느낌이 전혀 들지 않았습니다.

나에게있어서 주된 이유는 하스켈에서 뭔가 이 아니라고 말할 수 있습니다.은 IO 나 상태를 사용합니다. 정말 유용한 정보입니다.

F # (및 C#)에서는 다른 사람들의 코드에 일반적인 기대치가 없으므로 코드에 자신의 규율을 추가하는 것이 많은 도움이되지 않을 것이고 일반적으로 약간의 오버 헤드 (주로 구문 론적)를 지불하게 될 것입니다. 고집합니다.

모나드도 higher-kinded types이 부족하기 때문에 .NET 플랫폼에서 너무 잘 작동하지 않습니다. 워크 플로우 구문을 사용하여 F #에 모나 딕 코드를 작성할 수 있지만 C#에서는 좀 더 고통 스럽지만 쉽게 할 수 없습니다. 여러 개의 다른 모나드를 추상화하는 코드를 작성하십시오.

4

함수가 그 서명을보고 부작용이 있는지를 알 수있는 기능은 함수가 무엇을하는지 이해하려고 할 때 매우 유용합니다. 함수가 적을수록 일 수 있습니다. (. 다형성 함수는 인수를 수행 할 수있는 작업을 제한하는 데 도움이 다른 것입니다) 소프트웨어 트랜잭션 메모리를 구현하는 많은 언어에서

, 문서는 warnings like the following가 있습니다

I/O 및면 다른 활동 트랜잭션이 다시 시도되므로 트랜잭션에서는 영향을 피해야합니다.

해당 경고가 유형 시스템에 의해 시행되는 금지가되면 언어가 더 안전해질 수 있습니다.

부작용이없는 코드로만 최적화를 수행 할 수 있습니다. 그러나 부작용의 부재는 처음에 "무엇이라도 허용한다"면 결정하기 어려울 수 있습니다.

IO 모나드의 또 다른 이점은 IO 동작이 main 함수의 경로에 있지 않는 한 "비활성"이므로 데이터로 조작하고 컨테이너에 넣고 런타임에 구성하는 것이 쉽고 곧.

물론 IO에 대한 모나드 접근법에는 단점이 있습니다. 그러나 "유연하고 원칙적인 방식으로 순수한 게으른 언어로 I/O를 수행하는 몇 가지 방법 중 하나"가되는 것 외에 이점이 있습니다.

7

"우리는 C#에서 IO 모나드가 필요합니까?" 대신에 "C#에서 순도와 불변성을 안정적으로 얻는 방법이 필요합니까?"라고 물어보십시오.

주요 이점은 부작용을 제어하는 ​​것입니다. 모나드를 사용하든 또는 다른 메커니즘을 사용하든 상관 없습니다. 예를 들어 C#에서는 메서드를 pure, 클래스를 immutable으로 표시 할 수 있습니다. 그것은 부작용을 줄이기위한 좋은 방법이 될 것입니다.

이러한 가상 버전의 C#에서는 계산의 90 %를 순수하게 만들려고하고 나머지 10 %에는 IO 및 부작용이 제한되지 않습니다. 그런 세상에서 저는 절대적인 순수성과 IO 모나드에 대한 필요성을별로 보지 못합니다.

부작용 코드를 모나 딕 스타일로 기계적으로 변환하면 아무 것도 얻지 못합니다. 이 코드는 품질이 전혀 향상되지 않습니다. 코드 품질을 90 % 순수하게 유지하고 IO를 작고 쉽게 검토 할 수있는 장소에 집중시켜야합니다.

1

어디서나 IO를 수행 할 수있는 언어에서 IO Monad는 실제 사용하지 않습니다. 사용하고자하는 유일한 것은 부작용을 통제하는 것입니다. 모나드 외부에서 부작용을 막을 수있는 방법이 없으므로별로 중요한 부분이 없습니다.

Maybe 모나드는 잠재적으로 유용한 것처럼 보이지만 실제로는 게으른 평가가있는 언어에서만 작동합니다.

doSomething :: String -> Maybe Int 
doSomething name = do 
    x <- lookup name mapA 
    y <- lookup name mapB 
    return (x+y) 

이 "단락"는 Nothing가 발생 할 수있는 표현을 할 수 있습니다 : 첫 번째 반환 Nothing이 경우 다음 하스켈 표현에서, 두 번째 lookup은 평가되지 않습니다. C#의 구현은 두 가지 조회를 모두 수행해야합니다 (반대 사례를 보는 것이 좋습니다.) if 문을 사용하는 것이 더 나을 것입니다.

또 다른 문제는 추상화의 손실입니다. C# (또는 모나드처럼 보이는 모나드)에서는 확실히 모나드를 구현할 수 있지만 C#에는 더 높은 종류가 없으므로 하스켈 에서처럼 일반화 할 수 없습니다. 예를 들어, mapM :: Monad m => Monad m => (a -> m b) -> [a] -> m [b] (어떤 에 대한 어떤 모나드 작동) 같은 기능을 C#에서 실제로 표현할 수 없습니다. 당신은 확실히 같은 것을 할 수 :

public List<Maybe<a> mapM<a,b>(Func<a, Maybe<b>>); 

특정 모나드 (이 경우 Maybe) 작동,하지만 그것은 그 함수에서 추상 멀리 Maybe 수 없습니다. 다음과 같이 할 수 있어야합니다.

public List<m<a> mapM<m,a,b>(Func<a, m<b>>); 

C#에서는 불가능합니다.

+2

C#에서'Maybe/Option'을 구현하려면 느리게 평가되는'Func >'을 취하는'bind' (즉'SelectMany') 구현이 필요합니다. F #은 엄격한 언어이며 모나드 구현에는 아무런 문제가 없습니다. – Lee

+0

게으름에 대한 내 요점은 그것이 모나드를 구현하는 데 필요한 것이 아니라, 일부 모나드 (나는 아마도 어쩌면 둘 중 어느 하나 일까?)가 엄격한 언어에서 실제로 유용하지 않다는 것입니다. –

+2

'bind' _ _ _에 대한 인자는 느리게 평가되고, 심지어 엄격한 언어로. 'Option'은 F #에서 광범위하게 사용되며, 언어에서'null'을 표현하는 다른 방법으로 복잡하다는 점을 제외하면 C#에서는 유용 할 것입니다. '둘다'에도 동일하게 적용됩니다. – Lee

14

직장에서는 가장 중요한 비즈니스 로직에 대한 C# 코드에서 IO를 제어하기 위해 모나드를 사용합니다.두 가지 예는 고객을위한 최적화 문제에 대한 해결책을 찾는 금융 코드와 코드입니다.

재무 코드에서 우리는 모나드를 사용하여 데이터베이스에 대한 IO 쓰기 및 읽기를 제어합니다. 기본적으로 모나드 연산을위한 작은 연산 세트와 추상 구문 트리로 구성됩니다.

interface IFinancialOperationVisitor<T, out R> : IMonadicActionVisitor<T, R> { 
    R GetTransactions(GetTransactions op); 
    R PostTransaction(PostTransaction op); 
} 

interface IFinancialOperation<T> { 
    R Accept<R>(IFinancialOperationVisitor<T, R> visitor); 
} 

class GetTransactions : IFinancialOperation<IError<IEnumerable<Transaction>>> { 
    Account Account {get; set;}; 

    public R Accept<R>(IFinancialOperationVisitor<R> visitor) { 
     return visitor.Accept(this); 
    } 
} 

class PostTransaction : IFinancialOperation<IError<Unit>> { 
    Transaction Transaction {get; set;}; 

    public R Accept<R>(IFinancialOperationVisitor<R> visitor) { 
     return visitor.Accept(this); 
    } 
} 

본질적으로 모나드에서 행동의 건설을위한 추상 구문 트리와 함께 하스켈 코드

data FinancialOperation a where 
    GetTransactions :: Account -> FinancialOperation (Either Error [Transaction]) 
    PostTransaction :: Transaction -> FinancialOperation (Either Error Unit) 

이며, 본질적으로 : 당신은이 (실제하지 코드) 같은 것을 상상할 수있다 무료 모나드 : 실제 코드에서

interface IMonadicActionVisitor<in T, out R> { 
    R Return(T value); 
    R Bind<TIn>(IMonadicAction<TIn> input, Func<TIn, IMonadicAction<T>> projection); 
    R Fail(Errors errors); 
}  

// Objects to remember the arguments, and pass them to the visitor, just like above 

// Hopefully I got the variance right on everything for doing this without higher order types, which is how we used to do this. We now use higher order types in c#, more on that below. Here, to avoid a higher-order type, the AST for monadic actions is included by inheritance in 

, 그래서 우리는 뭔가 .Select() 대신에 의해 만들어진 것으로 기억이 더있다 효율성을 위해. 중간 계산을 포함한 재무 운영은 여전히 ​​IFinancialOperation<T> 유형입니다. 작업의 실제 성능은 통역사에 의해 수행됩니다. 통역사는 모든 데이터베이스 작업을 트랜잭션으로 래핑하고 구성 요소가 실패 할 경우 해당 트랜잭션을 롤백하는 방법을 처리합니다. 우리는 또한 단위 테스트를 위해 통역사를 사용합니다.

최적화 코드에서 우리는 최적화를 위해 외부 데이터를 얻기 위해 IO를 제어하기 위해 모나드를 사용합니다. 이것은 우리가 우리가 여러 설정에서 정확히 같은 비즈니스 코드를 사용할 수 있습니다 계산을 구성하는 방법의 무지 코드를 작성할 수 있습니다 :

  • 동기 IO 및
  • 비동기 IO 요구에 따라 수행 계산과 계산에 대한 계산 코드가 사용할 모나드 통과해야하기 때문에 단위 병렬
  • 조롱 IO에서 수행 많은 계산에

을 테스트, 우리는 모나드의 명시 적 정의가 필요합니다. 여기에 하나 있습니다. IEncapsulated<TClass,T>은 본질적으로 TClass<T>을 의미합니다. 이를 통해 C# 컴파일러는 모나드 유형을 세 가지 모두 동시에 추적하여 모나드 자체를 처리 할 때 캐스팅해야 할 필요성을 극복 할 수 있습니다.

public interface IMonadGetSomething<M> : IMonadFail<Error> { 
    IEncapsulated<M, Something> GetSomething(); 
} 

그런 다음 우리가 계산하는 방법에 대해 알고하지 않는 코드를 작성할 수 있습니다 :

public interface IEncapsulated<TClass,out T> 
{ 
    TClass Class { get; } 
} 

public interface IFunctor<F> where F : IFunctor<F> 
{ 
    // Map 
    IEncapsulated<F, B> Select<A, B>(IEncapsulated<F, A> initial, Func<A, B> projection); 
} 

public interface IApplicativeFunctor<F> : IFunctor<F> where F : IApplicativeFunctor<F> 
{ 
    // Return/Pure 
    IEncapsulated<F, A> Return<A>(A value); 
    IEncapsulated<F, B> Apply<A, B>(IEncapsulated<F, Func<A, B>> projection, IEncapsulated<F, A> initial); 
} 

public interface IMonad<M> : IApplicativeFunctor<M> where M : IMonad<M> 
{ 
    // Bind 
    IEncapsulated<M, B> SelectMany<A, B>(IEncapsulated<M, A> initial, Func<A, IEncapsulated<M, B>> binding); 
    // Bind and project 
    IEncapsulated<M, C> SelectMany<A, B, C>(IEncapsulated<M, A> initial, Func<A, IEncapsulated<M, B>> binding, Func<A, B, C> projection); 
} 

public interface IMonadFail<M,TError> : IMonad<M> { 
    // Fail 
    IEncapsulated<M, A> Fail<A>(TError error); 
} 

이제 우리는 우리의 계산이 볼 수 있어야합니다 IO의 부분에 대한 모나드의 또 다른 클래스를 만드는 상상할 수 조립되어

public class Computations { 

    public IEncapsulated<M, IEnumerable<Something>> GetSomethings<M>(IMonadGetSomething<M> monad, int number) { 
     var result = monad.Return(Enumerable.Empty<Something>()); 
     // Our developers might still like writing imperative code 
     for (int i = 0; i < number; i++) { 
      result = from existing in r1 
        from something in monad.GetSomething() 
        select r1.Concat(new []{something}); 
     } 
     return result.Select(x => x.ToList()); 
    } 
} 

이것은 IMonadGetSomething<>의 동기 및 비동기 구현 모두에서 재사용 될 수있다. 이 코드에서 비동기 설정에서도 오류가 발생할 때까지 GetSomething()이 차례로 발생합니다. (실제 생활에서 목록을 만드는 방법이 아닙니다.)

+1

나는 그것이 정말로 받아 들여 져야만한다. 그러나 모나 딕 라이브러리를 사용하는 사례가 더 부족하다. – vittore

2

항상 IO 모나드는 특별하고 추론하기가 어렵습니다. 하스켈 커뮤니티에서는 IO가 유용하지만 다른 모나드가 제공하는 많은 이점을 공유하지 않는다고 잘 알려져 있습니다. 그것은 당신이 언급 한 것처럼 좋은 모델링 도구가 아닌 권한 위치에 의해 크게 동기 부여됩니다.

그렇기 때문에, C#이나 실제로는 타입 어노테이션이 부작용을 포함하지 않는 언어에서는별로 유용하지 않다고 말하고 싶습니다.

하지만 하나의 모나드입니다. 앞에서 언급했듯이 LINQ에서는 Failure가 표시되지만보다 정교한 모나드는 부작용 언어에서도 유용합니다.

예를 들어, 임의의 글로벌 및 로컬 상태 환경 에서조차도 상태 모나드는 권한이 부여 된 일부 종류의 상태에서 작동하는 작업 체제의 시작과 끝을 모두 나타냅니다. 하스켈이 누리는 부작용 제거를 얻지는 못했지만, 여전히 좋은 문서를 얻을 수 있습니다.

파서 모나드 (Parser monad)와 같은 것을 소개하면 더 좋아집니다. C#에서도 모나드를 사용하면 문자열을 사용하면서 수행 된 비 결정적, 역 추적 오류 같은 것을 지역화 할 수 있습니다. 분명히 특정 종류의 변이성으로 그렇게 할 수는 있지만 Monads는 특정 표현이 당신이 관련되어있을 수있는 지구 적 상태에 상관없이 그 효과적인 체제에서 유용한 행동을 수행한다고 표현합니다.

그래서 저는 그렇다고 말하면 모든 입력 된 언어에서 유용하다고 말하고 싶습니다. 그러나 하스켈 (Haskell)과 같은 IO는 그것을합니까? 어쩌면 그렇게 많이는 아니 겠지요.

+1

어떤 의미에서 IO Monad는 특별하고 추론하기가 어렵습니까? 모나드 법칙을 따르고, 직접적인 해석 (명령형 프로그램을 대표하는 데이터 구조)을하고, 안전하지 않은 (모든 것을'안전하지 않은';'Debug.Trace'; ...) 함수에서 벗어나거나) 프로그램의 비 -OIO 부분을 손상시키지 않습니다. – delnan

+0

분명히 모나드이지만 표기법은 규칙적인 명령형 프로그램만큼이나 어두운 것입니다. C#에서 그 멍청함을 원한다면 아무 것도 얻지 못할 것입니다. 그러나 다른 모나드에는 매우 명확한 표시가 있습니다. –

관련 문제