2013-05-27 21 views
8

Vorrei abortire opzionalmente un'azione getChar. devo la seguente funzione:Come interrompere getChar in sicurezza?

getChar' :: (Char -> IO()) -> IO (IO()) 

In caso di abort <- getChar' callback, un carattere viene letto dallo standard input, salvo abort è chiamato prima di un carattere è disponibile. Se un carattere viene letto, viene chiamato callback.

Ho il seguente prototipo di attuazione:

import Control.Monad 
import Control.Concurrent 

getChar' :: (Char -> IO()) -> IO (IO()) 
getChar' callback = do 
    v <- newEmptyMVar 
    tid <- forkIO $ do 
     c <- getChar 
     b <- tryPutMVar v() 
     when b $ callback c 
    return $ do 
     b <- tryPutMVar v() 
     when b $ killThread tid 

Il problema è che killThread può interrompere il filo dopo aver letto il carattere, ma prima di mettere () in MVAR.

Non ho idea di come risolvere questo problema, è possibile del tutto con il pacchetto base? In caso contrario, hai visto una funzione simile implementata in altri pacchetti?

risposta

1

Penso che il modo più semplice per ottenere questo è quello di eseguire il proprio buffering. Ecco un semplice prototipo. Si presume che chiami launchIOThread esattamente una volta nel tuo programma. Non gestisce EOF o altre eccezioni IO, ma dovrebbe essere facile.

import Control.Concurrent 
import Control.Concurrent.STM 
import Data.Maybe 
import Control.Monad 

type Buffer = TVar (Maybe Char) 

launchIOThread :: IO Buffer 
launchIOThread = do 
    buf <- atomically $ newTVar Nothing 
    _ <- forkIO $ ioThread buf 
    return buf 

ioThread :: Buffer -> IO() 
ioThread buf = loop where 
    loop = 
    join $ atomically $ do 
     contents <- readTVar buf 
     if isJust contents -- no-one has taken the character yet 
     then retry -- relax 
     else return $ do 
      c <- getChar 
      atomically $ writeTVar buf (Just c) 
      loop 

getChar' :: Buffer -> (Char -> IO()) -> IO (IO()) 
getChar' buf callback = do 
    abortFlag <- atomically $ newTVar False 

    _ <- forkIO $ doGetChar abortFlag 

    return $ atomically $ writeTVar abortFlag True 

    where 
    doGetChar abortFlag = join $ atomically $ do 
     mbC <- readTVar buf 
     abort <- readTVar abortFlag 
     case mbC of 
     Just c -> 
      do writeTVar buf Nothing; return $ callback c 
     Nothing | abort -> return $ return() 
     _ -> retry 
+0

Grazie! STM e un buffer globale sono inevitabili qui? Cosa ne pensi, potrebbe 'getChar' essere definito in' IO' anche senza compromettere la sua semantica? Edit: Suppongo di sì perché 'IO' potrebbe avere uno stato globale. –

+0

Mi chiedo perché 'getChar ':: (Char -> IO()) -> IO (IO())' non sia in 'Prelude' o' System.IO', ha semantica semplice e sembra impossibile definire con costrutti esistenti (voglio dire senza il parametro 'Buffer'). –

+0

1. Sì, ci sono hack per creare variabili globali in Haskell. –

0

Quello che si vuole fare è usare i costrutti di gestione delle eccezioni in modo tale che, indipendentemente dalle eccezioni, l'MVar sia sempre lasciato in uno stato sicuro. In particolare, probabilmente si desidera withMVar.

+0

Il problema esiste anche se supponiamo che 'getChar' non sollevi eccezioni, quindi' withMVar' non aiuta. –