Lifting IO actions into Applicatives

Michael Snoyman <michael <at> snoyman.com>
2013-10-01 06:58:54 GMT

I'm wondering if anyone's run into this problem before, and if there's a common solution.

In Yesod, we have applicative forms (based originally on formlets). These forms are instances of Applicative, but not of Monad. Let's consider a situation where we want to get some user input to fill out a blog post datatype, which includes the current time:

data Blog = Blog Title UTCTime Contents

myBlogForm :: Form Blog

myBlogForm = Blog <$> titleForm <*> something <*> contentsForm

The question is: what goes in something? Its type has to be:

something :: Form UTCTime

Ideally, I'd call getCurrentTime. The question is: how do I lift that into a Form? Since Form is only an Applicative, not a Monad, I can't create a MonadIO instance. However, Form is in fact built on top of IO[1]. And it's possible to create a MonadTrans instance for Form, since it's entirely possible to lift actions from the underlying functor/monad into Form. So something can be written as:

something = lift $ liftIO getCurrentTime

This works, but is unintuitive. One solution would be to have an ApplicativeIO typeclass and then use liftIOA. My questions here are:

I'm wondering if anyone's run into this problem before, and if there's a common solution.

In Yesod, we have applicative forms (based originally on formlets). These forms are instances of Applicative, but not of Monad. Let's consider a situation where we want to get some user input to fill out a blog post datatype, which includes the current time:

data Blog = Blog Title UTCTime Contents

myBlogForm :: Form Blog

myBlogForm = Blog <$> titleForm <*> something <*> contentsForm

The question is: what goes in something? Its type has to be:

something :: Form UTCTime

Ideally, I'd call getCurrentTime. The question is: how do I lift that into a Form? Since Form is only an Applicative, not a Monad, I can't create a MonadIO instance. However, Form is in fact built on top of IO[1]. And it's possible to create a MonadTrans instance for Form, since it's entirely possible to lift actions from the underlying functor/monad into Form. So something can be written as:

something = lift $ liftIO getCurrentTime

This works, but is unintuitive. One solution would be to have an ApplicativeIO typeclass and then use liftIOA. My questions here are:

Re: Lifting IO actions into Applicatives

Niklas Haas <haskell <at> nand.wakku.to>
2013-10-01 07:29:00 GMT

On Tue, 1 Oct 2013 02:21:13 -0500, John Lato <jwlato <at> gmail.com> wrote:
> It's not a solution per se, but it seems to me that there's no need for the
> Monad superclass constraint on MonadIO. If that were removed, we could
> just have
>
> class LiftIO t where
> liftIO :: IO a -> t a
>
> and it would Just Work.
One concern with this is that it's not exactly clear what the semantics
are on LiftIO (is liftIO a >> liftIO b equal to liftIO (a >> b) or not?)
and the interaction between LiftIO and Applicative/Monad would have to
be some sort of ugly ad-hoc law like we have with Bounded/Enum etc.
Furthermore, you might end up specifying a lot of ‘redundant’
constraints like (Monad m, LiftIO m) that way, just a thing to keep in
mind.
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe <at> haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe

Re: Lifting IO actions into Applicatives

On Tue, Oct 01, 2013 at 09:29:00AM +0200, Niklas Haas wrote:
> On Tue, 1 Oct 2013 02:21:13 -0500, John Lato <jwlato <at> gmail.com> wrote:
> > It's not a solution per se, but it seems to me that there's no need for the
> > Monad superclass constraint on MonadIO. If that were removed, we could
> > just have
> >
> > class LiftIO t where
> > liftIO :: IO a -> t a
> >
> > and it would Just Work.
>
> One concern with this is that it's not exactly clear what the semantics
> are on LiftIO (is liftIO a >> liftIO b equal to liftIO (a >> b) or not?)
> and the interaction between LiftIO and Applicative/Monad would have to
> be some sort of ugly ad-hoc law like we have with Bounded/Enum etc.
Shouldn't it be an *Applicative* constraint?
class Applicative t => ApplicativeIO t where
liftIO :: IO a -> t a
and require that
liftIO (pure x) = pure x
liftIO (f <*> x) = liftIO f <*> liftIO x
Seems like ApplicativeIO makes more sense than MonadIO, which is
unnecessarily restrictive. With planned Functor/Applicative/Monad shuffle,
the former could completely replace the latter.
Tom

