2014-05-04 29 views
5

Bana öyle geliyor ki Haskell'deki istisnalar sadece atıldıktan hemen sonra yakalanabiliyor ve Java veya Python'da olduğu gibi propaganda edilmiyor. Bu gösteren kısa bir örnek aşağıda verilmiştir:Haskell'deki İstisnaları Yakalama

{-# LANGUAGE DeriveDataTypeable #-} 

import System.IO 
import Control.Monad 
import Control.Exception 
import Data.Typeable 

data MyException = NoParseException String deriving (Show, Typeable) 
instance Exception MyException 

-- Prompt consists of two functions: 
-- The first converts an output paramter to String being printed to the screen. 
-- The second parses user's input. 
data Prompt o i = Prompt (o -> String) (String -> i) 

-- runPrompt accepts a Prompt and an output parameter. It converts the latter 
-- to an output string using the first function passed in Prompt, then runs 
-- getline and returns user's input parsed with the second function passed 
-- in Prompt. 
runPrompt :: Prompt o i -> o -> IO i 
runPrompt (Prompt ofun ifun) o = do 
     putStr (ofun o) 
     hFlush stdout 
     liftM ifun getLine 

myPrompt = Prompt (const "> ") (\s -> if s == "" 
    then throw $ NoParseException s 
    else s) 

handleEx :: MyException -> IO String 
handleEx (NoParseException s) = return ("Illegal string: " ++ s) 

main = catch (runPrompt myPrompt()) handleEx >>= putStrLn 

programını çalıştırdıktan sonra, sadece bastığınızda hiçbir şey yazmak whithout, görmek gerekiyordu [Enter]: çıktıda Illegal string:. Bunun yerine prog: NoParseException "" görünür. Şimdi, Prompt tipi ve runPrompt işlevinin, modülün dışındaki ortak kitaplıkta tanımlandığını ve İstisnayı yapıcıya iletilen işlevlerdeki özel durumu işlemek üzere değiştirilemeyeceğini varsayalım. runPrompt'u değiştirmeden özel durumu nasıl ele alabilirim?

İstisna işleme işlevini bu şekilde enjekte etmek için üçüncü alanı Prompt'a eklemeyi düşündüm, ancak bu bana çirkin görünüyor. Daha iyi bir seçim var mı?

cevap

10

Yapmakta olduğunuz sorun, özel kodunuzu salt kod içine attığınız içindir: throw türü Exception e => e -> a şeklindedir. Saf koddaki istisnalar kesin olmayan ve do not guarantee ordering with respect to IO operations'dur. Yani catch, throw numaralı saflığı görmez. Bunu düzeltmek için "diğer IO işlemleriyle ilgili değerlendirme siparişi vermek için kullanılabilecek" (dokümanlardan) evaluate :: a -> IO a'u kullanabilirsiniz. evaluate geri dönüş gibidir, ancak aynı zamanda değerlendirmeyi zorlar. Bu nedenle, IO eylemi sırasında ifun'u değerlendirmek üzereile liftM ifun getLine değiştirebilirsiniz. (liftM f mx = return . f =<< mx hatırlayın, bu nedenle bu aynıdır ancak değerlendirmenin üzerinde daha fazla kontrol ile.) Ve başka bir şey değiştirmeden, doğru cevabı alırsınız: Gerçekten

*Main> :main 
> 
Illegal string: 

olsa da, bu değil istisnaları kullanırım. İnsanlar, Haskell kodunda ve özellikle salt kodda bu kadar istisnalar kullanmazlar.

runPrompt :: Prompt o i -> o -> IO (Either MyException i) 
runPrompt (Prompt ofun ifun) o = do putStr $ ofun o 
            hFlush stdout 
            ifun `liftM` getLine 

Biz çimdik ediyorum: Sonra

data Prompt o i = Prompt (o -> String) (String -> Either MyException i) 

bir Either sadece dönecekti istemi çalışan: Giriş işlevin potansiyel başarısızlık türü kodlanmış böylece ben daha ziyade Prompt yazardım myPromptthrow yerine Left ve Right kullanımı:

myPrompt :: Prompt a String 
myPrompt = Prompt (const "> ") $ \s -> 
      if null s 
       then Left $ NoParseException s 
       else Right s 

Ve sonra istisna işlemek için either :: (a -> c) -> (b -> c) -> Either a b -> c kullanıyoruz.

handleEx :: MyException -> IO String 
handleEx (NoParseException s) = return $ "Illegal string: " ++ s 

main :: IO() 
main = putStrLn =<< either handleEx return =<< runPrompt myPrompt() 

(Ek ilgisiz, not:. Ona, burada bazı üslup değişiklikler yapmış fark edeceksiniz söyleyebilirim tek gerçekten önemlidir null s değil s == "" kullanmaktır.Biz çünkü biz burada evaluate kullanmak gerekmez

runPromptException :: Prompt o i -> o -> IO i 
runPromptException p o = either throwIO return =<< runPrompt p o 

: Eğer gerçekten eski davranış geri üst düzeyinde istiyorsanız)

, bir istisna olarak Left davayı atar runPromptException :: Prompt o i -> o -> IO i yazabilir IO hesaplamaları içinde kesin istisnalar atmak için throwIO kullanarak yeniden kullanın. Bununla, eski main işleviniz iyi çalışacaktır.

+0

Adamım, şimdi gecenin geri kalanında konuşmam var. Çok teşekkürler. :) – Sventimir

2

myPrompt türüne bakarsanız, IO numaralı telefon numarasına göre Prompt o String olduğunu görürsünüz. En küçük düzeltme için: daha uygun olabilir rağmen

{-# LANGUAGE DeriveDataTypeable #-} 

import System.IO 
import Control.Monad 
import Control.Exception 
import Data.Typeable 

data MyException = NoParseException String deriving (Show, Typeable) 
instance Exception MyException 

-- Prompt consists of two functions: 
-- The first converts an output paramter to String being printed to the screen. 
-- The second parses user's input. 
data Prompt o i = Prompt (o -> String) (String ->IO i) 

-- runPrompt accepts a Prompt and an output parameter. It converts the latter 
-- to an output string using the first function passed in Prompt, then runs 
-- getline and returns user's input parsed with the second function passed 
-- in Prompt. 
runPrompt :: Prompt o i -> o -> IO i 
runPrompt (Prompt ofun ifun) o = do 
     putStr (ofun o) 
     hFlush stdout 
     getLine >>= ifun 

myPrompt :: Prompt o String 
myPrompt = Prompt (const "> ") (\s -> if s == "" 
    then throw $ NoParseException s 
    else return s) 

handleEx :: MyException -> IO String 
handleEx (NoParseException s) = return ("Illegal string: " ++ s) 

main = catch (runPrompt myPrompt()) handleEx >>= putStrLn

o Prompt o i e = Prompt (o -> String) (String -> Either i e) olmak.

+0

Ah, sanırım şimdi anlıyorum. Böylece istisnalar sadece 'IO a' geri arama zincirindeki sonraki işlevler kadar yayılır mı, düzeltmekteyim? İstihbarat safında işlevler tutmak istedim, ancak şimdi çalışıyor. Ayrıştırma her zaman hataya eğilimlidir, bu yüzden belki de IO ile işaretlemek akıllıca olacaktır. Ya da saflığı korumak için ya orada koyun. Çok teşekkür ederim. :) – Sventimir