I decided to play around with Ginger because it is a dynamic templating engine. Since it resolves most types at runtime it does not make use of too many inscrutable Haskell language extensions or compile-time tools such as Template Haskell. My opinion—possibly unpopular among Haskell enthusiasts (you should also ask me about my opinions on category theory at some point)—is that these more “highly typed” approaches to Haskell programming, while very clever, often yield APIs that are very tricky for newcomers to grasp and, most importantly, use. I am not against static typing at all. If I were I wouldn’t program in Haskell. There are, however, times when these things become an active obstacle to getting things done.
However, while Ginger avoids many of the excesses, many of its types and functions are still fairly polymorphic. This can leads to situations where type signatures are required in order to resolve ambiguities in the type inference. In my case, this led to some frustration even when using the so-called easy API. See this bug (which I hope to submit a fix for soon) for starters.
Now that I’ve tamed
easyRender (I think!), I thought I’d share the fruits of my labour with you here. Here’s a GitHub project with my experimentation. I’ll regurgitate specific interesting bits right here.
This snippet demonstrates how to directly render a template with a
HashMap as the context:
This is more or less equivalent to the example given in Ginger’s Getting Started guide under Running - The Easy Interface.
This is fun, but we’ll need to do more sophisticated things eventually. Ginger has a variant type
GVal that it uses to represent all of the possible types Ginger templates can handle at runtime. In the
HashMap example given above, Ginger uses
ToGVal instance via its
toGVal function to convert the
HashMap into an instance of
The main problem with
GVal for newcomers is, in my opinion, the mysterious
m type parameter which, the documentation says, should be a
Monad. Unfortunately, I’m a bit dense and do not have a natural intuition for that might mean exactly. Thence my experimentation and this article. The first step in untangling
GVal and figuring out what a viable value for
m might be, I thought, might to be try to explicitly convert my
HashMap myself. This led to:
Attempts to use
ctx without providing an explicit type signature for
ctx leads to the following yummy GHC compiler error:
/path/to/src/gingerapp/src/Main.hs:36:21: error: • Ambiguous type variable ‘m0’ arising from a use of ‘easyRender’ prevents the constraint ‘(ToGVal (Run SourcePos (Writer Text) Text) (GVal m0))’ from being solved. Probable fix: use a type annotation to specify what ‘m0’ should be. These potential instance exist: instance ToGVal m (GVal m) -- Defined in ‘Text.Ginger.GVal’ • In the second argument of ‘($)’, namely ‘easyRender ctx template’ In a stmt of a 'do' block: Text.putStrLn $ easyRender ctx template In the expression: do let ctx = toGVal $ HashMap.fromList ... Text.putStrLn $ easyRender ctx template | 36 | Text.putStrLn $ easyRender ctx template | ^^^^^^^^^^^^^^^^^^^^^^^
I spent quite a bit of time staring at this wondering what to do next. It then dawned on me that a reasonable value for
m0 might be
Run SourcePos (Writer Text) Text (see documentation for the various types at
Text). I’m not sure why GHC couldn’t infer this automatically but—whatever—let’s just move on:
And, huzzah: it compiles! Of course, a simple map of
Text is one thing. What if we want to store other things in our
GVal. This question led me to look at using
dict. This is a convenience function for generating heterogeneous
And, miraculously, it worked.
Now I think I know enough to be dangerous with Ginger!
All content © 2019 Richard Cook. All rights reserved.