> On Tue, 1 Oct 2013 02:21:13 -0500, John Lato <jwlato <at> gmail.com> wrote:
> > It's not a solution per se, but it seems to me that there's no need for the
> > Monad superclass constraint on MonadIO. If that were removed, we could
> > just have
> >
> > class LiftIO t where
> > liftIO :: IO a -> t a
> >
> > and it would Just Work.
>
> One concern with this is that it's not exactly clear what the semantics
> are on LiftIO (is liftIO a >> liftIO b equal to liftIO (a >> b) or not?)
> and the interaction between LiftIO and Applicative/Monad would have to
> be some sort of ugly ad-hoc law like we have with Bounded/Enum etc.

Shouldn't it be an *Applicative* constraint?

class Applicative t => ApplicativeIO t where

liftIO :: IO a -> t a

and require that

liftIO (pure x) = pure x
liftIO (f <*> x) = liftIO f <*> liftIO x

Seems like ApplicativeIO makes more sense than MonadIO, which is
unnecessarily restrictive. With planned Functor/Applicative/Monad shuffle,
the former could completely replace the latter.
Tom

Re: Lifting IO actions into Applicatives

Yitzchak Gale <gale <at> sefer.org>
2013-10-01 12:27:20 GMT

Dan Burton wrote:
> From what you've said, it sounds like you can already write:
>
> serverSide :: IO a -> Form a
>
> This seems elegant enough to me for your needs. Just encourage it as an
> idiom specific to Forms.
>
> myBlogForm = Blog <$> titleForm <*> serverSide getCurrentTime <*>
> contentsForm
>
> Could you abstract `serverSide` out into a typeclass, such as ApplicativeIO?
> Sure. but why bother? The point is, you've got the specialization you need
> already.
In my opinion, this is the nicest answer to the original question
about yesod forms. But the more general question that it led to
is an interesting and important one.
-Yitz

Could you abstract `serverSide` out into a typeclass, such as ApplicativeIO? Sure. but why bother? The point is, you've got the specialization you need already.

Yes, I agree that to simply solve the problem in yesod-form, this would be a great solution. But as to "why bother" with ApplicativeIO: my point in sending this email was to see if other people have been bothered by this, and if it's therefore worth coming up with a general purpose solution. If there's no real interest in it, I don't see a need to create such a general solution. On the other hand, if people think this is worth a general ApplicativeIO class, I'd be happy to use that instead of defining an ad-hoc function in yesod-form.

Thanks to everyone for this great discussion, I'm thoroughly enjoying following it.

Re: Lifting IO actions into Applicatives

Yitzchak Gale <gale <at> sefer.org>
2013-10-01 12:17:40 GMT

Tom Ellis wrote:
> Shouldn't it be an *Applicative* constraint?
>
> class Applicative t => ApplicativeIO t where
> liftIO :: IO a -> t a
>
> and require that
>
> liftIO (pure x) = pure x
> liftIO (f <*> x) = liftIO f <*> liftIO x
>
> Seems like ApplicativeIO makes more sense than MonadIO, which is
> unnecessarily restrictive. With planned Functor/Applicative/Monad shuffle,
> the former could completely replace the latter.
In fact, it even makes sense to define it as FunctorIO, with the only laws
being that liftIO commutes with fmap and preserves id, i.e., that it is
a natural transformation. (Those laws are also needed for ApplicativeIO
and MonadIO.)
Since Haskell is not dependently typed and we specify laws only as
human-readable comments, should we define only FunctorIO and
then just specify in the comments the additional laws that should
be satisfied for Applicative and Monad? Or should we have equivalent
definitions that differ only in the laws that are expected to be satisfied?
Or should the different definitions have different superclass constraints?
Let's get it right this time and not create yet another
Functor/Applicative/Monad mess.
Thanks,
Yitz

