아마도 모나드 대 예외


답변:


56

사용 Maybe(또는 사촌 Either기본적으로 동일한 방식으로 작동하지만 대신에 임의의 값을 반환 할 수 있습니다 Nothing) 예외 약간 다른 용도로 사용됩니다. Java 용어로는 런타임 예외가 아닌 확인 된 예외가있는 것과 같습니다. 예상치 못한 오류가 아니라 처리해야 할 것으로 예상 되는 것을 나타냅니다 .

따라서 항목이 목록에 없을 가능성이 있기 때문에 같은 함수 indexOfMaybe값을 반환 합니다. 이것은 null유형 안전 방식을 사용하여 null케이스 를 처리해야한다는 점을 제외하고 함수에서 돌아 오는 것과 매우 유사 합니다 . Either오류 사례와 관련된 정보를 반환 할 수 있다는 점을 제외하고는 동일한 방식으로 작동하므로 실제로는 예외와 비슷합니다 Maybe.

그렇다면 Maybe/ Either접근법 의 장점은 무엇 입니까? 하나, 그것은 언어의 일류 시민입니다. Either예외를 던지는 것과 사용하는 함수를 비교해 봅시다 . 예외적 인 경우, 유일한 유일한 의지는 try...catch진술입니다. 이 Either기능을 위해 기존 조합기를 사용하여 흐름 제어를보다 명확하게 만들 수 있습니다. 다음은 몇 가지 예입니다.

먼저, 그렇지 않은 함수를 얻을 때까지 행에서 오류가 발생할 수있는 여러 함수를 시도한다고 가정 해 봅시다. 오류가 없으면 특별한 오류 메시지를 반환하려고합니다. 이것은 실제로 매우 유용한 패턴이지만를 사용하는 것은 끔찍한 고통 try...catch입니다. 행복하게도, Either단지 정상적인 값이므로 기존 함수를 사용하여 코드를 훨씬 명확하게 만들 수 있습니다.

firstThing <|> secondThing <|> throwError (SomeError "error message")

또 다른 예는 옵션 기능입니다. 쿼리를 최적화하는 기능을 포함하여 실행할 여러 기능이 있다고 가정 해 봅시다. 이것이 실패하면 다른 모든 것을 실행하기를 원합니다. 다음과 같은 코드를 작성할 수 있습니다.

do a <- getA
   b <- getB
   optional (optimize query)
   execute query a b

이 두 가지 경우 모두를 사용하는 것보다 명확하고 짧으며 try..catch더 중요하게 더 의미 론적입니다. 같은 기능을 사용 <|>하거나하는 것은 optional당신의 의도가 수 많이 사용하는 것보다 명확 try...catch항상 예외를 처리 할 수 있습니다.

또한 ! 와 같은 줄로 코드를 정리할 필요는 없습니다if a == Nothing then Nothing else ... . 치료 MaybeEither모나드의 요점은 이것을 피하는 것입니다. 전파 의미론을 바인드 함수로 인코딩하여 널 / 오류 검사를 무료로 얻을 수 있습니다. 명시 적으로 확인 해야하는 유일한 시간은 Nothing주어진 이외의 것을 반환하고 싶을 때뿐입니다. Nothing심지어 코드를 더 좋게 만드는 표준 라이브러리 함수가 많이 있습니다.

마지막으로, 또 다른 장점은 Maybe/ Either유형이 더 간단 하다는 것입니다 . 추가 키워드 또는 제어 구조를 사용하여 언어를 확장 할 필요는 없습니다. 모든 것이 단지 라이브러리 일뿐입니다. 그것들은 단지 정상적인 값이기 때문에 타입 시스템을 더 단순하게 만듭니다 .Java에서는 타입 (예 : 반환 타입)과 throws사용하지 않을 효과 (예 : 명령문) 를 구별해야합니다 Maybe. 또한 다른 사용자 정의 유형과 동일하게 작동하므로 언어에 특수한 오류 처리 코드가 없어도됩니다.

또 다른 승리 즉 Maybe/ Either펑터와 모나드는, 그들은 일반적으로 기존의 모나드 제어 흐름 기능 (있는 공정한 번호가)와, 활용할 수있는 수단, 다른 모나드와 함께 잘 재생합니다.

즉, 몇 가지주의 사항이 있습니다. 우선, 점검되지 않은 예외를 대체 Maybe하거나 Either대체 하지 마십시오 . 0으로 나누는 것과 같은 것을 처리하는 다른 방법이 필요할 때마다 모든 단일 부서가 Maybe값을 반환하는 것이 고통 스럽기 때문에 간단 합니다.

