2014-10-18 15 views
7

Attualmente sto lavorando con i collegamenti Haskell a una libreria C HDF5. Come molte librerie C, questa utilizza molte indicazioni nelle sue chiamate di funzioni.Quali sono le convenzioni Haskell per la gestione di schemi di parentesi profondamente annidati?

I normali funzioni di "best practice" Haskell per ripartizione e liberando risorse c Seguire le bracket pattern, come alloca, withArray, ecc li utilizzano, spesso immettere diverse parentesi nidificate. Per esempio, ecco un piccolo estratto per gli attacchi HDF5:

selectHyperslab rID dName = withDataset rID dName $ \dID -> do 
    v <- withDataspace 10 $ \dstDS -> do 
    srcDS <- c'H5Dget_space dID 
    dat <- alloca3 (0, 1, 10) $ \(start, stride, count) -> do 
     err <- c'H5Sselect_hyperslab srcDS c'H5S_SELECT_SET start stride count nullPtr 
     -- do some work ... 
     return value 

alloca3 (a, b, c) action = 
    alloca $ \aP -> do 
    poke aP a 
    alloca $ \bP -> do 
     poke bP b 
     alloca $ \cP -> do 
     poke cP c 
     action (aP, bP, cP) 

Nel codice di cui sopra, le parentesi annidate sono funzioni staffa ho scritto withDataset, withDataspace, e alloca3, che ho scritto per evitare che la staffa di nidificazione di andare un altro 3 livelli in profondità nel codice. Per le librerie C con un sacco di chiamate di acquisizione delle risorse e gli argomenti di puntatore, codifica con le primitive di supporto standard in grado di ottenere ingestibile (che è il motivo per cui ho scritto alloca3 per ridurre la nidificazione.)

Quindi in generale, ci sono delle migliori pratiche o tecniche di codifica per aiutare a ridurre l'annidamento di parentesi quando è necessario allocare e deallocare molte risorse (come con le chiamate C)? L'unica alternativa che ho trovato è il trasformatore ResourceT, che dal tutorial sembra essere progettato per rendere possibile l'acquisizione/rilascio di risorse di interlacciamento e non per semplificare il modello di parentesi.

+0

Non sembra come tutte le allocazioni dipendono l'uno dall'altro, quindi scrivi prima tutti gli allocati, con lo stesso livello di indentazione, quindi il resto della tua funzione. A seconda della lunghezza, potresti anche scriverli sulla stessa riga: 'alloca $ \ a -> alloca $ \ b -> alloca $ \ c -> fai \ n ...' – user2407038

+2

La discussione su https: // www .reddit.com/r/haskell/commenti/s49kk/using_contt_to_please_the_eye/parla molto di idiomi per questo. – Carl

+0

Questo sembra anche un altro livello di monade attorno all ''alloca'. –

risposta

7

Ultimamente ero investigating this problem in Scala. Il pattern ricorrente è (a -> IO r) -> IO r, in cui una determinata funzione viene eseguita all'interno di un contesto di allocazione delle risorse dato un valore di tipo a. E questo è solo ContT r IO a, che è facilmente disponibile in Haskell. Così possiamo scrivere:

import Control.Monad 
import Control.Monad.Cont 
import Control.Monad.IO.Class 
import Control.Exception (bracket) 
import Foreign.Ptr (Ptr) 
import Foreign.Storable (Storable) 
import Foreign.Marshal.Alloc (alloca) 

allocaC :: Storable a => ContT r IO (Ptr a) 
allocaC = ContT alloca 

bracketC :: IO a -> (a -> IO b) -> ContT r IO a 
bracketC start end = ContT (bracket start end) 

bracketC_ :: IO a -> IO b -> ContT r IO a 
bracketC_ start end = ContT (bracket start (const end)) 

-- ...etc... 

-- | Example: 
main :: IO() 
main = flip runContT return $ do 
    bracketC_ (putStrLn "begin1") (putStrLn "end1") 
    bracketC_ (putStrLn "begin2") (putStrLn "end2") 
    liftIO $ putStrLn "..." 

monade di serie/funzioni applicative consentono di semplificare un sacco di codice, ad esempio:

allocAndPoke :: (Storable a) => a -> ContT r IO (Ptr a) 
allocAndPoke x = allocaC >>= \ptr -> liftIO (poke ptr x) >> return ptr 

-- With the monad alloca3 won't be probably needed, just as an example: 
alloca3C (a, b, c) = 
    (,,) <$> allocAndPoke a <*> allocAndPoke b <*> allocAndPoke c 

allocaManyC :: (Storable a) => [a] -> ContT r IO [Ptr a] 
allocaManyC = mapM allocAndPoke 
+0

Questo approccio è lo stesso della discussione reddit collegata da @Carl, che credo sia un buon segno. Tuttavia, non capisco perché 'alloca3' non è più necessario. Permette di mischiare e abbinare i tipi allocati nella tupla, mentre 'allocaManyC' alloca' Ptr's dello stesso tipo. – fluffynukeit

+0

@fluffynukeit Ovviamente può essere usato come una mano corta, il mio punto era che con la monade possiamo anche scrivere 'x <- allocAndPoke a; y <- allocAndPoke b; ... 'ecc. –

+0

sembra che questo concetto sia rappresentato anche nel pacchetto' managed': https://hackage.haskell.org/package/managed-1.0.0/docs/Control-Monad-Managed.html – ocramz

Problemi correlati