Re: Lifting IO actions into Applicatives

On Tue, Oct 01, 2013 at 03:17:40PM +0300, Yitzchak Gale wrote:
> Tom Ellis wrote:
> > Shouldn't it be an *Applicative* constraint?
> >
> > class Applicative t => ApplicativeIO t where
> > liftIO :: IO a -> t a
> >
> > and require that
> >
> > liftIO (pure x) = pure x
> > liftIO (f <*> x) = liftIO f <*> liftIO x
> >
> > Seems like ApplicativeIO makes more sense than MonadIO, which is
> > unnecessarily restrictive. With planned Functor/Applicative/Monad shuffle,
> > the former could completely replace the latter.
>
> In fact, it even makes sense to define it as FunctorIO, with the only laws
> being that liftIO commutes with fmap and preserves id, i.e., that it is
> a natural transformation. (Those laws are also needed for ApplicativeIO
> and MonadIO.)
I think that law follows automatically from parametricity, doesn't it?
> Since Haskell is not dependently typed and we specify laws only as
> human-readable comments, should we define only FunctorIO and
> then just specify in the comments the additional laws that should
> be satisfied for Applicative and Monad? Or should we have equivalent
> definitions that differ only in the laws that are expected to be satisfied?
> Or should the different definitions have different superclass constraints?
In tackling such questions I think it would be useful to know how many such
instances there can be. Can there be more than one morphism between two
monads? Between two applicatives? I would guess there are plenty of
examples of functors with more than one functor morphism (natural
transformation) between them.
Perhaps these questions are easy, but I don't know how to approach them.
Tom

Re: Lifting IO actions into Applicatives

John Wiegley <johnw <at> fpcomplete.com>
2013-10-01 18:00:08 GMT

>>>>> Yitzchak Gale <gale <at> sefer.org> writes:
> In fact, it even makes sense to define it as FunctorIO, with the only laws
> being that liftIO commutes with fmap and preserves id, i.e., that it is a
> natural transformation. (Those laws are also needed for ApplicativeIO and
> MonadIO.)
Given that we are moving toward Applicative (and thus Functor) as a superclass
of Monad, why not just solve the MonadIO problem and similar type classes with
natural transformations? It requires 3 extensions, but these are extensions I
believe should become part of Haskell anyway:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
module NatTrans where
import Control.Monad.IO.Class
import Control.Monad.Trans.Maybe
class (Functor s, Functor t) => NatTrans s t where
nmap :: forall a. s a -> t a
-- Such that: nmap . fmap f = fmap f . nmap
-- In 7.10, this Functor constraint becomes redundant
instance (Functor m, MonadIO m) => NatTrans IO m where
nmap = liftIO
main :: IO ()
main = void $ runMaybeT $ nmap $ print (10 :: Int)
Now if I have a functor of one kind and need another, I reach for nmap in the
same way that I reach for fmap to transform the mapped type.
--
--
John Wiegley
FP Complete Haskell tools, training and consulting
http://fpcomplete.com johnw on #haskell/irc.freenode.net

> In fact, it even makes sense to define it as FunctorIO, with the only laws
> being that liftIO commutes with fmap and preserves id, i.e., that it is a
> natural transformation. (Those laws are also needed for ApplicativeIO and
> MonadIO.)

Given that we are moving toward Applicative (and thus Functor) as a superclass
of Monad, why not just solve the MonadIO problem and similar type classes with
natural transformations? It requires 3 extensions, but these are extensions I
believe should become part of Haskell anyway:

Now if I have a functor of one kind and need another, I reach for nmap in the
same way that I reach for fmap to transform the mapped type.
--
John Wiegley
FP Complete Haskell tools, training and consultinghttp://fpcomplete.com johnw on #haskell/irc.freenode.net

