lymar / hastache

Haskell implementation of Mustache template
Other
96 stars 16 forks source link

Allow more general types for lambdas #18

Open nurpax opened 12 years ago

nurpax commented 12 years ago

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 11 years ago

Certainly possible in a static language, if enough reflection is available. See Go templates: http://golang.org/pkg/html/template/

isturdy commented 11 years ago

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 10 years ago

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.