I want to use a website as a working example in order to help learn Haskell. I'm trying to follow the Heist tutorial from the Snap website, and display the result of a factorial function in a web page.
I can get the example function defined "server side" without the compiler complaining, but I cannot figure out how to bind the function to a tag which I can then place into the HTML. Specifically, this part works fine (in, say, Site.hs):
factSplice :: Splice Snap
factSplice = do
input <- getParamNode
let text = T.unpack $ X.nodeText input
n = read text :: Int
return [X.TextNode $ T.pack $ show $ product [1..n]]
But the really important part - how to evaluate this function as part of a web page (such as how to bind it to a tag like < fact />) - is cryptic. The instructions say to drop:
bindSplice "fact" factSplice templateState
somewhere in the code. But this alone is not sufficient. This statement is not an expression (stuff = bindSplice...), so it is not clear how or where to put it in the code. Moreover, it is not at all clear where "templateState" is supposed to come from. It almost seems like "templateState" is supposed to be a placeholder for default values like emptyTemplateState
or defaultHeistState
, but these both appear to have been deprecated years ago, and the latest version of Heist (0.14) does not recognize them.
MightyByte has commented on this kind of question several times in 2011, but the answers all gloss over exactly the confusing part, i.e. how to actually get data into a web page. Can anyone help?
-- UPDATE --
Thank you very much, mightybyte! Your explanation and some cursory poking around in the source code cleared up a lot of confusion, and I was able to get the factorial example from the Snap website tutorial working. Here is my solution - I'm a complete n00b, so apologies if the explanation seems pedantic or obvious.
I more or less used the addConfig approach that mightybyte suggested, simply copying the implementation of addAuthSplices
from SpliceHelpers.hs. I started with the default project via "snap init", and defined a function addMySplices
in Site.hs
addMySplices :: HasHeist b => Snaplet (Heist b) -> Initializer b v ()
addMySplices h = addConfig h sc
where
sc = mempty & scInterpretedSplices .~ is
is = do
"fact" ## factSplice
This uses lenses to access the fields of the SpliceConfig neutral element mempty
, so I had to add Control.Lens
to the dependencies in Site.hs, as well as Data.Monoid
to put mempty
in scope. I also changed the type signature of the factorial splice to factSplice :: Monad n => I.Splice n
, but the function is otherwise unchanged from its form in the Heist tutorial. Then I put a call to addMySplices
in the application initializer, right next to addAuthSplices
in Site.hs
app :: SnapletInit App App
...
addAuthSplices h auth
addMySplices h
...
which results in factSplice
being bound to the tag <fact>
. Dropping <fact>8</fact>
into one of the default templates renders 40320 on the page, as advertised.
This question from about a year ago contains a superficially similar solution, but does not work with the latest version of Heist; the difference is that some fields were made accessible through lenses instead of directly, which is explained on the Snap project blog in the announcement of Heist 0.14. In particular, hcCompliedSplices
has been completely redefined - there's even a friendly warning about it in Types.hs.