I try to create a new route in yesod / haskell with a handler called state, but I get the error empty 'do' block
The steps to reproduce are the following:
Create new yesod application: stack new haskellYesod yesodweb/simple
yesod add-handler for adding a new handler with the params:
Name of route: State
Route Pattern: /state/
Methods: GET
Add following code in src/Handler/State.hs
module Handler.State where
import Import
getStateR :: Handler Html
getStateR = do
defaultLayout $ do
$(widgetFile "bla")
Create simple HTML site templates/bla.hamlet:
<h1> BLA!
Start server with stack exec yesod devel
After that I get the error:
src/Handler/State.hs:7:21: error: Empty 'do' block
|
7 | defaultLayout $ do
What's happening is that without the TemplateHaskell extension, the syntax $(...) isn't recognized as intended. Instead, it's parsed as a do-block followed by the operator $ and then the expression (...), as if you'd written:
getStateR = do
defaultLayout $ (do $ widgetFile "bla")
^^ empty do-block
You'd see the same problem with the standalone program:
main = do
$(thiswontwork)
which is parsed as main = do $ thiswontwork and generates an Empty 'do' block message, too.
Adding {-# LANGUAGE TemplateHaskell #-} to the top of the State.hs file is enough to fix the problem.
The problem was missing extensions. After adding the following, the error disappeared:
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
Related
While going through the examples of the Yesod Book, I'm running into an issue with the following snippet:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
import Data.Text (Text)
import qualified Data.Text as T
import Yesod
data App = App
instance Yesod App
mkYesod "App" [parseRoutes|
/person/#Text PersonR GET
/year/#Integer/month/#Text/day/#Int DateR
/wiki/*Texts WikiR GET
|]
getPersonR :: Text -> Handler Html
getPersonR name = defaultLayout [whamlet|<h1>Hello #{name}!|]
handleDateR :: Integer -> Text -> Int -> Handler Text -- text/plain
handleDateR year month day =
return $
T.concat [month, " ", T.pack $ show day, ", ", T.pack $ show year]
getWikiR :: [Text] -> Handler Text
getWikiR = return . T.unwords
main :: IO ()
main = warp 3000 App
(It's on page 124 of 598; route arguments)
The instance declaration on line 11 raises the following error:
YesodRouteParams.hs:11:10: error:
• No instance for (RenderRoute App)
arising from the superclasses of an instance declaration
• In the instance declaration for ‘Yesod App’
|
11 | instance Yesod App
|
It can be fixed by moving that line below the mkYesod block, where routes are defined.
I'm trying to understand why that is. Does it mean that Template Haskell evaluation at compile time happens simultaneously with the written code evaluation?
I ask because in Crystal, for example, macros are expanded before anything else. So the order of things doesn't really matter in a file (or app). But by the looks of it, they do in Haskell. Or is there another explanation?
This was because of a change made in GHC 9.0.1. From the release notes:
Breaking change: Template Haskell splices now act as separation points between constraint solving passes. It is no longer possible to use an instance of a class before a splice and define that instance after a splice. For example, this code now reports a missing instance for C Bool:
class C a where foo :: a
bar :: Bool
bar = foo
$(return [])
instance C Bool where foo = True
If you were to downgrade to GHC 8.10.7, you'd see that your code would then work as you wrote it.
I opened https://github.com/yesodweb/yesodweb.com-content/pull/269 to fix the examples in the book.
If I set the approot like the following
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module Main where
import Yesod.Core
data Yello = Yello
mkYesod "Yello" [parseRoutes|
/home1 Home1R GET
/home2 Home2R GET
|]
instance Yesod Yello where
approot = ApprootStatic "http://localhost:3000/v1"
getHome1R :: Handler Html
getHome1R = defaultLayout [whamlet|Homepage 01|]
getHome2R :: Handler Html
getHome2R = defaultLayout [whamlet|<a href=#{Home1R}>Homepage 02|]
main :: IO ()
main = warp 3000 Yello
then #{Home1R} get's rendered to http://localhost:3000/v1/home1 but this little yesod-based web application is only reachable at http://localhost:3000 (w/o the /v1).
What's the purpose of approot?
Where to set/ add /v1?
Trying to make a yesod app (without stack, and yesod init) and when ever I compile it I run into this monad error. I know these issues are common, but I've never seen anyone ask this question with these specific types (i.e. HandlerT and IO String). Here's all my code, so I think it should be easy enough for someone to test this. Also I'm using GHC 7.10.3, which is a little older, but I don't think my issue has anything to do with the compiler version.
$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.10.3
Here's the compiler error message:
$ ghc -o yesod_test yesod_monad_test.hs
[1 of 1] Compiling Main ( yesod_monad_test.hs, yesod_monad_test.o )
yesod_monad_test.hs:25:15:
Couldn't match expected type `HandlerT HelloWorld IO Text'
with actual type `IO String'
In a stmt of a 'do' block: snippet <- readFile temp
In the expression:
do { let temp = "posts/" ++ title ++ ".html";
snippet <- readFile temp;
defaultLayout
((asWidgetT . toWidget) (toHtml (preEscapedText snippet))) }
And here's my code.
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
import Yesod
import Network.Wai (pathInfo, rawPathInfo, requestMethod, responseLBS)
import Data.Text (Text)
import Text.Blaze (preEscapedText)
import Control.Exception (IOException, try)
import Control.Monad (when)
data HelloWorld = HelloWorld
mkYesod "HelloWorld" [parseRoutes|
/post/#String PostR GET
|]
instance Yesod HelloWorld
getPostR :: String -> Handler Html
getPostR title = do
let temp = "posts/" ++ title ++ ".html"
snippet <- readFile temp
defaultLayout [whamlet|#{preEscapedText snippet}|]
main :: IO ()
main = warp 3000 HelloWorld
I'm surprised that this code doesn't work, since I believe I'm following the tutorial (http://www.yesodweb.com/book/routing-and-handlers#routing-and-handlers_overlap_checking) pretty closely, though I'm using Strings over the Text type.
Thank you.
#Alec's answer was basically correct. Thanks!
Basically, #amalloy is telling you to replace snippet <- readFile temp with snippet <- liftIO $ readFile temp.
How do you build a unique key with two or more fields using the persistLowerCase quasiquoter?
When using Database.Persist to create models for a simple website using guidance from the Yesod book, the following error crops up:
Build FAILED
$PREFIX/App/Models.hs: line 42, column 18:
Not in scope: data constructor `GroupSlug'
$PREFIX/App/Models.hs: line 43, column 19:
Not in scope: data constructor `GroupName'
App/Models.hs
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module App.Models where
import Data.Text (Text)
import Database.Persist.TH
share [ mkPersist sqlSettings
, mkMigrate "migrateAll"] [persistLowerCase|
Post
group GroupId
postName Text
postSlug Text
period Text
content_en Text
content_pt Text
UniquePost postSlug
deriving Show
Group
groupId Int
groupName Text
groupSlug Text
parent GroupId
UniqueGroup groupId groupSlug
deriving Show
|]
App/Handlers.hs
{-# LANGUAGE OverloadedStrings #-}
module App.Handlers where
import Database.Esqueleto
import App.Models
groupQuery x =
select $
from $ \g -> do
where_ (g ^. GroupSlug ==. val x)
return $ g ^. GroupName
Versions:
Haskell Platform 2013.2
Cabal == 1.19.2
persistent == 1.3.0.2
scotty == 0.6.2
esqueleto == 1.3.4.5
Am I missing some extension or overlooking something else?
As far as I'm aware, data constructors that you're trying to use are actually called GroupGroupSlug and GroupGroupName (not just GroupSlug and GroupName). That is because the data type is generated by taking the entity name and joining it with the field name (with proper capitalisation). Try loading your source files with the -ddump-splices command-line parameter, and you should see it in the output somewhere.
I'm just starting doing some Yesod + Haskell stuff.
Is jsonToRepJson broken or something?
I made this code below but I always get an error in jsonToRepJson part.
It seems it doesn't get the expected type?
Any help would be great! Thanks :3
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
import Yesod
import Data.Text
data APP = APP
instance Yesod APP
mkYesod "APP" [parseRoutes|
/ TestR GET
|]
getTestR :: Handler RepJson
getTestR = jsonToRepJson $ object ["test".= ("test"::Text)]
main::IO()
main = warpDebug 3001 APP
this is what I get when I use runhaskell
api.hs:18:12:
Couldn't match expected type `RepJson' with actual type `Value'
Expected type: Handler RepJson
Actual type: HandlerT APP IO Value
In the expression:
jsonToRepJson $ object ["test" .= ("test" :: Text)]
In an equation for `getTestR':
getTestR = jsonToRepJson $ object ["test" .= ("test" :: Text)]
You must convert your value toJSON.
Eg.:
jsonToRepJson $ object [("result", toJSON resultValue)]
:)
You can read about that change in Yesod 1.2
What I did is, I used the TypeContent handler.
If I understood correctly what I've read, repSelect allows us to easily handle what type of data representation the client asks for.
It reads the request header and checks if it asks for JSON, then it will spit out JSON data, if it needs HTML it will then give the HTML page. Providing that you yourself had added the specific data needed using providRep.
Here's my code.
mkYesod "APP" [parseRoutes|
/ TestR GET
|]
getTestR::Handler TypedContent
getTestR = do
selectRep $ do
provideRep $ jsonToRepJson $ object $ (["test" .= ("test"::Text)])
main::IO()
main = warpDebug 3001 APP