2010-01-26 1 views
6

Haskell 함수가 여러 가지 조건을 검사하고 오류시 오류 메시지를 반환하는 좋은 방법은 무엇입니까?Haskell 방법 abort가있는 여러 항목의 오류 검사

파이썬 또는 유사한 언어에서

, 그것은 간단합니다 :

if failure_1: 
    return "test1 failed" 
if failure_2: 
    return "test2 failed" 
... 
if failure_n: 
    return "testn failed" 
do_computation 

어떻게 if 문 하스켈에서/임의의 중첩 된 경우없이이 작업을 수행합니까?

편집 : 일부 테스트 조건에는 IO 모나드에 테스트 결과를 저장하는 IO가 필요할 수 있습니다. 나는 이것이 많은 해결책에 꼬임을 넣었다고 생각한다.

답변

12

그래서 you'r e가 IO 안에 갇혀 있고 많은 수의 조건을 확인하려면 if을 중첩하지 않아야합니다. 나는 하스켈에서 좀 더 일반적인 문제 해결에 대한 답변을 통해 나를 용서 해주길 바랍니다.

어떻게 행동해야하는지 추상을 고려하십시오. 프로그램이 프로그램은 기능의 나머지 부분을 삭제하고 오류 메시지를 반환하는 경우에는 기능

  • 실패의 나머지 부분을 실행하는 경우

    • 성공 : 조건을 선택하면 두 가지 결과 중 하나가 있습니다.

    여러 조건을 검사하는 것은 재귀 적으로 볼 수 있습니다. "함수의 나머지 부분"을 실행할 때마다 결과를 반환하는 마지막 단계에 도달 할 때까지 다음 조건을 찾습니다. 이제 문제를 해결하기위한 첫 걸음으로, 그 구조를 사용하여 일들을 나누어 보겠습니다. 기본적으로, 우리는 여러 조건을 여러 조건부 함수로 구성 할 수있는 조각으로 바꾸기를 원합니다. 우리는이 조각들의 본질에 대해 무엇을 결론 지을 수 있습니까?

    1) 각 조각은 두 가지 유형 중 하나를 반환 할 수 있습니다. 오류 메시지 또는 다음 단계의 결과.

    2) 각 조각은 다음 단계를 실행할지 여부를 결정해야하므로 단계를 결합 할 때 다음 단계를 나타내는 함수를 인수로 제공해야합니다.

    3) 각 조각에는 다음 단계가 주어지기 때문에 균일 한 구조를 유지하려면 최종 무조건 단계를 조건부 단계와 동일한 것으로 변환해야합니다.

    첫 번째 요구 사항은 Google 검색 결과에 Either String a과 같은 유형이 필요하다는 것을 분명히 암시합니다. 이제 두 번째 요구 사항에 맞는 결합 함수와 세 번째 요구 사항에 맞는 래핑 함수가 필요합니다. 또한 단계를 결합 할 때 이전 단계의 데이터에 액세스 할 수 있습니다 (예 : 서로 다른 두 입력의 유효성을 검사 한 다음 두 입력이 같은지 확인). 각 단계에서 이전 단계의 결과를 인수로 가져와야합니다.

    각 단계 유형을 약어로 표기하면 다른 기능에는 어떤 유형이 있습니까?

    이제 형식 서명이 이상하게 보입니다. 그렇지 않습니까?

    "오류 메시지와 함께 일찍 실패 할 수있는 계산을 실행하는"일반적인 전략은 실제로 모나드 구현에 적합합니다. 실제로 mtl package에는 이미 하나가 있습니다. 더 중요한 것은이 경우에 모나드 이 있습니다. 즉, 오류 모나드 구조를 다른 모나드에 추가 할 수 있습니다 (예 : IO).

    그래서, 우리가 모듈을 가져 따뜻한 퍼지 ErrorTIO을 마무리하는 타입의 동의어를 만들고, 멀리 갈 수 :

    import Control.Monad.Error 
    
    type EIO a = ErrorT String IO a 
    
    assert pred err = if pred then return() else throwError err 
    
    askUser prompt = do 
        liftIO $ putStr prompt 
        liftIO getLine 
    
    main :: IO (Either String()) 
    main = runErrorT test 
    
    test :: EIO() 
    test = do 
        x1 <- askUser "Please enter anything but the number 5: " 
        assert (x1 /= "5") "Entered 5" 
        x2 <- askUser "Please enter a capital letter Z: " 
        assert (x2 == "Z") "Didn't enter Z" 
        x3 <- askUser "Please enter the same thing you entered for the first question: " 
        assert (x3 == x1) $ "Didn't enter " ++ x1 
        return() -- superfluous, here to make the final result more explicit 
    

    당신이 기대하는 것처럼, test을 실행 한 결과, 성공의 경우 Right()이거나 실패한 경우 Left String입니다. 여기서 String은 적절한 메시지입니다. assert이 실패를 리턴하면 다음 조치 중 하나도 수행되지 않습니다.

    IO 작업의 결과를 테스트하는 경우 과 유사한 도우미 함수를 쓰는 것이 가장 쉽습니다.이 방법은 대신 IO Bool 또는 다른 방법을 사용합니다.

    또한 liftIO의 사용이 EIO의 값으로 IO 작업을 변환하고, runErrorTEIO 조치를 실행하고 전체 결과와 Either String a 값을 반환 할 수 있습니다. 더 자세한 정보가 필요하면 monad transformers을 읽어보십시오.

  • +0

    아주 좋은 대답입니다! –

    +0

    오류 모나드를 좋아합니다! 또한 컴파일러에서 가짜 오류 메시지를 스켈링하기에 좋습니다 .... +1 –

    +1

    @Norman Ramsey : 그렇습니다! 하스켈과의 초기 모험에서 '오류'로 '어느 쪽이든'을 사용하는 도서관을 만났을 때 나는 가치를 전달하기 위해 '권리'에서 추출하는 것이 헛되지 않았다. 그래서 (hightsight에서)'fmap'과'(>> =)'와 거의 같은 함수를 작성했습니다. 같은 시간에 모나드 구조를 인식하는 이해를 얻었을 때, 나는 MonadError가 항상 존재한다는 것을 발견했고, 나의 원유 재발 명에 대해 다소 어리 석다는 것을 알게되었다."공상적인"모나드를 둘러싼 모든 소동에서 모나드 '우아함'과 '어쩌면'이 잊혀 질 것 같습니다 ... –

    5

    일반적으로 패턴 매칭은 문 및 오류 검사 조건도 예외없는 경우를 많이보다 갈 수있는 더 나은 방법은 다음과 같습니다

    func :: [Int] -> Either String Int 
    func [] = Left "Empty lists are bad" 
    func [x] 
        | x < 0 = Left "Negative? Really?" 
        | odd x = Left "Try an even number" 
    func xs = Right (length xs) 
    

    이 기능은 오류 메시지 나 매개 변수의 길이 중 하나를 반환합니다. 오류 사례는 먼저 시도되고 일치하지 않는 경우에만 마지막 사례가 실행됩니다.

    +0

    감사합니다. 좋은 생각입니다. 원래 언급하지 않은 것은 테스트 조건 중 일부가 IO를 필요로한다는 것입니다. 패턴 가드에서 IO condtions를 확인하는 방법이 있습니까? – me2

    -1

    사용 경비원 :

    f z 
        | failure_1 = ... 
        | failure_2 = ... 
        | failure_3 = ... 
        | failure_4 = ... 
        | otherwise = do_computation 
    
    1

    난 당신이 가드에서 IO를 사용할 수 있다고 생각하지 않습니다.

    myIoAction filename = foldr ($) [noFile, fileTooLarge, notOnlyFile] do_computation 
        where do_computation 
          = do {- do something -} 
           return (Right answer) 
         noFile success 
          = do {- find out whether file exists -} 
           if {- file exists -} then success else return (Left "no file!") 
         fileTooLarge success 
          = do {- find out size of file -} 
           if maxFileSize < fileSize then return (Left "file too large") else success 
         -- etc 
    
    1

    이 비록 당신의 other question이 하나에 의도 수정, 당신은 스위치/case 문 같은 것을 만들 수로

    select :: Monad m => [(m Bool, m a)] -> m a -> m a 
    select fallback [] = fallback 
    select fallback ((check, action) : others) = do 
        ok <- check 
        if ok then action else select fallback others 
    
    newfile :: FilePath -> IO Bool 
    newfile x = select 
        (return True) 
        [ (return $ length x <= 0, return False) 
        , (doesFileExist x,  return False) ] 
    

    촬영 :

    대신에, 당신이 뭔가를 할 수 있습니다 특별한 사람은 쉽게 쓸 수있다.

    newFile [] = return False 
    newFile fn = fmap not $ doesFileExist fn