> In fact, it even makes sense to define it as FunctorIO, with the only laws
> being that liftIO commutes with fmap and preserves id, i.e., that it is a
> natural transformation. (Those laws are also needed for ApplicativeIO and
> MonadIO.)

Given that we are moving toward Applicative (and thus Functor) as a superclass
of Monad, why not just solve the MonadIO problem and similar type classes with
natural transformations? It requires 3 extensions, but these are extensions I
believe should become part of Haskell anyway:

Now if I have a functor of one kind and need another, I reach for nmap in the
same way that I reach for fmap to transform the mapped type.
--
John Wiegley
FP Complete Haskell tools, training and consultinghttp://fpcomplete.com johnw on #haskell/irc.freenode.net

Re: Lifting IO actions into Applicatives

John Wiegley <johnw <at> fpcomplete.com>
2013-10-07 16:43:57 GMT

>>>>> Daniil Frumin <difrumin <at> gmail.com> writes:
> Isn't it the case that there could be more than one natural transformation
> between functors?
Yes, I imagine there would have to be some newtype wrappers to distinguish in
those cases.
--
--
John Wiegley
FP Complete Haskell tools, training and consulting
http://fpcomplete.com johnw on #haskell/irc.freenode.net

Re: Lifting IO actions into Applicatives

I'm wondering if anyone's run into this problem before, and if there's a common solution.

In Yesod, we have applicative forms (based originally on formlets). These forms are instances of Applicative, but not of Monad. Let's consider a situation where we want to get some user input to fill out a blog post datatype, which includes the current time:

data Blog = Blog Title UTCTime Contents

myBlogForm :: Form Blog

myBlogForm = Blog <$> titleForm <*> something <*> contentsForm

The question is: what goes in something? Its type has to be:

something :: Form UTCTime

Ideally, I'd call getCurrentTime. The question is: how do I lift that into a Form? Since Form is only an Applicative, not a Monad, I can't create a MonadIO instance. However, Form is in fact built on top of IO[1]. And it's possible to create a MonadTrans instance for Form, since it's entirely possible to lift actions from the underlying functor/monad into Form. So something can be written as:

something = lift $ liftIO getCurrentTime

This works, but is unintuitive. One solution would be to have an ApplicativeIO typeclass and then use liftIOA. My questions here are:

Re: Lifting IO actions into Applicatives

There's actually no problem with defining a MonadTrans instance for non-monads. Obviously this can't follow the laws directly (since they're defined in terms of monadic bind and return), but I think we could probably state Applicative versions of those laws (assuming I haven't made a stupid mistake):

I'm wondering if anyone's run into this problem before, and if there's a common solution.

In Yesod, we have applicative forms (based originally on formlets). These forms are instances of Applicative, but not of Monad. Let's consider a situation where we want to get some user input to fill out a blog post datatype, which includes the current time:

data Blog = Blog Title UTCTime Contents

myBlogForm :: Form Blog

myBlogForm = Blog <$> titleForm <*> something <*> contentsForm

The question is: what goes in something? Its type has to be:

something :: Form UTCTime

Ideally, I'd call getCurrentTime. The question is: how do I lift that into a Form? Since Form is only an Applicative, not a Monad, I can't create a MonadIO instance. However, Form is in fact built on top of IO[1]. And it's possible to create a MonadTrans instance for Form, since it's entirely possible to lift actions from the underlying functor/monad into Form. So something can be written as:

something = lift $ liftIO getCurrentTime

This works, but is unintuitive. One solution would be to have an ApplicativeIO typeclass and then use liftIOA. My questions here are:

There's actually no problem with defining a MonadTrans instance for non-monads. Obviously this can't follow the laws directly (since they're defined in terms of monadic bind and return), but I think we could probably state Applicative versions of those laws (assuming I haven't made a stupid mistake):

I'm wondering if anyone's run into this problem before, and if there's a common solution.

