API design question

43 views
Skip to first unread message

Bardur Arantsson

unread,
Jan 19, 2017, 2:11:20 PM1/19/17
to haskel...@googlegroups.com
Hi all,

I'm writing an API for a little database library which has -- until now
-- used io-streams, and I was wondering how one would design the query API.

In particular, I have a function similar to:

query' :: Text -> [SqlValue] -> (Maybe Int -> InputStream [SqlValue]
-> IO a) -> IO a
query' sql params callback = do
...

(I'm actually really using MonadIO, etc., but that shouldn't matter much
for the question -- I'll have to use SafeT from pipes-safe to ensure
proper transaction/connection handling, but I don't think that'll change
things too much.)

The idea is that the user-provided callback receives a "row count"
(Maybe Int) and an InputStream where it can read as much as it wants
(until EOS, obviously). Once the callback terminates, the transaction
commits and the returned value is returned from query'

My question is: How would this API look in the pipes world? The obvious
candidate would be something like

query' :: Text -> [SqlValue] -> Producer' [SqlValue] m r

but that precludes the "row count". Another option might be

query' :: Text -> [SqlValue] -> (Maybe Int -> Consumer' [SqlValue] m
r) -> X

... but I'm unsure whether that even makes sense and what I'd want X to
be. Any opinions?

Daniel Díaz

unread,
Jan 20, 2017, 10:35:38 AM1/20/17
to Haskell Pipes
I would keep the API similar to the io-streams, by passing a function that consumes a Producer

    query' :: Text -> [SqlValue] -> (Maybe Int -> Producer [SqlValue] -> IO a) -> IO a 

This makes resource handling easier. It would also let you pass pipes parsers (which can be easily turned into producer-consuming functions). But perhaps "parsing" the sequence of tuples of a SQL query doesn't make a lot of sense.

Another option would be to forgo pipes and just use Folds from the "foldl" package. Perhaps monadic Folds over ExceptT that would let you "exit early".

Gabriel Gonzalez

unread,
Jan 22, 2017, 11:56:09 AM1/22/17
to haskell-pipes
The only thing I would add to Daniel's answer is that you can use the `managed` library to simplify the type a little bit to:

    query' :: Text -> [SqlValue] -> Managed (Maybe Int, Producer [SqlValue] IO ())

Also, since you aren't using the return value of the `Producer`, you can simplify it even further by using `ListT`:

    query' :: Text -> [SqlValue] -> Managed (Maybe Int, ListT IO [SqlValue])

It's not clear to me, though, why each read from the database returns a list of `SqlValue`s, though



--
You received this message because you are subscribed to the Google Groups "Haskell Pipes" group.
To unsubscribe from this group and stop receiving emails from it, send an email to haskell-pipes+unsubscribe@googlegroups.com.
To post to this group, send email to haskel...@googlegroups.com.

Bardur Arantsson

unread,
Jan 23, 2017, 11:16:53 AM1/23/17
to haskel...@googlegroups.com
On 2017-01-22 17:56, Gabriel Gonzalez wrote:
> The only thing I would add to Daniel's answer is that you can use the
> `managed` library to simplify the type a little bit to:
>
> query' :: Text -> [SqlValue] -> Managed (Maybe Int, Producer
> [SqlValue] IO ())
>

Ah, thanks, I'll have a look at managed too.

> Also, since you aren't using the return value of the `Producer`, you can
> simplify it even further by using `ListT`:
>
> query' :: Text -> [SqlValue] -> Managed (Maybe Int, ListT IO [SqlValue])
>
> It's not clear to me, though, why each read from the database returns a
> list of `SqlValue`s, though

Oh, that's just because SqlValue is a the value in a single column, so
[SqlValue] is the full row. (I have no need for anything more fancy to
map rows to data structures.)

Thanks for the suggestions Gabriel and Daniel.

Regards,

Reply all
Reply to author
Forward
0 new messages