Allow more general types for lambdas #18

nurpax opened this Issue Sep 19, 2012 · 3 comments


None yet
3 participants

nurpax commented Sep 19, 2012

I find the current implementation of lambdas being of type ByteString -> ByteString to be fairly restrictive, leading to boiler-platy Haskell code to preprocess inputs to Hastache to turn some things into strings.

I propose that lambdas should be generalized so that lambdas could be called with objects and not just ByteStrings. AFAICT, this would be similar to other mustache implementations that pass in a "this" that contains the current item under iteration.

Here's an example (only tested to type check, but didn't try running). I hope the idea comes through better in code:

{-# LANGUAGE DeriveDataTypeable #-}
import Text.Hastache
import Text.Hastache.Context
import qualified Data.ByteString.Lazy as LZ
import Data.Data
import Data.Generics

main = hastacheStr defaultConfig (encodeStr template) context
    >>= LZ.putStrLn

data Status = Open | Closed deriving (Data, Typeable)

data Bug = Bug {
    summary :: String
  , status :: Status
  } deriving (Data, Typeable)

data BugList = BugList {
    expandStatus :: Bug -> String
  , bugs :: [Bug]
    } deriving (Data, Typeable)

-- expandStatus would be called with an instance of Bug from the
-- parent context.  This would be more general than the current method
-- of restricting lambdas to ByteString -> ByteString.
template = "{{#bugs}} {{expandStatus}}  {{/bugs}}"

statusClass (Bug _ Open) = "class=\"bug_open\""
statusClass (Bug _ Closed) = "class=\"bug_closed\""

buglist =
  [Bug "buggy monad" Open,
   Bug "fixed lambda" Closed]

context =
  mkGenericContext $ BugList statusClass buglist

The key part being how statusClass is be called for Bug items in the #bugs list being iterated.

I don't know how easy it is to build something like this in Haskell though. I realize what I'm asking for is easy in dynamic languages but could be harder in a statically typed language.

crufter commented Jan 15, 2013

Certainly possible in a static language, if enough reflection is available. See Go templates:

isturdy commented Apr 9, 2013

Apologies for necroing... I think that this can be done, although it would probably require a radical change to the internal types. The problem I see with this is that it collides with existing Mustache syntax. By my understanding, in "{{#bugs}} {{expandStatus}} {{/bugs}}", expandStatus refers (unsuccessfully) to a field of Bug (since when the outer context is a list, the inner text is repeatedly evaluated in that context). Even using the existing Mustache lambda style, we have "{{#expandStatus}} {{bug1}} {{/expandstatus}}", and per the Mustache standard expandStatus is responsible for evaluating any replacements within the text. One could say that if the inside of a lambda contains only a substitution, the function is called not on the text but on the Haskell term represented, but that seems error-prone.

In point of fact, I think there is more hope for doing this in the static language: it is, I believe, possible (with sufficient abuse of Data.Typeable) for the library to recognize whether the function wants the raw text or the Haskell term (albeit with ambiguity if the function does have type (ByteString -> ByteString) and the internal text refers to a ByteString).


nurpax commented Feb 19, 2014

I realize now that my Mustache example was in error. I think what I actually meant is that it'd be great to be able to be able to register "helpers", like for example in Handlebars.

If possible, expandStatus would register into the scope, and you could call it from a template like so:

template = "{{#bugs}} {{{expandStatus this}}}  {{/bugs}}"

By default it could call the helper with the current this context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment