I'm looking for a way to dynamically define functions in Haskell, or for Haskell's idiomatic equivilent of which I'm clearly not aware.
The scenario is as follows: I have a tagWithAttrs
function that generates new functions based on the provided String
argument. The definition looks something like this:
tagWithAttrs :: String -> ([(String, String)] -> [String] -> String)
tagWithAttrs tagName = [...] -- Implementation ommited to save room.
h1 :: [(String, String)] -> [String] -> String
h1 = tagWithAttrs "h1"
main :: IO ()
main = putStrLn $ h1 [("id", "abc"), ("class", "def")] ["A H1 Test"]
-- Should display '<h1 id="abc" class="def">A H1 Test</h1>'.
So far so good. But the line in which I assign h1
is one of many, since I'd have to do that for every single HTML tag I'm defining. In Python, I'd loop over a list of the HTML tag names, inserting each respective result from tag_with_attrs
into the dictionary returned by globals()
. In short, I'd be inserting new entries into the symbol table dynamically.
What is the Haskell equivilent of this idiom?
Btw, I'm fully aware that I'm duplicating the work of many existing libraries that already do HTML tags. I'm doing this for a toy project, nothing more :)
EDIT: Some posted solutions are suggesting mechanisms that still rely on defining the end result tag functions one-by-one. This violates DRY, else I would have just done it how I was doing it. It's that DRY violation that I'm trying to side-step.
Haskell is statically typed, which means that all symbols must be type-checked at compile time. So that means you can't add entries into the symbol table at runtime.
What you want is meta-programming. Where code runs at compile time to generate other code (that you naturally and rightly feel lazy to type). That implies something like a macro system .
Haskell does not have macros, but there is template Haskell: http://www.haskell.org/haskellwiki/Template_Haskell
As with macros, the idea is that you write a function that generates an
AST. The meta-function takes the name of the function you want to use (in your
case, div
, ul
, li
etc) and generates the AST of a functional with that name.
A bit overkill, but if you really want to do it this is a relatively simple tutorial: http://playingwithpointers.com/archives/615
Well, as you know Haskell is curried and functions are first class, so you really don't need any magic to do that. Just recognize that you can do stuff like:
import qualified Data.Map as M
import Data.Map (Map)
import Data.Text (Text)
type TagName = Text
type TagWithAttrs = Map TagName ([(String, String)] -> [String] -> String)
tagFuncs :: TagWithAttrs
tagFuncs =
M.fromList $
("h1", \xs ys -> zs) :
("h2", \xs ys -> zs) :
{- ... -}
[]
tagWithAttrs :: TagName -> [(String, String)] -> [String] -> String
tagWithAttrs = flip M.lookup tagFuncs
This is all regular efficient Haskell. Note: You may be tempted to define tagFuncs
as a local value to tagWithAttrs
by using a where
clause. While this can make your code more beautiful it will also result in the map to be regenerated for each invocation of tagWithAttrs
.
To dynamically insert things into the map you can make the map an argument of tagWithAttrs
instead of a top level map. Another alternative is to use a concurrent variable like an MVar
or (probably better) a TVar
.
This can be done easily with some Template Haskell:
{-# LANGUAGE TemplateHaskell #-}
import Control.Monad (forM)
import Language.Haskell.TH
tagWithAttrs :: String -> ([(String, String)] -> [String] -> String)
tagWithAttrs tagName = undefined
$(forM ["h1", "h2", "h3"] $ \tag ->
valD (varP (mkName tag)) (normalB [| tagWithAttrs $(stringE tag) |]) [])
main :: IO ()
main = putStrLn $ h1 [("id", "abc"), ("class", "def")] ["A H1 Test"]
This generates declarations h1 = tagWithAttrs "h1"
, h2 = tagWithAttrs "h2"
, h3 = tagWithAttrs "h3"
, and so on. To add more, just add them to the list.
The code is a bit ugly since it's not possible to splice patterns in TH. Otherwise, we would have been able to write something like [d| $(mkName tag) = tagWithAttrs $(stringE tag) |]
. Instead, we have to manually construct the declaration using TH combinators.
I think what I would do would be to define a data type for tags:
data Tag = H1 | H2 | H3 ...
deriving (Show, Eq, Ord, Enum, Bounded)
This is your single point of definition for all the tags that there are.
Then define a function that maps Tag
values to the appropriate function:
tag :: Tag -> [(String, String)] -> [String] -> String
tag = tagWithAttrs . show
And then where you want to call h1
, h2
, h3
, you instead call tag H1
, tag H2
, tag H3
, etc.
Note that this is identical in verbosity to if you had defined functions tag_h1
, tag_h2
, tag_h3
, etc; effectively you've just got slightly longer names (which happen to include a space). To me this is the ideal combination of DRY and "say what you mean". h1
doesn't seem like a function to me anyway; I'd actually much rather think that I was working with one function on a number of data items than that I had a giant set of functions.
If I was then unsatisfied with the speed of this (because the compiler probably won't optimize away all of the tagWithAttrs
calls away) and I had determined that this was the "lowest hanging fruit" to speed up my application, I would look at memoizing tagWithAttrs
or tag
, but internally so as to keep the same interface. One quick possibility: pre-populate a map with all tags; you can use the Enum
and Bounded
instance to do this without explicitly re-listing all the tags (this is something you couldn't do with tags represented by functions or by strings). A side benefit of non-strict evaluation is that this will probably result in tagWithAttrs
being evaluated exactly once for each tag that is actually used.
That would still leave a data-structure lookup on each tag
call (unless the compiler is clever enough to optimise them away, which is not impossible). I doubt that would be the most significant performance bottleneck unless you've done some heavy optimisation of the rest of your program. To do all the lookups at compile time (without relying on the optimiser), I think you do need Template Haskell. I probably wouldn't go that far in this case, just because I sincerely doubt I would need it to go any faster (and I have way more available compute-time than me-time). But even if I was using Template Haskell to get the lookups done at compile-time I would prefer to not make it look like a separate top-level function for each tag; I just find "tags and a function that knows how to render tags" to be a more natural and flexible approach than "tags, which can be called to render themselves".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With