또 다른 문제는 여러 유형의 오류가 반환되는 것입니다 (이 경우에만 적용됨 Either). 예외를 사용하면 동일한 함수에서 다른 유형의 예외를 처리 할 수 ​​있습니다. 를 사용 Either하면 한 가지 유형 만 얻을 수 있습니다. 이는 하위 유형을 지정하거나 생성자로서 모든 다른 유형의 오류를 포함하는 ADT를 통해 극복 할 수 있습니다 (이 두 번째 방법은 Haskell에서 일반적으로 사용되는 방법 임).

아직도, 나는 더 간단하고 유연하기 때문에 Maybe/ Either접근법을 선호합니다 .


대부분 동의하지만 "선택적"예제는 의미가 없다고 생각합니다. 예외와 마찬가지로 그렇게 할 수있을 것 같습니다 : void Optional (Action act) {try {act (); } catch (Exception) {}}
Errorsatz

11
  1. 예외는 문제의 원인에 대한 자세한 정보를 전달할 수 있습니다. OpenFile()던질 수 FileNotFound또는 NoPermission또는 TooManyDescriptors등 없음이 정보를 전달하지 않습니다.
  2. 반환 값이없는 상황에서는 예외를 사용할 수 있습니다 (예 : 언어가있는 언어의 생성자 사용).
  3. 예외를 사용하면 많은 if None return None스타일 문 없이 정보를 스택에 쉽게 보낼 수 있습니다 .
  4. 예외 처리는 거의 항상 값을 반환하는 것보다 더 높은 성능 영향을 미칩니다.
  5. 가장 중요한 것은 예외와 어쩌면 모나드는 다른 목적을 가지고 있다는 것입니다. 예외는 문제를 나타내는 데 사용되지만 어쩌면 그렇지 않습니다.

    "간호사, 5 호실에 환자가 있다면 기다릴 수 있습니까?"

    • 아마 모나드 : "의사, 5 호실에는 환자가 없습니다."
    • 예외 : "의사, 5 호실은 없습니다!"

    ( "if"에 유의 하십시오-의사가 아마도 모나드를 기대하고 있음을 의미합니다 )


7
포인트 2와 3에 동의하지 않습니다. 특히, 2는 모나드를 사용하는 언어에서 실제로 문제를 일으키지 않습니다. 그 언어에는 구성 중에 실패를 처리해야 할 수수께끼를 생성하지 않는 규칙이 있기 때문입니다. 3과 관련하여 모나드가있는 언어 는 명시적인 검사 가 필요 없는 투명한 방식으로 모나드를 처리하기위한 적절한 구문을 가지고 있습니다 (예 : None값을 전파 할 수 있음). 요점 5는 단지 옳습니다… 문제는 다음과 같습니다. 어떤 상황이 명백하게 예외적입니까? 그 결과 …… 많지 않습니다 .
Konrad Rudolph

3
(2)와 관련하여 bind테스트를 None수행해도 구문 오버 헤드가 발생하지 않는 방식으로 작성할 수 있습니다 . 아주 간단한 예인 C #은 Nullable연산자를 적절히 오버로드합니다 . None유형을 사용할 때도 필요 하지 않습니다 . 물론 검사는 여전히 완료되지만 (유형 안전하지만) 무대 뒤에서 코드를 어지럽히 지 않습니다. (5)에 대한 귀하의 이의 제기에 대한 귀하의 이의 제기에도 동일하게 적용되지만 항상 적용되는 것은 아님에 동의합니다.
Konrad Rudolph

4
@Oak : (3)과 관련 Maybe하여 모나드 로 취급하는 요점은 전파를 None암시 적 으로 만드는 것 입니다. 즉 , Nonegiven 을 반환 None하려면 특별한 코드를 전혀 작성할 필요가 없습니다. 당신이 일치해야 할 유일한 시간은 당신이 뭔가를하고 싶은 경우입니다 None. 당신은 if None then None일종의 진술이 필요하지 않습니다 .
Tikhon Jelvis

5
@ 오크 : 사실이지만, 그것은 내 요점이 아닙니다. 무슨 말인지 당신이 얻을 것입니다 null정확히 (예를 들어 같은 확인 if Nothing then Nothing)에 대한 무료 때문에 Maybe모나드이다. 에 대한 bind ( >>=) 정의로 인코딩됩니다 Maybe.
Tikhon Jelvis

2
또한 (1)과 관련하여 다음 Either과 같이 동작하는 오류 정보 (예 :)를 전달할 수있는 모나드를 쉽게 작성할 수 있습니다 Maybe. 이 둘 사이의 전환은 실제로 Maybe특별한 경우 이기 때문에 실제로는 간단 Either합니다. (Haskell에서는으로 생각할 수 Maybe있습니다 Either ().)
Tikhon Jelvis

6

"아마도"는 예외를 대체하지 않습니다. 예외는 예외적 인 경우에 사용하기위한 것입니다 (예 : DB 연결을 열고 DB 서버는 존재하지 않지만). "아마도"는 유효한 값이 있거나 없을 수있는 상황을 모델링하기위한 것입니다. 키에 대한 사전에서 값을 얻는다고 가정하십시오. 키가 있거나 없을 수 있습니다. 이러한 결과에 대해 "예외적 인"것은 없습니다.


2
실제로, 나는 누락 된 키가 어떤 시점에서 물건이 끔찍하게 잘못되었다는 것을 의미하는 사전을 포함하는 꽤 많은 코드를 가지고 있습니다.

3
@delnan : 누락 된 값이 예외적 인 상태의 신호가 될 수는 없습니다. 실제로 변수가 유효한 값을 가질 수도 있고 없을 수도있는 상황을 모델링 할 수도 있습니다. C #에서 nullable 형식을 생각하십시오.
Nemanja Trifunovic

6

나는 Tikhon의 대답을 두 번째로 생각하지만 모든 사람들이 놓친 매우 중요한 실질적인 포인트가 있다고 생각합니다.

  1. 적어도 주류 언어로 된 예외 처리 메커니즘은 개별 스레드에 밀접하게 연결되어 있습니다.
  2. Either메커니즘은 스레드에 전혀 연결되지 않습니다.

오늘날 우리가 실제로보고있는 것은 많은 비동기식 프로그래밍 솔루션이 다양한 Either스타일의 오류 처리 방식을 채택하고 있다는 것입니다. 다음 링크 중 하나에 자세히 설명 된 Javascript promise를 고려하십시오 .

약속 개념을 사용하면 다음과 같은 비동기 코드를 작성할 수 있습니다 (마지막 링크에서 가져옴).

var greetingPromise = sayHello();
greetingPromise
    .then(addExclamation)
    .then(function (greeting) {
        console.log(greeting);    // 'hello world!!!!’
    }, function(error) {
        console.error('uh oh: ', error);   // 'uh oh: something bad happened’
    });

기본적으로 약속은 다음과 같은 객체입니다.

  1. 아직 완료되었거나 완료되지 않은 비동기 계산의 결과를 나타냅니다.
  2. 결과에 대해 수행 할 추가 작업을 연결하여 결과를 사용할 수있을 때 트리거되며 결과를 약속으로 사용할 수 있습니다.
  3. 약속의 계산이 실패 할 경우 호출되는 실패 처리기를 연결할 수 있습니다. 핸들러가 없으면 오류가 체인의 이후 핸들러로 전파됩니다.

기본적으로 계산이 여러 스레드에서 발생할 때 언어의 기본 예외 지원이 작동하지 않기 때문에 약속 구현은 오류 처리 메커니즘을 제공해야하며 이는 Haskell의 Maybe/ Either유형 과 유사한 모나드로 판명됩니다 .


스레드와는 아무런 관련이 없습니다. 브라우저의 JavaScript는 항상 여러 스레드가 아닌 하나의 스레드에서 실행됩니다. 그러나 나중에 함수가 언제 호출되는지 알지 못하므로 여전히 예외를 사용할 수 없습니다. 비동기가 자동으로 스레드가 관여하는 것은 아닙니다. 이것이 예외로 작업 할 수없는 이유이기도합니다. 함수를 호출하고 즉시 실행되는 경우에만 예외를 가져올 수 있습니다. 그러나 비동기의 전체 목적은 미래에 자주 실행되는 것이 아니라 즉시 다른 작업이 완료 될 때 실행되는 것입니다. 따라서 예외를 사용할 수 없습니다.
David Raab

1

Haskell 타입 시스템은 사용자에게의 가능성을 인정해야 Nothing하지만, 프로그래밍 언어는 종종 예외가 발생하지 않아도됩니다. 즉, 컴파일 타임에 사용자가 오류를 확인했음을 알 수 있습니다.


