Try monad를 사용하여 보다 안전한 데이터 분석

52566 단어 functionalcsharp
나는 이전에 maybe monads와 어떻게 use them with lists를 써서 대상 프로그래밍 언어의 빈 인용 가능성을 없앨 수 있는지 쓴 적이 있다.이 독립된 글은 데이터 해석 이상을 예로 삼아 모든 다른 유형의 처리되지 않은 이상을 방지하기 위해 더욱 통용되는monad를 사용하는 방법을 소개했다.
(주: 전 시리즈에 이 점을 포함시키려고 했지만 결국 이런 측면에서 보면 정보를 조직하기 어렵다는 것을 알게 되었다. 이 글을 독립된 문장으로 보여주면 독자는 처음부터try monad를 쓸 수 있고 이전의 monad를 이해할 필요가 없다. 그러나 불행한 부작용은 이 문장이 중복될 수 있다는 것이다.)r 앞의 두 게시물.)

보다 안전한 예외 처리 필요
프로세스 코드 중의 이상 처리는 서투르고 믿을 수 없다.그것은 다른 블록급 프로그래밍 개념과 마찬가지로 서투르다. 아주 지루하지만 읽기도 쉽지 않다.그것도 믿을 수 없다. 왜냐하면 어떤 조작이 처리해야 할 이상을 던질지 예측하기 어렵다.
자바 컴파일러는 이상을 검사하는 데 도움을 주었습니다. 이상을 검사하는 것은 지루하고 서투른 방법으로 서명할 수 있지만, 이상을 이상을 검사하는 것으로 설정하는 것은 선택할 수 있기 때문에 보장되지 않습니다.
이러한 이유로 C#는 검사 이상을 완전히 피하지만, 이것은 실행을 예측할 수 없게 할 것이다. 원본 코드를 보지 않으면 어떤 C# 함수가 안전하지 않은 조작을 포함하는지 확정할 수 없기 때문이다.비정상적으로 좋은 문서 기록이 있어도 컴파일러는 비정상적으로 처리할 수 없습니다.따라서 TryParse 와 같은 터무니없는 구조를 얻었습니다. 아웃 파라미터와null 검사가 필요합니다.
/* SO MANY LINES OF CODE */
bool success = Int32.TryParse(value, out number);
if (success)
{ 
    Console.WriteLine("Converted '{0}' to {1}.", value, number);
}
else
{ 
    Console.WriteLine("Attempted conversion of '{0}' failed.", value ?? "<null>");
}
따라서 대상을 대상으로 하는 프로그래머는 때때로 그들의 코드만 받아들여서 의외의 이상을 던진다. 설령 당신이 그것을 매우 열심히 처리하더라도.이 글은 너에게 의외의 절차 행위를 받아들일 필요가 없다는 것을 알려줄 것이다.대상을 대상으로 하는 코드에 함수 구조를 도입함으로써 모든 이상을 깔끔하게 처리할 수 있다.

쪽지가 해결 방안의 흡인력이다.
monad는 존재할 수도 있고 존재하지 않을 수도 있는 값을 포장하는 패키지를 제공합니다.우리는 이상을 구체적으로 처리할 수도 있고 묵묵히 실패할 수도 있다. 우리의 선택은 간결하고 명확하며 읽을 수 있을 것이다.
함수식 프로그래밍에서, 단자는 병렬 형식이다.대상 언어 자체에는 연합 유형이 없지만, 연합 유형과 같은 인터페이스를 가지고 있다.인터페이스도 대상을 대상으로 하는 숨겨진 세부 사항을 가장 잘 실천하는 것이기 때문에 우리는 이 두 가지 측면이 모두 가장 좋다는 것을 안다.

