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.
If you look at the top level Heist module, you'll see the initHeist function. As the documentation points out, this is the main initialization function. And you are supposed to pass it all of your templates and splices. A look at the type signature tells us these are all bundled together in the HeistConfig data type.
initHeist :: Monad n => HeistConfig n -> EitherT [String] IO (HeistState n)
This gives you back a HeistState, which is what you pass to renderTemplate when you want to render a template. Also, HeistConfig contains a SpliceConfig field for all your splices.
But this is all the low level interface. Heist was designed to have no dependencies on Snap. It is a template library and can work with any web framework. (Or even standalone with no web framework at all for things like generating HTML email.) If you're using Heist with Snap, you'll probably want to use the convenience stuff that we supply which handles the details of calling initHeist, renderTemplate, etc.
For a working example of this, your best bet is to look at the project template that you get when you do "snap init". You can see that code on github here. If you look in Application.hs, you'll see that there's a line including the Heist snaplet in the application data structure. Then, if you look at the bottom of Site.hs, you'll see that there's a call to heistInit. This function is defined by the heist snaplet here. The snaplet takes care of the initialization, state management, on-the-fly template reloading, etc for you.
So, to put this all together...from the Heist API described above, we see that we need to define our splices inside HeistConfig. But our application will interact with all this stuff mostly through the snaplet's API. So if we look in the heist snaplet API for something involving HeistConfig/SpliceConfig, we find two relevant functions:
heistInit' :: FilePath -> HeistConfig (Handler b b) -> SnapletInit b (Heist b)
addConfig :: Snaplet (Heist b) -> SpliceConfig (Handler b b) -> Initializer b v ()
These type signatures suggest two ways of defining your splices. You could use heistInit' instead of heistInit and pass your HeistConfig to it up front. Or you could add your splices afterwards by calling addConfig with the resulting Snaplet (Heist b).
Related
I'm using the GTK3 build of gtk2hs. I have a Cairo drawing in the Render monad and I want to send it to a printer.
I see that there is a Surface type which encapsulates the Cairo back end. For example it is possible to create an SVG surface using withSVGSurface, and there are similar functions for PDF, Postscript and PNG. Once you have a Surface you can apply a Render action (i.e. actually draw on it) using renderWith. This is perfectly straightforward, and I can see how to use these functions to export drawings as files.
However printing does not seem to work this way. The printOptDrawPage signal provides its callback with a printContext value. This has a function printContextGetCairoContext which returns a Cairo Context. However the library documentation doesn't have an entry for this Context type, and I can't find any functions that use it.
It looks like there ought to be printContextGetSurface function, or else a way to convert a Context into a Surface. Am I missing something?
Hah, whoops, that's embarrassing! It seems the bindings are just a little incomplete in this department.
Luckily it should be pretty easy to update them. Taking a look at the definition of the Render monad:
newtype Render m = Render { runRender :: ReaderT Cairo IO m }
We can see that the Cairo object you get from printContextGetCairoContext is just what you need to do something useful with a Render action. The implementation of the renderWith function gives you a clue about what cleanup actions you should take:
renderWith surface (Render m) = liftIO $
bracket (Internal.create surface)
(\context -> do status <- Internal.status context
Internal.destroy context
unless (status == StatusSuccess) $
fail =<< Internal.statusToString status)
(\context -> runReaderT m context)
I think one of two patches would be sensible here:
Expose a renderWith-alike that consumes a Cairo. Let the user connect up printContextGetCairoContext with the new renderWith-alike.
Don't expose printContextGetCairoContext at all; replace it with
printContextRender :: PrintContextClass self => self -> Render a -> IO a
and have printContextRender merge the call to printContextGetCairoContext with the renderWith-style cleanup.
I like option (1) for its clean backwards-compatibility story; but I like (2) a lot better from the API-design side of things. Since this module probably hasn't seen much use for the reasons you're describing, I'd lean towards patch (2).
I'd also note that you may want to look into the gtk documentation a bit to check whether the Cairo context cleanup is somebody else's responsibility (the PrintOperation's, for example).
Happy hacking!
In
How to make nicEditor snaplet? (Several questions)
I followed the hints and put addRoutes into the snaplet initializer and it worked. After updating snap and heist (0.12) modules to newer, I have some difficulties with addRoutes. If addRoutes is removed from nicsnapInit (in the snaplet example) and put into routes-function in Application.hs, the route is initialized at the same time as other routes in App and route is found.
However, if addRoutes is in nicsnapInit, server just says
no handler accepted "/netext".
This looks like the route is not added.
How to use addRoutes in snaplet initializers at the moment (heist 0.12)?
Learned almost right away that Snap Framework: Custom snaplet handler won't render template has almost similar problem. There addTemplates was used just before addRoutes. After similar changes this seems to work, that is, with line
addTemplates h ""
where h is the parameter at
nicsnapInit :: HasHeist b => Snaplet (Heist b) -> SnapletInit b Nicsnap
and corresponding change to the application initializer.
This changes the url to "niced/netext", i.e. first the snaplet name and then the route that is defined in the snaplet.
Is it possible to add only the "/netext"-part?
Use nestSnaplet "" nicsnap $ nicsnapInit .... The first parameter to nestSnaplet is not the snaplet's name. It is the URL that all the snaplet's routes are relative to.
I am currently working on a small game utilizing reactive banana and SDL. Since the goal is mostly to learn more about reactive banana and FRP, I attempted to use dynamic switching to set up a collection of game objects, but without much success so far.
In the Bartab example, the only example that uses event switching I've found, the event that ultimately triggers the creation of a new entry in the collection is acquired from outside the event network instead of using an internal event. My question is: Is this the only way to do it or is it just a special case for this example?
Are there any more examples of dynamic event switching being used in reactive banana?
From what I understand I get a Moment t (Anytime Behavior a), use execute to create an Event t (Anytime Behavior a), which is then in turn used to update the Behavior carrying the collection.
The Moment t (Anytime Behavior a) is created using trimB on a Behavior created from the triggering Event. If this triggering event originates in the event network, it doesn't compile with the error message "Could not deduce (t ~ t1)". I'm not sure what the ~ exactly means, but it obviously throws the error because the two Frameworks values (t) of the event network and this new Moment value are different.
So, long story short, I don't understand how Event Switching in reactive banana works and I'm not sure why. It should be relatively straightforward in theory.
Edit:
-- Event Network
-- ==========================
setupNetwork :: forall t. Frameworks t => (EventSource Command, EventSource ()) -> IORef SpriteMap -> GameMap -> Moment t ()
setupNetwork ((addHandlerE, _), (addHandlerT, _)) sprites map = do
-- Input Events
----------------
eInput <- fromAddHandler addHandlerE -- Player Commands
eFrame <- fromAddHandler addHandlerT -- Event on each Frame
let
[...]
eSpawnEvent :: Event t (DSCoord)
eSpawnEvent = extractCoord <$> eLeftClick
where
extractCoord (LeftClick c) = c
spawnPos :: Frameworks s => Moment s (AnyMoment Behavior DSCoord)
spawnPos = trimB $ stepper (0,0) eSpawnEvent
eSpawnPos <- execute $ (FrameworksMoment spawnPos <$ eSpawnEvent)
[...]
I simply tried to mirror newEntry / eNewEntry from the example, just using a normal event to create the new behavior. This produces the "Could not deduce (t ~ s)" error in spawnPos.
Edit2:
It works, but now the same error comes up on the line where I use execute to create the Event. "Could not deduce t ~ t1"
It appears to me that the code is essentially correct except for a minor mistake:
The compiler rightly complains that spawnPos has a (polymorphic) starting time s while the starting time of eSpawnEvent is fixed to t, which is different. The latter time is brought into scope via the forall t part in type signature of setupNetwork. In other words, t represents the starting time of the whole network.
The simple fix is to change the offending line to
spawnPos :: Frameworks t => Moment t (AnyMoment Behavior DSCoord)
I'm coding a http client as a learning project with the use of network-http package (http://hackage.haskell.org/package/HTTP-4000.2.2) .
There is a Network.Browser module which defines getBrowserState function.
Probably just a lame beginner question but how can I get BrowserState record from BrowserAction monad if Network.Browser module does not export data constructor or record's lift function ?
import Network.Browser
-- getBrowserState :: BrowserAction t (BrowserState t)
extractBS :: BrowserAction t (BrowserState t) -> BrowserState t
-- ??? implementation ???
In addition what about further handling of BrowserState fields like bsCookies, bsDebug, bsProxy etc. ? (http://hackage.haskell.org/packages/archive/HTTP/4000.2.2/doc/html/src/Network-Browser.html#BrowserState)
What you're trying to do doesn't really make sense. A BrowserAction describes an action, while a BrowserState describes the current state of the browser at some point within an action.
The only way of getting something out of a BrowserAction is to run it with the browse function.
browse :: BrowserAction conn a -> IO a
It's essentially the same as why you can't get a Something out of an IO Something, except here you have the function browse which allows you to "get stuff out of" a browser action by running it.
For example, you can make an action that extracts the current browser state after the original action and run that:
browse (action >> getBrowserState) :: IO (BrowserState conn)
Note the IO in the type here, as running the action may have side effects.
That said, I suspect that what you really want is to make the code that needs the current browser state part of an action.
browse $ do action
state <- getBrowserState
-- do stuff with the state
Note that BrowserAction has a MonadIO instance, so you can still do IO stuff by using liftIO.
browse $ do -- browsing
liftIO $ putStrLn "foo"
-- more browsing
In other words, you shouldn't think of it as getting stuff out of a BrowserAction. You should instead think of how to make the browsing-related code part of a BrowserAction which you then run with browse.
How do you get the content of ui elements when using reactive-banana? The event0 returns an event of type Event (), which has unit type instead of the type of the control. event1 takes an event of type Event w (a -> IO ()), but command is of type Event w (IO ()). mapAccumE and mapAccumB takes pure functions as parameters, so get text foo can't be used with them.
Basically, you want to work with functions instead of data. If you're thinking "How do I create a behavior which has the current text in a box", you don't. Instead you write functions that take the current text as a parameter, and pass it in when necessary. Suppose you want to print the contents of a textbox when a button is pressed. Then you would do something like this:
eButton :: NetworkDescription (Event ())
eButton = event0 button command
network = do
pressButton <- eButton
reactimate $ (\() -> get text foo >>= print) <$> pressButton
If you need to get input into a Behavior, you can similarly use a function with type Behavior (String -> a) (or whatever type you need), and then just pass the string in at the point of the reactimate call.
(Author of reactive-banana speaking. Sorry for the late reply, the possibility of questions being asked here didn't even cross my mind. :-) )
I discovered today that I omitted a very crucial feature from the library: getting the content of a UI element as a Behavior. Embarassing! :-D
John describes the current workaround, but the next version of reactive-banana will include the missing feature.
EDIT: I have released reactive-banana version 0.4 which now includes the functionality in form of a function
fromPoll :: IO a -> NetworkDescription (Behavior a)