2011-02-25 3 views
20

내 Haskell 프로그램에서 getLine 함수를 사용하여 사용자가 지정한 값을 읽으 려합니다. 그런 다음 read 함수를 사용하여이 값을 문자열에서 적절한 하스켈 형식으로 변환하고 싶습니다. read 함수에 의해 발생 된 구문 분석 오류를 어떻게 catch하고 사용자에게 값을 다시 입력하도록 요청할 수 있습니까?하스켈의 읽기 함수에서 구문 분석 예외를 잡는 방법은 무엇입니까?

올바르게 작동하지 않는 IO 시스템으로 인한 오류가 아니기 때문에 이것이 "IO 오류"가 아니라고 생각합니까? 그것은 의미 론적 오류이므로 IO 오류 처리 메커니즘을 사용할 수 없습니까?

+2

곧 나오는 GHC 7.6에는'Text.Read.readEither :: Read a => String -> A String a'와'Text.Read.readMaybe :: Read a => String -> Maybe a'가있을 것입니다. – sdcvvc

답변

26

원하지 않습니다. 당신은 아마도 그런 식으로 대신 reads를 사용하려면 :

maybeRead = fmap fst . listToMaybe . reads 

(튜플의 두 번째 요소는 ""없는 경우도 나머지 문자열이 있다면 당신은, 즉, 밖으로 오류 할 수도 있지만)

그들은 그들 만이 때 비행합니다 :

대신 error 예외를 잡는 읽고 사용하려는 이유 이유매우 쉽게 잘못된 장소에서 그들을 잡으려고 시도 할 수 있기 때문에 순수 코드에서 예외는 악이다 강요 당하지 않고 전에. 그 위치를 찾는 것은 간단한 일이 아닙니다. 이것이 하스켈 프로그래머가 코드의 총계를 유지하려는 이유 중 하나입니다. 즉 종료 및 예외가 없습니다.

적절한 구문 분석 프레임 워크 (예 : parsec)와 haskeline을 살펴볼 수도 있습니다.

+8

'maybeRead'는 (다른 것들 중에서) hackage의'utility-ht' 및'applicative-extras' 패키지에 있으며, 기본에 포함시키기 위해 제안되었습니다. 나는 그 제안의 지위에 대해 확신하지 못한다. – luqui

+1

Text.Read – TheEnvironmentalist

8

이것은 @ barsoap의 답변에 대한 추가 정보입니다.

하스켈 예외는 순수 코드를 포함하여 어디에서 던질 수 있지만 IO 모나드에서만 잡힐 수 있습니다. 순수한 코드에 의해 던져진 예외를 잡으려면 순수한 코드를 평가하도록 IO 문에 catch 또는 try을 사용해야합니다. 그 아무것도 잡을 것 때문에

str2Int :: String -> Int -- shortcut so I don't need to add type annotations everywhere 
str2Int = read 

main = do 
    print (str2Int "3") -- ok 
    -- print (str2Int "a") -- raises exception 
    eVal <- try (print (str2Int "a")) :: IO (Either SomeException()) 
    case eVal of 
    Left e -> do -- couldn't parse input, try again 
    Right n -> do -- could parse the number, go ahead 

당신은 SomeException보다 더 구체적인 뭔가를 사용해야합니다. 위의 코드에서 tryread이 문자열을 구문 분석 할 수없는 경우 Left exception을 반환하지만 값을 인쇄하려고 할 때 IO 오류가 있거나 숫자가 잘못 될 수있는 경우 다른 문자가 있으면 Left exception을 반환합니다. (메모리 부족 등).

이제 순수 코드의 예외가 악의적 인 이유는 다음과 같습니다. 입출력 코드가 실제로 결과를 평가하도록 강요하지 않으면 어떻게 될까요? 당신이 이것을 실행하면

main2 = do 
    inputStr <- getLine 
    let data = [0,1,read inputStr] :: [Int] 
    eVal <- try (print (head data)) :: IO (Either SomeException()) 
    case eVal of 
    Right() -> do -- No exception thrown, so the user entered a number ?! 
    Left e -> do -- got an exception, probably couldn't read user input 

, 당신은 당신이 항상 상관없이 사용자가 입력 한 케이스 문의 Right 지점에서 끝 없음을 확인할 수 있습니다. 이것은 try에 전달 된 IO 작업이 입력 된 문자열 read을 시도하지 않기 때문입니다. 목록의 첫 번째 값인 data을 인쇄합니다.이 값은 상수이며 절대로 목록의 꼬리에 닿지 않습니다. 따라서 case 문의 첫 번째 분기에서 코더는 데이터를 평가한다고 생각하지만 그렇지 않다면 read은 여전히 ​​예외를 throw 할 수 있습니다.

read은 사용자가 입력 한 내용을 분석하지 않고 데이터를 직렬화 해제하기위한 것입니다. reads을 사용하거나 실제 파서 연결자 라이브러리로 전환하십시오.나는 uu-parsinglib을 좋아하지만, parsec, polyparse 등 많은 것들도 좋습니다. 어쨌든 긴 시간이 지나면 여분의 힘이 필요할 것입니다.

3

여기에만 공백을 후행 수 있습니다 개선 maybeRead을하지만 아무것도 :

import Data.Maybe 
import Data.Char 

maybeRead2 :: Read a => String -> Maybe a 
maybeRead2 = fmap fst . listToMaybe . filter (null . dropWhile isSpace . snd) . reads 
+0

'null에서 읽으십시오. dropWhile isSpace'는 단지'all isSpace'입니다, 안 그래요? –

7

이 당신의 기대를 만족 readMaybe 및 readEither있다. Text.Read 패키지에서이 기능을 찾을 수 있습니다.

+0

가장 좋은 답변은 IMHO! – Titou

관련 문제