In Yesod, we have applicative forms (based originally on formlets). These forms are instances of Applicative, but not of Monad. Let's consider a situation where we want to get some user input to fill out a blog post datatype, which includes the current time:

data Blog = Blog Title UTCTime Contents

myBlogForm :: Form Blog

myBlogForm = Blog <$> titleForm <*> something <*> contentsForm

The question is: what goes in something? Its type has to be:

something :: Form UTCTime

Ideally, I'd call getCurrentTime. The question is: how do I lift that into a Form? Since Form is only an Applicative, not a Monad, I can't create a MonadIO instance. However, Form is in fact built on top of IO[1]. And it's possible to create a MonadTrans instance for Form, since it's entirely possible to lift actions from the underlying functor/monad into Form. So something can be written as:

something = lift $ liftIO getCurrentTime

This works, but is unintuitive. One solution would be to have an ApplicativeIO typeclass and then use liftIOA. My questions here are:

Re: Lifting IO actions into Applicatives

MigMit <miguelimo38 <at> yandex.ru>
2013-10-01 07:25:06 GMT

What about (Compose Form IO) Blog type? Form is Applicative, IO — the same, their composition should be Applicative as well (one good thing about Applicatives — they really compose). Take a look at Control.Compose module.

I'm wondering if anyone's run into this problem before, and if there's a common solution.

In Yesod, we have applicative forms (based originally on formlets). These forms are instances of Applicative, but not of Monad. Let's consider a situation where we want to get some user input to fill out a blog post datatype, which includes the current time:

data Blog = Blog Title UTCTime Contents

myBlogForm :: Form Blog

myBlogForm = Blog <$> titleForm <*> something <*> contentsForm

The question is: what goes in something? Its type has to be:

something :: Form UTCTime

Ideally, I'd call getCurrentTime. The question is: how do I lift that into a Form? Since Form is only an Applicative, not a Monad, I can't create a MonadIO instance. However, Form is in fact built on top of IO[1]. And it's possible to create a MonadTrans instance for Form, since it's entirely possible to lift actions from the underlying functor/monad into Form. So something can be written as:

something = lift $ liftIO getCurrentTime

This works, but is unintuitive. One solution would be to have an ApplicativeIO typeclass and then use liftIOA. My questions here are:

Re: Lifting IO actions into Applicatives

Oliver Charles <ollie <at> ocharles.org.uk>
2013-10-01 08:15:47 GMT

On 10/01/2013 07:58 AM, Michael Snoyman wrote:
> I'm wondering if anyone's run into this problem before, and if there's a
> common solution.
>
> In Yesod, we have applicative forms (based originally on formlets).
> These forms are instances of Applicative, but not of Monad. Let's
> consider a situation where we want to get some user input to fill out a
> blog post datatype, which includes the current time:
>
> data Blog = Blog Title UTCTime Contents
>
> myBlogForm :: Form Blog
> myBlogForm = Blog <$> titleForm <*> something <*> contentsForm
>
> The question is: what goes in something? Its type has to be:
>
> something :: Form UTCTime
>
> Ideally, I'd call getCurrentTime. The question is: how do I lift that
> into a Form? Since Form is only an Applicative, not a Monad, I can't
> create a MonadIO instance. However, Form is in fact built on top of
> IO[1]. And it's possible to create a MonadTrans instance for Form, since
> it's entirely possible to lift actions from the underlying functor/monad
> into Form. So something can be written as:
>
> something = lift $ liftIO getCurrentTime
Is it really necessary to have a type class to do this? You can always
just introduce 'io :: IO a -> Form a' to lift IO actions into a form.
Then you just have:
myBlogForm = Blog <$> titleForm <*> io getCurrentTime <*> contentsForm
In digestive-functors, we have the base monad in the type of the form
itself, so we provide 'monadic' which goes from
m (Form m a) -> Form m a (hand-waving as there are actually other type
constraints). You might get more power by following in those steps, and
having the aforementioned 'io' function actually be:
io :: IO (Form a) -> Form a
- ocharles