I try to return a uuid within a route definition for a web app (Spock Webserver).
A route is pretty simple to define
get("PATH") $ do
text "Hello World"
Now I try to return a uuid via nextRandom from the Data.UUID.V1 module.
The function returns a IO(Maybe UUID) value.
So I thought, since I am in IO and work with another IO I have to do bind the value simply with <-, like so:
get ("id") $ do
uuid<-nextUUID
json . pack $ show $ uuid
But the compiler says no:
Couldn't match type ‘ActionCtxT ctx0 m0’ with ‘IO’
Expected type: IO b0
Actual type: ActionCtxT ctx0 m0 b0
• In a stmt of a 'do' block: json . pack $ show $ uuid
In the second argument of ‘($)’, namely
‘do { uuid <- nextUUID;
json . pack $ show $ uuid }’
Why is it throwing that error?
I could easily create the uuid with a simple print example, but within Spock I don't understand what the ActionCtxT does and why I can't do the uuid IO in it.
So I thought, since I am in IO and work with another IO
That's the trouble here, when you're routing in Spock, you're not in IO. The error message tells you what context you're really in: ActionCtxT ctx0 m0. According to the docs, that's a monad transformer stack that bundles effects and state.
You can "lift" an IO computation into the right type using liftIO.
get ("id") $ do
uuid <- liftIO nextUUID
json . pack $ show $ uuid
Based on Libbys helpful answer I just added the catch for the Nothing of the Maybe UUID. Here the full programm:
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Web.Spock hiding (head)
import Web.Spock.Config
import Data.UUID.V1
import Data.Pool
import Control.Monad.IO.Class
import Database.PostgreSQL.Simple
import Data.Aeson (Value(Null))
import qualified Network.HTTP.Types.Status as Http
type AppAction a = SpockActionCtx () Connection AppSession AppState a
data AppState = EmptyState
data AppSession = EmptySession
main :: IO ()
main =
do pool<-createPool (connect (ConnectInfo "localhost" 5432 "" "" "envelopes") ) close 1 10 10
spockCfg <- defaultSpockCfg EmptySession (PCPool pool) EmptyState
runSpock 8080 (spock spockCfg app)
app :: SpockM Connection AppSession AppState ()
app = do
get ("json/id") $ do
uuid<-liftIO nextUUID
case uuid of
Nothing -> do
setStatus Http.status500
json Null
Just x -> json $ show x
Related
The following code tries to combine two examples that work separately:
day 19 of 24 (2012) and e.g. ws example but I took almost everything websocket related away to get a small example.
Please, find the code below. The msgHandler is called by helloDb, which will get the the snaplet containing the db-connection and pass it to the msgHandler. The snaplet-posgresql-simple docs (at the end) give convenience instances and an example how to use one of them in the Initializer monad.
When I take the two commented lines away, ghc say that there are two instances involving out-of-scope types and that instances do overlapp: HasPostgres (ReaderT r m) and HasPostgres (ReaderT (Snaplet Postgres) m).
So the question is, how to get the program to compile so that I could pass db-connection from the snaplet to the websocket-part.
My goal is to make the websocket listen for messages, query db, and send messages pack. Other things that I already tried:
aFun :: (MonadIO m, HasPostgres m) => ... m (..) having both db-queries and websocket-things (both liftIO'd) compiles until WS.runWebSocketsSnap calls directly or indirectly aFun.
Tried to tell msgHandler :: (MonadIO m, HasPostgres m) but then ghc says that there is no instance for HasPosgres IO. My feeling is that this should be doable without IO-instance. Or is it?
The code below is trying to use snaplet in non-snaplet context but I'm not sure if this is the correct way.
Is there a better approach to combining websockets and (db-) snaplets in snapframework? After trying several approaches I'm in serious mental lock state obviously needing help. Any help (even small hints about what kind of things I should start learning/refreshing), will be highly appreciated!
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Data.Maybe
import Data.Monoid ((<>))
import Control.Lens
import Control.Monad.Trans
import Control.Monad.Reader
import Snap.Snaplet
import Snap.Snaplet.PostgresqlSimple
import Snap.Http.Server
import Snap.Core as SC
import Data.ByteString as BS
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified Network.WebSockets as WS
import qualified Network.WebSockets.Snap as WS
newtype App = App { _db :: Snaplet Postgres }
makeLenses ''App
msgHandler :: (MonadIO m) => App -> BS.ByteString -> WS.PendingConnection -> m ()
msgHandler appSt mUId pending = do
conn <- liftIO $ WS.acceptRequest pending
-- res <- liftIO $ runReaderT (query "SELECT name FROM users WHERE id = ?" (Only mUId)) dbSnaplet
-- liftIO $ print (res :: [Name])
liftIO $ T.putStrLn "msgHandler ended"
where dbSnaplet = view db appSt
initApp :: SnapletInit App App
initApp = makeSnaplet "myapp" "My application" Nothing $
App <$> nestSnaplet "db" db pgsInit
<* addRoutes [("/hello/:id", helloDb)]
newtype Name = Name { _nm :: Text } deriving (Show, Eq)
instance FromRow Name where fromRow = Name <$> field
helloDb :: Handler App App ()
helloDb = do
Just mUId <- getParam "id"
userName <- with db $ listToMaybe <$> query "SELECT name FROM users WHERE id = ?" (Only mUId)
writeText $ maybe "User not found" (\h -> "Hello, " <> (T.pack . show) h) (userName :: Maybe Name)
sStApp <- getSnapletState
WS.runWebSocketsSnap $ msgHandler (view snapletValue sStApp) mUId
main :: IO ()
main = serveSnaplet defaultConfig initApp
The overlapping instance issue you ran into is a bug in the snaplet-postgresql-simple library that has been fixed but the fix has not yet been released. You might want to ask the maintainer about this.
In the meantime you can either pull the latest version of the library from Github, or redefine a type different but isomorphic to ReaderT (Snaplet Postgres), copying the HasPostgres instance.
What I'm trying to do is to create a somewhat smart reverse proxy server that should process some requests on its own and forward the others to the backend of choice. To make it challenging I'm trying hard to do it in Haskell, which I am a total newbie in.
Here's the code I've come up so far:
{-# LANGUAGE OverloadedStrings #-}
import Control.Applicative
import Data.ByteString
import Network.HTTP.ReverseProxy
import Network.HTTP.Types
import Network.Wai
import Network.Wai.Handler.Warp
import Network.Wai.Middleware.RequestLogger
import qualified Network.HTTP.Client as HC
helloApp :: Application
helloApp req respond =
respond $ responseLBS status200 [("Content-Type", "text/plain")] "Hello"
proxyStubApp :: Application
proxyStubApp req respond =
respond $ responseLBS status200 [("Content-Type", "text/plain")] "You've hit the stub"
proxyApp :: IO Application
proxyApp = do
manager <- HC.newManager HC.defaultManagerSettings
return $ waiProxyTo (const $ return $ WPRProxyDest ProxyDest { pdHost = "localhost", pdPort = 9393 }) defaultOnExc manager
app :: Application
app req respond =
serve req respond
where serve = lookupServeFunction req
lookupServeFunction :: Request -> Application
lookupServeFunction req
| isInfixOf "sample_path" (rawPathInfo req) = proxyStubApp
| otherwise = helloApp
main = run 3011 =<< (logStdoutDev <$> return app)
It works fine, but when I exchange proxyStubApp for actual proxyApp I am forced to add IO all over the place. Particularly it gets added to app, consequently leaving me with the following compilation error message:
Couldn't match expected type ‘Request -> t5 -> t4’
with actual type ‘IO Application’
The equation(s) for ‘app’ have two arguments,
but its type ‘IO Application’ has none
I feel like I understand why it is happening, but I'm out of ideas of how to cope with it :( Or am I doing something totally wrong?
Thank you!
P.S. Here are the dependencies should you want to compile the thing on your own: wai warp http-types text bytestring wai-extra time http-reverse-proxy http-client
The IO in IO Application is kind-of redundant. Note that
type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
so, expanding proxyApp's arguments (what you already do in proxyStubApp), you get
proxyApp :: Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
proxyApp req continuation = do
manager <- HC.newManager HC.defaultManagerSettings
waiProxyTo (...) req respond
That works, because in either case
proxyApp :: IO Application
proxyApp = do
manager <- HC.newManager ...
...
and
proxyApp :: Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
proxyApp req continuation = do
manager <- HC.newManager ...
...
the IO action HC.newManager ... is "run within IO".
You may find it conceptually clearer to construct an Application in IO and hand it to some other place, and I won't argue with you. I want to note though, that you choose the Application based on the Request, so in a way you are in the hypothetical HTTP monad when choosing, so lookupServeFunction's signature Request -> Application makes more sense to me.
If you want to keep that type signature for proxyApp,
lookupServeFunction and app will have to be in IO as well and main will have to change accordingly, e.g.
myApp <- app
...
As haoformayor said, It is generally easier to work without the outer IO layer.
You might also like to simplify main.
fmap logStdoutDev (return app)
is the same as
return (logStdoutDev app)
and
run 3011 =<< return (logStdoutDev app)
is the same as
run 3011 (logStdoutDev app)
You might want to install hlint, which will help you spot these.
import qualified Data.ByteString.Lazy as L
import Data.Enumerator.List (consume)
data App = App
mkYesod "App" [parseRoutesNoCheck|
/*Texts WikR GET --PathMultiPiece handler for all combinations
/ WikiR POST
|]
instance Yesod App
getWikR :: [Text] -> Handler Text
getWikR parts = return "" --defaultLayout [whamlet||]
postWikiR = do
bss <- lift consume
return $ RepJson $ toContent $ L.fromChunks bss
main :: IO ()
main = warp 3000 App
gives the error,
yesod/hw.hs:14:1:
Couldn't match type ‘Data.Enumerator.Internal.Iteratee
Data.ByteString.Internal.ByteString m0’
with ‘IO’
Expected type: HandlerT App IO RepJson
Actual type: HandlerT
App
(Data.Enumerator.Internal.Iteratee
Data.ByteString.Internal.ByteString m0)
RepJson
In the first argument of ‘yesodRunner’, namely ‘postWikiR’
In the expression:
yesodRunner postWikiR env3739_adGV (Just WikiR) req3739_adGW
The error line #ln-num#14 is mkYesod "App" [parseRoutesNoCheck|
I am sure, the error is to do with postWikiR#return $ RepJson $ toContent $ L.fromChunks bss. But not sure, how to fix?
Reference-1
Probably you are trying to run old code that does not work with current Yesod.
You can get this to compile and run (ghc-7.10.1, yesod-1.4.1.5) with
import Data.Enumerator (run_)
...
bss <- run_ consume
but I'm not sure what you want it to do. As is, it will return an empty list.
In the question Web, Scotty: connection pool as monad reader it is shown how to use ScottyT to embed a Reader monad in the stack to access a static configuration (in that case, a connection pool).
I have a similar question, but simpler – or at least I thought so…
I want to add a Reader to a single handler (i.e. a ActionT), not the whole app.
I started modifying the program from the question above, but I cannot figure out how to turn an ActionT Text (ReaderT String IO) into the ActionT Text IO the handler needs to be. After fumbling around and trying to use typed holes hoping to see how to construct this I have to give up for now and ask for help. I really feel this should be simple, but cannot figure out how to do this.
Here's the program, with the lines where I'm stuck highlighted:
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Text.Lazy as T
import Data.Text.Lazy (Text)
import Control.Monad.Reader
import Web.Scotty.Trans
type ActionD = ActionT Text (ReaderT String IO)
main :: IO ()
main = do
scottyT 3000 id id app
-- Application
app :: ScottyT Text IO ()
app = do
get "/foo" $ do
h <- handler -- ?
runReaderT h "foo" -- ?
--get "/bar" $ do
-- h <- handler
-- runReaderT h "bar"
-- Route action handler
handler :: ActionD ()
handler = do
config <- lift ask
html $ T.pack $ show config
If you want to run each action in a separate reader, you don't need the more complex Scotty.Trans interface at all. You can just build you monad stack the other way around, with ReaderT on top.
import qualified Data.Text.Lazy as T
import Control.Monad.Reader
import Web.Scotty
type ActionD = ReaderT String ActionM
main :: IO ()
main = do
scotty 3000 app
-- Application
app :: ScottyM ()
app = do
get "/foo" $ do
runReaderT handler "foo"
-- Route action handler
handler :: ActionD ()
handler = do
config <- ask
lift $ html $ T.pack $ show config
I am writing simple sitemap.xml crawler. The code is below. My question is why the code in the end of main does not print anything. I suspect it's because haskell's lazyness but don't know how to deal with it here:
import Network.HTTP.Conduit
import qualified Data.ByteString.Lazy as L
import Text.XML.Light
import Control.Monad.Trans (liftIO)
import Control.Monad
import Data.String.Utils
import Control.Exception
download :: Manager -> Request -> IO (Either HttpException L.ByteString)
download manager req = do
try $
fmap responseBody (httpLbs req manager)
downloadUrl :: Manager -> String -> IO (Either HttpException L.ByteString)
downloadUrl manager url = do
request <- parseUrl url
download manager request
getPages :: Manager -> [String] -> IO [Either HttpException L.ByteString]
getPages manager urls =
sequence $ map (downloadUrl manager) urls
main = withManager $ \ manager -> do
-- I know simpleHttp is bad here
mapSource <- liftIO $ simpleHttp "http://example.com/sitemap.xml"
let elements = (parseXMLDoc mapSource) >>= Just . findElements (mapElement "loc")
Just urls = liftM (map $ (replace "/#!" "?_escaped_fragment_=") . strContent) elements
mapElement name = QName name (Just "http://www.sitemaps.org/schemas/sitemap/0.9") Nothing
return $
getPages manager urls >>= \ pages -> do
print "evaluate me!"
sequence $ map print pages
You're running into the same problem I describe here, at least as far as having incorrect code that typechecks when it should actually give a type error: Why is the type of "Main.main", "IO ()" and not "IO a"?. This is why you should always give main the type signature main :: IO () explicitly.
To fix the problem, you will want to replace return with lift (see http://hackage.haskell.org/package/transformers/docs/Control-Monad-Trans-Class.html#v:lift) and replace sequence $ map ... with mapM_. mapM_ f is equivalent to sequence_ . map f.
Substitute your last return with runResourceT (http://hackage.haskell.org/package/resourcet-1.1.1/docs/Control-Monad-Trans-Resource.html#v:runResourceT). As it's type suggests, it would turn ResourceT into IO action.