Search code examples
haskellpersistentblaze-htmlhaskell-spock

Blaze-html type error inside forM_ block


I'm just starting Haskell web development using Spock, persistent and blaze-html.

In one of the routes I have, I want to load every row in my selected tables. I do something like this:

  get ("/show/flight/" <//> (var :: Var Integer)) $ \f -> requireUser $ \(_, l) -> do
     fs <- runSQL $ loadFlightInfos f

     case fs of
        [] -> blaze $ template False (showResultAlertBar False "Oops, something went wrong! Please try again.")
        _  -> blaze $ template True (H.toHtml $ usersUsername l) loadFlightSeat

           where
              loadFlightSeat :: H.Html
              loadFlightSeat =
                 forM_ fs $ \fs' -> do
                    sid <- runSQL $ getSeatIdByFlight fs' c

                    case sid of
                       Nothing  -> H.div H.! A.class_ "alert alert-danger" $ "Oops, something went wrong! Please try again."
                       Just rid -> H.a H.! A.href (H.toValue $ "/flight/seat/" <> show c <> "/" <> show (fromIntegral $ (fromSqlKey . entityKey) sid)) H.! A.class_ "btn btn-theme" $ H.toHtml fs'

loadFlightInfos has type:

Integer -> SqlPersistM [Entity Flight]

and getSeatIdByFlight:

T.Text -> Integer -> SqlPersistM (Maybe (Entity Flight))

I copied runSQL from Spock's blog sample app, and it's something like this:

runSQL :: (HasSpock m, SpockConn m ~ SqlBackend) => SqlPersistT (NoLoggingT (ResourceT IO)) a -> m a
runSQL action = runQuery $ \conn -> runResourceT $ runNoLoggingT $ runSqlConn action conn

The type error I got:

Couldn't match expected type ‘SqlBackend’
        with actual type ‘SpockConn Text.Blaze.Internal.MarkupM’
In the expression: runSQL
In a stmt of a 'do' block:
   sid <- runSQL $ getSeatIdByFlight fs' c

I still don't understand this type error, because I know runSQL is a wrapper from persistent to Spock and if I simply just want to output HTML, why can't it pass type checking?

How do I resolve this type error?


Solution

  • Disclaimer: I didn't run your code.

    I know runSQL is a wrapper from persistent to Spock

    Exactly, and there lies your issue. The type of runSQL is:

    runSQL :: (HasSpock m, SpockConn m ~ SqlBackend)
           => SqlPersistT (NoLoggingT (ResourceT IO)) a -> m a
    

    The result type, (HasSpock m, SpockConn m ~ SqlBackend) => m a, tells us that runSQL gives a result in the Spock monad. Therefore, loadFlightSeat should be a Spock monad computation as well. However, you gave it type H.Html, which has nothing to do with the Spock monad. The issue will probably go away if you remove the mistaken type signature of loadFlightSeat and adjust your usage of loadFlightSeat accordingly:

         flightSeat <- loadFlightSeat -- Returns an H.Html value in the Spock monad.
         case fs of
            [] -> blaze $ template False (showResultAlertBar False "Oops, something went wrong! Please try again.")
            _  -> blaze $ template True (H.toHtml $ usersUsername l) flightSeat
    

    P.S.: The type error you got...

    Couldn't match expected type ‘SqlBackend’
            with actual type ‘SpockConn Text.Blaze.Internal.MarkupM’
    In the expression: runSQL
    In a stmt of a 'do' block:
       sid <- runSQL $ getSeatIdByFlight fs' c
    

    ...is unusually weird because H.Html happens to be a synonym for MarkupM (), with MarkupM being a monad defined by blaze. As a consequence, the signature you gave to loadFlightSeat leads the compiler to attempt matching Spock's monad with MarkupM.