가능한 상태의 모델링
빈 인터페이스부터 시작합시다.이것은 두 가지 가능한 상태를 대표할 것이다. 우리가 우리의 가치가 있든지, 아니면 예외가 있든지.
시도하다.대테러 엘리트
public interface ITry<T>
{
}
지금 우리는 두 종류로 그것을 실현할 수 있다.Success 유형은 실제로 데이터를 포함한다.
성취대테러 엘리트
public class Success<T>
{ 
    private T member; 
    public Success(T member) { this.member = member; }
}
그리고 우리의 실패 유형은 하나의 이상만 포함합니다.
실패대테러 엘리트
public class Failure<T>
{ 
    private Exception ex; 
    public Failure(Exception ex) { this.ex = ex; }
}
우리가 이미 이 두 개의 구조 함수를 정의했으니, 우리는 오류 포획 코드를 작성할 수 있다.
시도하다.대테러 엘리트
public interface ITry<T>
{
}

public static class Try
{ 
    public ITry<T> Factory<T>(Func<T> unsafeOperation) 
    { 
        try { return new Success<T>(unsafeOperation()) } 
        catch(Exception e) { return new Failure(e) } 
    }
}
현재 우리는 이Factory를 사용하여 우리를 포장할 수 있다. 이런 방법은 이상을 던지고 ITry<T>의 실례를 되돌려줄 수 있다. T가 아니라 후자는
  • 어떤 방법이 안전하지 않은지 명확하게 전달하고
  • 코드의 가독성을 높인다
  • 강제 호출 코드는 이상 상황을 어떻게 처리하는지 결정한다.
  • 오류 처리를 다음과 같이 한 줄로 압축합니다.
  • ITry<int> int n = Try.Factory<int>(() => int.Parse("hello, world."));
    

    매핑 및 평면 매핑
    그러나 지금 만약 조작이 성공한다면, 우리는 반환값과 상호작용하는 방법이 필요하다.memberSuccess사유이지만 우리는 전달 함수를 통해 실현할 수 있다.다음과 같이 인터페이스에 두 가지 방법을 추가합니다.
    시도하다.대테러 엘리트
    public interface ITry<T>
    { 
        /// <summary> applies func to the value if `this` was a `Success`, else 
        /// fails silently 
        /// </summary> 
        /// <returns> a new `Success` of the result of `func(t)` or a Failure 
        /// </returns> 
        ITry<TNext> Map<TNext>(Func<T, TNext> func); 
    
        /// <summary> applies func if `this` was a `Success` or fails silently. 
        /// </summary> 
        /// <returns> a new `Success` if both `this` and `func` were successful 
        /// or a failure if either failed. 
        /// </returns> 
       ITry<TNext> FlatMap<TNext>(Func<T, ITry<TNext>> func);
    }
    
    public static class Try
    { 
        public ITry<T> Factory<T>(Func<T> unsafeOperation) 
        { 
            try { return new Success<T>(unsafeOperation()) } 
            catch(Exception e) { return new Failure<T>(e) } 
        }
    }
    
    이를 통해 가치를 최대한 활용하면서 동시에 가치를 보호할 수 있습니다.
    이제 우리는 이렇게 안전하게 그것들을 실시할 수 있다.
    성취대테러 엘리트
    public class Success<T>
    { 
        private T member; 
        public Success(T member) { this.member = member; } 
    
        public ITry<TNext> Map<TNext>(Func<T, TNext> func) 
            => Try.Factory(() => func(member)); 
    
        public ITry<TNext> FlatMap<TNext>(Func<T, ITry<TNext>> func)
            => func(member);
    }
    
    실패대테러 엘리트
    public class Failure<T>
    { 
        private Exception ex; 
        public Failure(Exception ex) { this.ex = ex; } 
    
        public ITry<TNext> Map<TNext>(Func<T, TNext> func) 
            => new Failure<TNext>(ex); 
    
        public ITry<TNext> FlatMap<TNext>(Func<T, ITry<TNext>> func) 
            => new Falure<TNext>(ex);
    }
    
    객체에 대한 설계 모드를 잘 아는 경우 빈 객체 모드로 간주할 수 있습니다. 실패는 기본적으로 메서드를 무시하는 가상 객체에 의해 다음과 같이 표시됩니다.
    ITry<int> doubled = Try.Factory<int>(() => int.Parse("5"))
        .Map(i => i * 2);
    
    이 모든 것은 좋지만, 어느 순간 회원 가치관을 열어야 할지도 모른다.두 가지 안전한 방법이 있다.이제 이 두 개를 봅시다.

    예비(fallback)을 사용하여 try monad 안전하게 확장
    우리는 예비품을 제공하기만 하면 우리의 ITry<T> 를 열고 하나를 얻을 수 있다.가장 뚜렷한 방법은 T라는 방법을 통해 직접 값을 제공하는 것이다.
    시도하다.대테러 엘리트
    public interface ITry<T>
    { 
        /// <returns> the member of `this` if `this` was a `Success`, or the 
        /// fallback if it was a `Failure`. 
        /// </returns> 
        T GetSafe(T fallback); 
        /// <summary> applies func to the value if `this` was a `Success`, else 
        /// fails silently 
        /// </summary> 
        /// <returns> a new `Success` of the result of `func(t)` or a Failure 
        /// </returns> 
        ITry<TNext> Map<TNext>(Func<T, TNext> func); 
    
        /// <summary> applies func if `this` was a `Success` or fails silently. 
        /// </summary> 
        /// <returns> a new `Success` if both `this` and `func` were successful 
        /// or a failure if either failed. 
        /// </returns> 
        ITry<TNext> FlatMap<TNext>(Func<T, ITry<TNext>> func);
    }
    
    public static class Try
    { 
        public ITry<T> Factory<T>(Func<T> unsafeOperation) 
        { 
            try { return new Success<T>(unsafeOperation()) } 
            catch(Exception e) { return new Failure<T>(e) } 
        }
    }
    
    성취대테러 엘리트
    public class Success<T>
    { 
        private T member; 
        public Success(T member) { this.member = member; } 
    
        public T GetSafe(T fallback) => member; 
    
        public ITry<TNext> Map<TNext>(Func<T, TNext> func) 
            => Try.Factory(() => func(member)); 
    
        public ITry<TNext> FlatMap<TNext>(Func<T, ITry<TNext>> func) 
            => func(member);
    }
    
    실패대테러 엘리트
    public class Failure<T>
    { 
        private Exception ex; 
        public Failure(Exception ex) { this.ex = ex; } 
    
        public T GetSafe(T fallback) => fallback; 
    
        public ITry<TNext> Map<TNext>(Func<T, TNext> func) 
            => new Failure<TNext>(ex); 
    
        public ITry<TNext> FlatMap<TNext>(Func<T, ITry<TNext>> func)
            => new Falure<TNext>(ex);
    }
    
    이것은 매우 간단하다. GetSafe() 반환을 포기하고 Success 그것에 의존한다.이렇게 하면 다음과 같이 한 행에서 오류를 캡처하고 복구할 수 있습니다.
    //evaluates to zero.
    int i = Try.Factory<int>(() => int.Parse("hello, world"))
        .GetSafe(0);
    

    아날로그 모드 일치 기능 예비(fallback)
    그러나 리베이트 값이 계산에 비싸다고 가정하면 평가하거나 검색할 수 없다.우리는 매번 조작이 성공할 때마다 이 값을 계산하고 그것을 버리기를 원하지 않는다.우리는 함수 프로그래밍을 사용하여 조건부로 그것을 계산할 수 있다.
    함수식 프로그래밍의 패턴 일치 구조와 같이 새로운 방법 Failure 을 호출합시다.이 함수는 두 개의 함수 매개 변수를 채택하여 상응하는 매개 변수를 집행할 것이다.
    시도하다.대테러 엘리트
    public interface ITry<T>
    { 
        /// <returns> the member of `this` if `this` was a `Success`, or the 
        /// fallback if it was a `Failure`. 
        /// </returns> 
        T GetSafe(T fallback); 
    
        /// <summary> applies func to the value if `this` was a `Success`, else 
        /// fails silently 
        /// </summary> 
        /// <returns> a new `Success` of the result of `func(t)` or a Failure 
        /// </returns> 
        ITry<TNext> Map<TNext>(Func<T, TNext> func); 
    
        /// <summary> applies func if `this` was a `Success` or fails silently. 
        /// </summary> 
        /// <returns> a new `Success` if both `this` and `func` were successful 
        /// or a failure if either failed. 
        /// </returns> 
        ITry<TNext> FlatMap<TNext>(Func<T, ITry<TNext>> func); 
    
        /// <summary>applies `success` if `this was a `Success` or applies 
        /// `failure` if `this` was a failure. 
        /// </summary> 
        /// <returns> the result of whichever function executed.</returns> 
        TNext Match(Func<T, TNext> success, Func<Exception, TNext> failure);
    }
    
    public static class Try
    { 
        public ITry<T> Factory<T>(Func<T> unsafeOperation) 
        { 
            try { return new Success<T>(unsafeOperation()) } 
            catch(Exception e) { return new Failure<T>(e) } 
        }
    }
    
    현재, 우리는 모든 인터페이스에 적당한 함수를 적용하고 다른 함수를 버려서 이 함수를 실현할 수 있다.
    성취대테러 엘리트
    public class Success<T>
    { 
        private T member; 
        public Success(T member) { this.member = member; } 
    
        public T GetSafe(T fallback) => member; 
    
        public ITry<TNext> Map<TNext>(Func<T, TNext> func) 
            => Try.Factory(() => func(member)); 
    
        public ITry<TNext> FlatMap<TNext>(Func<T, ITry<TNext>> func) 
            => func(member); 
    
        public TNext Match(Func<T, TNext> success, Func<Exception, TNext> failure) 
            => success(member);
    }
    
    실패대테러 엘리트
    public class Failure<T>
    { 
        private Exception ex; 
        public Failure(Exception ex) { this.ex = ex; } 
    
        public T GetSafe(T fallback) => fallback; 
    
        public ITry<TNext> Map<TNext>(Func<T, TNext> func) 
            => new Failure<TNext>(ex); 
    
        public ITry<TNext> FlatMap<TNext>(Func<T, ITry<TNext>> func) 
            => new Falure<TNext>(ex); 
    
        public TNext Match(Func<T, TNext> success, Func<Exception, TNext> failure) 
            => failure(ex);
    }
    
    두 번째 함수Match는 이상 자체와 상호작용을 할 수 있기 때문에 failure/try 블록에서처럼 창고 추적을 기록하거나 경보를 보낼 수 있습니다.
    /* The long running calculation doesn't execute as long as the parse 
    * succeeds.*/
    int n = Try.Factory<int>(() => int.Parse("5")) 
        .Match(i => i, ex => longRunningCalculation());
    

    다시 참조 예외
    물론, 어떤 잘못들은 우리가 보완할 수 없거나 보완할 수 없다.예를 들어 데이터베이스가 응답하지 않으면 백엔드 서비스가 해야 할 일은 프레임워크가 500레벨 HTTP로 응답하기 위한 이상을 던지는 것이다.따라서 탈출구를 제공하는 것은 좋은 생각이다.catch라고 부른다.
    시도하다.대테러 엘리트
    public interface ITry<T>
    { 
        /// <returns> the member of `this` if `this` was a `Success`, or the 
        /// fallback if it was a `Failure`. 
        /// </returns> 
        T GetSafe(T fallback); 
    
        /// <returns> the member of `this` if `this was a `Success` or throws 
        /// the exception if `this` was a failure. 
        /// </returns> 
        T GetUnsafe(); 
    
        /// <summary> applies func to the value if `this` was a `Success`, else 
        /// fails silently 
        /// </summary> 
        /// <returns> a new `Success` of the result of `func(t)` or a Failure 
        /// </returns> 
        ITry<TNext> Map<TNext>(Func<T, TNext> func); 
    
        /// <summary> applies func if `this` was a `Success` or fails silently. 
       /// </summary> 
       /// <returns> a new `Success` if both `this` and `func` were successful 
       /// or a failure if either failed. 
       /// </returns> 
       ITry<TNext> FlatMap<TNext>(Func<T, ITry<TNext>> func); 
    
       /// <summary>applies `success` if `this was a `Success` or applies 
       /// `failure` if `this` was a failure. 
       /// </summary> 
       /// <returns> the result of whichever function executed.</returns> 
       TNext Match(Func<T, TNext> success, Func<Exception, TNext> failure);
    }
    
    public static class Try
    { 
        public ITry<T> Factory<T>(Func<T> unsafeOperation) 
        { 
            try { return new Success<T>(unsafeOperation()) } 
            catch(Exception e) { return new Failure<T>(e) } 
        }
    }
    
    성취대테러 엘리트
    public class Success<T>
    { 
        private T member; 
        public Success(T member) { this.member = member; } 
    
        public T GetSafe(T fallback) => member; 
    
        public T GetUnsafe() => member; 
    
        public ITry<TNext> Map<TNext>(Func<T, TNext> func) 
            => Try.Factory(() => func(member)); 
    
        public ITry<TNext> FlatMap<TNext>(Func<T, ITry<TNext>> func) 
            => func(member); 
    
        public TNext Match(Func<T, TNext> success, Func<Exception, TNext> failure) 
            => success(member);
    }
    
    실패대테러 엘리트
    public class Failure<T>
    { 
        private Exception ex; 
        public Failure(Exception ex) { this.ex = ex; } 
    
        public T GetSafe(T fallback) => fallback; 
    
        public T GetUnsafe() 
            => throw new Exception("GetUnsafe failure.", ex); 
    
        public ITry<TNext> Map<TNext>(Func<T, TNext> func) 
            => new Failure<TNext>(ex); 
    
        public ITry<TNext> FlatMap<TNext>(Func<T, ITry<TNext>> func) 
            => new Falure<TNext>(ex); 
    
        public TNext Match(Func<T, TNext> success, Func<Exception, TNext> failure) 
            => failure(ex);
    }
    
    (방주: 새로운 이상을 만들지 않는 상황에서 낡은 것만 새로 고치지 마라GetUnsafe() - 낡은 것을 다시 갱신하면 로그 기록에 사용하고자 하는 창고 추적에 중요한 정보를 보존할 수 있다.)

    근데 왜죠?
    특히 우리가 이상을 다시 던지고 싶을 뿐이라면 이상을 잡는 것이 무슨 의미가 있겠는가?이런 상황에서 예외가 튀어나오는 게 낫지 않을까요?
    내가 이 점을 제기한 것은 가독성의 중요성을 강조하기 위해서이다.안전하지 않은 코드는 최소한 항상 명확한 경고 라벨을 가지고 있어야 한다.버퍼링 이상은 의외나 의외가 아닙니다. 코드를 호출할 때 버퍼링 이상이 필요하면 ex 이라는 함수를 사용하도록 요구해야 합니다.

    요컨대
    일부 기능 실천을 채택하면 당신의 대상을 향한 생활을 더욱 가볍게 할 수 있다.만약 당신이 이러한 범용적이고 고도로 추상적이며 매우 안전한 코드를 좋아한다면, 대상을 대상으로 방해하지 않도록 함수식 프로그래밍을 계속 공부하세요.만약 당신이 나와 함께 이 길을 가고 싶다면, 마음대로 subscribe to my RSS feed 하세요. 보답으로, 나는 다시는 이렇게 긴 댓글을 쓰지 않을 것을 보증합니다.

    좋은 웹페이지 즐겨찾기