haskellyesodhamletshakespeare-text

Compact if-then-else in Hamlet templates


I am trying to understand variable interpolation in Yesod's Hamlet, in particular implementing if-then-else-like logic. Let's say I want to add CSS styling based on what value the integer x has. The x comes from a for-loop in the template, i.e. I do not have access to it from the Haskell code.

Desired result:

<span class="even positive">2</span>
<span class="odd positive">13</span>
<span class="odd non-positive">-1</span>
<span class="even non-positive">0</span>

Attempt at inline if:

<span class="#{if even x then "even" else "odd"} ...">#{x}</span>

Attempt using $with:

$with cls <- (if even x then "even" else "odd")
    <span class="#{cls}">#{x}</span>

Neither works; both result in

• Illegal variable name: ‘if’
  When splicing a TH expression

Apparently that part of Haskell syntax is not implemented in the #{...} parser. Is there a clean way to do this? I can currently see only one way out, and it is ugly as hell:

$if ((even x) && (x > 0))
    <span class="even positive">#{x}</span>
$elseif (even x)
    <span class="even non-positive">#{x}</span>
$elseif (x > 0)
    <span class="odd positive">#{x}</span>
$else
    <span class="odd non-positive">#{x}</span>

Solution

  • Most Haskell syntax isn't supported inside these template expressions. I think that's by design, since you don't want your template to contain complex business logic.

    I would write the code as a function on the Haskell end, and call it from the template. Something like:

    -- .hs file
    altclass, signclass :: Int -> String
    altclass x = if even x then "even" else "odd"
    signclass x = if x > 0 then "positive" else "non-positive"
    
    -- .hamlet file
    <span class="#{altclass x} #{signclass x}">#{x}</span>
    

    Doing it this way properly separates the business logic out of the template, but still allows you to use in-template variables.