2014-10-18 31 views
7

我目前正在與Haskell綁定到HDF5 C庫。像許多C庫一樣,這個庫在其函數調用中使用了許多指針。管理深度嵌套支架模式的好Haskell約定是什麼?

通常的「最佳實踐」爲Haskell的分配和釋放C資金的功能按照bracket pattern,像allocawithArray等。在使用它們時,我經常進入幾個嵌套的括號內。例如,這裏是一個小的摘錄爲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) 

在上面的代碼中,嵌套括號托架功能我寫withDatasetwithDataspacealloca3,其中我寫,以防止支架從去另一個嵌套代碼深3級。對於具有大量資源獲取調用和指針參數的C庫,使用標準支架基元進行編碼會變得難以管理(這就是爲什麼我編寫了alloca3以減少嵌套。)

因此,一般來說,是否有任何最佳實踐或編碼技術在需要分配和釋放許多資源(如使用C調用)時幫助減少括號的嵌套?我找到的唯一選擇是ResourceT變壓器,它在教程中看起來像是設計用於使交錯資源獲取/釋放成爲可能,而不是簡化托架模式。

+0

它似乎並不像任何allocs互相依賴,所以先寫下所有的分配,使用相同的縮進級別,然後是其他的函數。根據長度的不同,你甚至可以將它們寫在同一行上:'alloca $ \ a - > alloca $ \ b - > alloca $ \ c - > do \ n ...' – user2407038 2014-10-18 04:17:47

+2

.reddit.com/r/haskell/comments/s49kk/using_contt_to_please_the_eye /會談很多關於成語的事情。 – Carl 2014-10-18 04:58:28

+0

這也看起來像'alloca'的另一個monad級別。 – 2014-10-18 08:47:59

回答

7

最近我是investigating this problem in Scala。重複模式是(a -> IO r) -> IO r,其中給定函數在給定值a的值的某個資源分配上下文中執行。這只是ContT r IO a,這在Haskell中很容易獲得。因此,我們可以這樣寫:

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 "..." 

標準單子/合用的功能,讓您可以簡化很多代碼,例如:

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

這種方法看起來與@Carl鏈接的reddit討論相同,這是我認爲的一個好兆頭。但是,我不明白爲什麼'alloca3'不再需要。它允許你在元組中混合和匹配分配的類型,而'allocaManyC'則分配相同類型的'Ptr's。 – fluffynukeit 2014-10-18 15:22:53

+0

@fluffynukeit當然,它可以作爲一個簡短的手,我的觀點是,與monad,我們可以寫'x < - allocAndPoke a; y < - allocAndPoke b; ...'等 – 2014-10-18 15:52:49

+0

這個概念似乎也代表'managed'包:https://hackage.haskell.org/package/managed-1.0.0/docs/Control-Monad-Managed.html – ocramz 2015-09-14 14:17:54