1
Java에는 확인 된 예외가 있습니다. 그리고 사람들은 종종 그들조차도 심하게 대했습니다.
블라디미르

1
@ DairT'arg ButJava는 IO 오류 (예 :)를 검사해야하지만 NPE는 검사하지 않습니다. Haskell에서는 다른 방법입니다. null에 해당하는 (아마도)는 확인되지만 IO 오류는 단순히 (확인되지 ​​않은) 예외를 throw합니다. 차이가 얼마나 큰지 잘 모르겠지만 Haskellers가 어쩌면에 대해 불평한다고 들리지 않으며 많은 사람들이 NPE를 확인하기를 원한다고 생각합니다.

1
@delnan 사람들은 NPE를 확인하기를 원하십니까? 그들은 화를 내야합니다. 그리고 나는 그것을 의미합니다. NPE를 검사하는 것은 처음에는 (Java에는없는 null이 아닌 참조를 사용하여) 피하는 방법이있는 경우에만 의미가 있습니다 .
Konrad Rudolph

5
@KonradRudolph 예, 사람들은 아마도 throws NPE모든 단일 서명과 catch(...) {throw ...} 모든 단일 메소드 본문 에 추가해야한다는 의미에서 확인하기를 원하지 않을 것입니다 . 그러나 나는 어쩌면와 같은 의미로 검사 시장이 있다고 생각합니다 : null 시스템은 선택 사항이며 유형 시스템에서 추적됩니다.

1
@delnan Ah, 동의합니다.
Konrad Rudolph

0

아마도 모나드는 기본적으로 대부분의 주류 언어에서 "널 의미 오류"확인 (널을 확인해야하는 경우 제외)과 동일하며 거의 동일한 장점과 단점이 있습니다.


8
글쎄, 올바르게 사용될 때 정적으로 유형을 검사 할 수 있기 때문에 동일한 단점 이 없습니다 . 모나드를 사용할 때 널 포인터 예외에 해당하는 것은 없습니다 (다시 올바르게 사용한다고 가정).
Konrad Rudolph

과연. 어떻게해야한다고 생각하십니까?
Telastyn

@Konrad 아마도 Maybe에 값을 사용하기 위해서는 None을 체크 해야 하기 때문입니다. (물론 많은 검사를 자동화 할 수는 있지만). 다른 언어는 그런 종류의 매뉴얼을 유지합니다 (주로 철학적 이유로).
Donal Fellows

2
@Donal 예. 그러나 모나드를 구현하는 대부분의 언어는 적절한 구문 설탕을 제공하므로이 검사는 종종 완전히 숨겨 질 수 있습니다. 예를 들어을 확인 하지 않고도 두 개의 Maybe숫자를 추가 할 수 있으며 결과는 다시 한 번 선택적인 값입니다. a + b None
Konrad Rudolph

2
nullable 형식과의 비교는 Maybe type 에 적용되지만 Maybe모나드로 사용 하면 구문을 추가 하여 null-ish 논리를 훨씬 더 우아하게 표현할 수 있습니다.
tdammers

0

예외 처리는 팩토링 및 테스트에 큰 고통이 될 수 있습니다. 나는 파이썬이 엄격한 "try ... catch"블록없이 예외를 잡을 수있는 멋진 "with"구문을 제공한다는 것을 알고있다. 그러나 Java에서 예를 들어, catch catch 블록은 크고, 상용구이거나, 장황하거나 매우 장황하며 분해하기가 어렵습니다. 또한 Java는 확인 된 예외와 확인되지 않은 예외에 대한 모든 노이즈를 추가합니다.

대신 모나드가 예외를 잡아서 처리 이상이 아닌 모나드 공간의 속성으로 취급하면 던지거나 잡는 것에 관계없이 해당 공간에 바인딩하는 함수를 자유롭게 혼합하고 일치시킬 수 있습니다.

그래도 모나드가 예외가 발생할 수있는 상황 (예 : null 체크를 Maybe로 밀어 넣기)을 방지하면 더 좋습니다. 만약 ... 그런 다음 시도하는 것보다 훨씬, 팩터링하고 테스트하기가 훨씬 쉽습니다.

내가 본 것에서 Go는 각 함수가 반환 (응답, 오류)하도록 지정하여 비슷한 접근 방식을 취하고 있습니다. 이는 핵심 응답 유형이 오류 표시로 장식 된 모나드 공간으로 함수를 "리프팅"하는 것과 같으며, 예외적으로 던지기 및 잡기 예외를 효과적으로 처리합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.