Search code examples
haskellyesod

Putting the runFormPost widget into a tuple


I would like to provide some context for the widgets (Html Forms) that are being generated by runFormPost. I thought I can simply stick the result into a Tuple with my context and pattern match it in my Hamlet but it turning out to be challenging because of Handler Monads.

I have a Monadic form with the following type signature:

myForm :: ModelId -> Model -> Html -> MForm Handler (FormResult MyData, Widget)
myForm rid rec extra = do
     -- whamlet code here

I render a series of forms inside forM and use the list of widgets in my Hamlet file. It all works fine.

widgets <- forM rs' $ \(Entity rid rec) ->
          runFormPost $ myForm rid rec

Now, I would like to add some data to each widget and return it as a tuple. For this simple example, let's assume I want to add a String. I tried the following code and it does not compile when I try to use it in my Hamlet file (it compiles if I don't use widgets list in my Hamlet file)

widgets <- forM rs' $ \(Entity rid rec) ->
          return ("Test", runFormPost $ myForm rid rec)

In my Hamlet file, I tried something like this (x is my String context):

$forall (x,((res,widget), enctype)) <- widgets
     <div>
        <form method=post action=@{HandlerR hId} enctype=#{enctype}>
           ^{widget}

I get the following error:

Couldn't match expected type `((t0, a1), a0)'
            with actual type `Handler ((FormResult MyData, Widget), Enctype)'
Expected type: [(t1, ((t0, a1), a0))]
  Actual type: [(t1,
                 Handler ((FormResult MyData, Widget), Enctype))]
In the second argument of `Data.Foldable.mapM_', namely `widgets'

So far I have tried using fmap and liftM inside forM or map but I keep getting a similar error. I also tried throwing Handler in my pattern match which gave me an error saying Handler not in scope.

Any thoughts on how I can append some additional piece of info to a widget and re-use it in my Hamlet file?

Thanks!


Solution

  • The problem is that runFormPost yields a result which is wrapped in a monad, Handler in this case.

    widgets :: (String, Handler ((FormResult MyData, Widget), Enctype)
    

    So, you should use the unwrapped version widgets which you defined at first, to add your additional information. Using your first version of widgets :: ((FormResult MyData, Widget), Enctype), you can then add the line

    let widgetsWithInfo = map (\w -> ("test", w)) widgets
    

    In your hamlet file, you should then embed widgetsWithInfo :: (String, ((FormResult MyData, Widget), Enctype).

    To your additional question: Your intention with fmap seems right to me, but (if it's not just a typo) your expression is parenthesised incorrect. You want to apply fmap to the result of runFormPost $ myForm rid rec, so you have to parenthesise that subexpression:

    fmap (\x -> ("Test", x)) (rumformPost $ myForm rid rec)