2

I have to admit that I am still not "there" yet when it comes to working efficiently with monads, so please forgive me if this is an easy question. I also have to apologize for not supplying working code, as this questions more related to a concept than an actual implementation that I am currently working on.

I'm working against an SQLite(3) database, and of course would like to send queries to it and get results back. Being already in IO, the fetchAllRows function returns an [[SqlValue]] which needs to be converted. With SQLite being very liberal in terms of what is text and what is floating point values (and Haskell not being liberal at all when it comes to types), safe conversion using safeFromSql seems appropriate. Now, if you manage to do all this in one function you would end up with that function being

myfunc :: String -> [SqlValue] -> IO [[ Either ConvertError a]]

or something like that, right? It seems to me that working with that structures of nested monads may be common enough (and cumbersome enough) for there to be a standard way of making it easier to work with that I am not aware of?

Fredrik Karlsson
  • 485
  • 8
  • 21
  • You should probably look into monad transformers, for example https://hackage.haskell.org/package/either/docs/Control-Monad-Trans-Either.html for your specific example. – kini Aug 25 '14 at 08:50
  • 1
    Can you give a more specific example of what you're trying to do? Sure there are pre-defined helpers for all kinds of monad-related tasks, but not for everything. It may be entirely reasonable to have an `IO [[Either ConvertError a]]` result; I wouldn't really treat such a thing as a nested monad, it's simply an `IO` action that returns a data structure. – leftaroundabout Aug 25 '14 at 09:00
  • Completely offtopic but you may be interested in [persistent](http://www.yesodweb.com/book/persistent) if you haven't already heard about it. – Sibi Aug 25 '14 at 14:02
  • Ok, more specifically, I would like to construct a puré function `myfunc2 :: a -> a` that I could apply to the returned `IO [[ Either ConvertError a]]`, but that means getting into the first list, the second list, handling the Either and then possibly apply `myfunc2` to the `a` in there. I don't see yet how that is usually done. What is standard practise here? – Fredrik Karlsson Aug 29 '14 at 18:08

1 Answers1

0

The issue is, it seems, only solved by some specific functions, and then most clearly in do syntax. The functions below solve the issue within the direct-sqlite3 package access to SQLite database (and also inserts a REGEXP handler).

import Text.Regex.Base.RegexLike
import qualified Text.Regex.PCRE.ByteString as PCRE
import qualified Data.ByteString as BS
import Data.Text (pack,Text,append)
import Data.Text.Encoding (encodeUtf8)
import Data.Int (Int64)
import Database.SQLite3

pcreRegex :: BS.ByteString -> BS.ByteString -> IO Int64
pcreRegex reg b = do
    reC <- pcreCompile reg
    re <- case reC of
        (Right r) -> return r
        (Left (off,e) ) -> fail e
    reE <- PCRE.execute re b
    case reE of
        (Right (Just _)) -> return (1 :: Int64)
        (Right (Nothing)) -> return (0 :: Int64)
        (Left (c,e)) -> fail e -- Not a good idea maybe, but I have no use for error messages.
    where pcreCompile = PCRE.compile defaultCompOpt defaultExecOpt



sqlRegexp :: FuncContext -> FuncArgs -> IO ()
sqlRegexp ctx args = do
    r <- fmap encodeUtf8 $ funcArgText args 0
    t <- fmap encodeUtf8 $ funcArgText args 1
    res <- pcreRegex r t
    funcResultInt64 ctx res 

getRows :: Statement -> [Maybe ColumnType] -> [[SQLData]] -> IO [[SQLData]]
getRows stmt coltypes acc = do
  r <- step stmt
  case r of
    Done -> return acc
    Row -> do
      out <- typedColumns stmt coltypes
      getRows stmt coltypes (out:acc)


runQuery q args columntypes dbFile = do
    conn <- open $ pack dbFile
    createFunction conn "regexp" (Just 2) True sqlRegexp
    statement <- prepare conn q
    bind statement args
    res <- fmap reverse $ getRows statement (fmap Just columntypes) [[]]
    Database.SQLite3.finalize statement
    deleteFunction conn "regexp" (Just 2) 
    close conn
    return $ res

Hope this helps someone out here.

Fredrik Karlsson
  • 485
  • 8
  • 21