Sorry for asking as potentially silly question, but returning to Haskell to do some conversion from one database package to a different one, I find myself a bit puzzled about how to do this properly.
In the Database.SQLite3
module, there is an execWithCallback
with type
execWithCallback :: Database -> Text -> ExecCallback -> IO ()
Now, the callback is defined as
type ExecCallback = ColumnCount -> [Text]-> [Maybe Text] -> IO ()
that is, a function with type ExecCallback
My silly test code compiles and runs correctly:
{-# LANGUAGE OverloadedStrings #-}
import Database.SQLite3
import Data.Text
cb :: ColumnCount -> [Text] -> [Maybe Text] -> IO ()
cb n cnl ct = do print $ cnl !! 1
return ()
main = do
dh <- open "fileinfo.sqlite"
execWithCallback dh "select * from files;" cb
close dh
but then, what is the point of the type??? And, how do I specify that cb
is an ExecCallback
??
In Haskell, with type
you define a type synonym. In your example that means that ExecCallback
is just an alias for the type ColumnCount -> [Text]-> [Maybe Text] -> IO ()
, they are interchangeable.
You could change the following lines
cb :: ColumnCount -> [Text] -> [Maybe Text] -> IO ()
cb n cnl ct = do print $ cnl !! 1
return ()
to
cb :: ExecCallback
cb n cnl ct = do print $ cnl !! 1
return ()
and everything would still work as is. It can make your code shorter and more readable.
One other good example is
type String = [Char]
in Prelude
. I bet you normally use String
instead of [Char]
in most cases. But you're absolutely free to use either.
Another (completely unrelated) example is the conduit
package where some type synonyms make a major difference:
type Sink i = ConduitM i Void
type Consumer i m r = forall o. ConduitM i o m r
For something that's a sink for values of any type i
, Sink i
seems way more readable than ConduitM i Void
. Same for Consumer
.