jaspervdj / digestive-functors

A general way to consume input using applicative functors
149 stars 71 forks source link

Monad instance for FormTree? #87

Closed mavenraven closed 10 years ago

mavenraven commented 10 years ago

Hey, so I've looking into adding a confirmation validator to digestive-functors-validations that would work similar to Active Record confirmations .

This looks like it needs a Monad instance for FormTree, so I've looked into adding that. The problem is that some of the helper functions are impossible to make the type system unify (I think). For example, if

...
Return   :: Field v a -> FormTree Identity v m a
Bind     :: FormTree Identity v m a
         -> (Field v a -> FormTree Identity v m b)
         -> FormTree Identity v m b
...

, then for

formMapView :: Monad m => (v -> w) 
                       -> FormTree Identity v m a 
                       -> FormTree Identity w m a
...
formMapView (Bind m f) = ???

we start with

FormTree Identity v m z
-> (Field v z -> FormTree Identity v m a)
-> FormTree Identity v m a

and we need to get our type to

FormTree Identity w m z
-> (Field w z -> FormTree Identity w m a)
-> FormTree Identity w m a

. So,

 formMapView (Bind m f) = ???

must be

 formMapView (Bind m f) = Bind  (mapFromView f m) ???

or

formMapView (Bind m f) = (\x -> ???)(mapFromView f m)

where

x :: FormTree Identity w m z -> FormTree Identity w m a

. The only way we have to get from

z -> a

is to use

(Field v z -> FormTree Identity v m a)

that we're given by y. But, we have no way of getting from

w -> v

only

v -> w

, so it doesn't look possible, at least not without changing the type signature. But the only place formMapView is used is for a Functor instance, so even that doesn't help. I was thinking of adding a new type called MonadFormTree and a runMonadFormTree that would let you get back to a regular FormTree. Seems kind of ugly though. Any thoughts?

jaspervdj commented 10 years ago

It's not possible (in a nice and composable way) to add a Monad instance for a form. This is discussed in the paper The Essence of Form Abstraction, the original paper that inspired formlet-like libraries.

What sort of validation are you trying to implement? There might be another trick to do it.

mavenraven commented 10 years ago

I think the best way to do what I want is something like this:

needsConfirmation :: (MonadState s m) => (a -> s) -> Form T.Text m a -> Form T.Text m a
needsConfirmation f = validateM g
where g a = do {
    put (f a);
    return (Success a);
 }

confirms :: (MonadState s m, Eq a) => (s -> a) -> Form T.Text m a -> Form T.Text m a
confirms f = validateM g
where g a = do {
    s <- get;
    if ((f s) == a)
       then return $ Success a
       else return $ Error "not equal!"
   }

I was trying to avoid using validateM for this, because it means I have to a StateT on top of Handler in Snap, and the user has to specify paths through the state to the value they want, but I don't really have anything better.