A collection of related proposals regarding monads

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
15 messages Options
Reply | Threaded
Open this post in threaded view
|

A collection of related proposals regarding monads

Cale Gibbard
I personally feel that the inclusion of 'fail' in the Monad class is
an ugly solution to the problem of pattern matching, and gives the
incorrect impression that monads should have some builtin notion of
failure. Indeed, it's becoming common to type the result of some
function in an arbitrary monad in order to indicate the potential for
failure, which is strictly speaking, not the right thing to do. (In a
lot of cases, it's going to be no better than complete program
failure)

We ought to be using MonadZero when we want to express failure, but it's gone!

What do people think of the following proposal? Remove fail from the
Monad class. Reinstate MonadZero as a separate class as in Haskell
1.4.

Do notation is translated as in Haskell 1.4:
do {e} = e
do {e;stmts} = e >> do {stmts}
-- if p is refutable,
do {p <- e; stmts} = let ok p = do {stmts}; ok _ = mzero in e >>= ok
-- if p is irrefutable,
do {p <- e; stmts} = e >>= \p -> do {stmts}
do {let decls; stmts} = let decls in do {stmts}

Thus, refutable pattern matches occuring in a do-expression would
force the expression to be typed explicitly with MonadZero. This is
probably a good thing, as it forces one to think about the
consequences of not properly dealing with a pattern match in any monad
which doesn't explicitly allow for failure.

Even if this translation of do-syntax isn't accepted, I still think
that we should have a separate MonadZero. Its existence lets various
type signatures become more expressive. There are a lot of cases where
a MonadPlus constraint pops up where a MonadZero constraint would do.
(I've also been seeing Monad get used for these cases, when it
shouldn't!) This distinction allows one to see immediately when a
monad is getting used for failure or whether choice is really
essential.

I'd also like to see the current use of MonadPlus split into MonadElse
(or MonadOr) and MonadPlus, as described at the bottom of
http://www.haskell.org/hawiki/MonadPlus
as it helps to clarify the distinction between backtracking-type
failure and immediate failure in types. We could even put this
distinction to good use in many monads which do support backtracking
anyway:

instance MonadElse [] where
    [] `morelse` ys = ys
    (x:xs) `morelse` ys = (x:xs)

Lastly, it would be nice to have some standard name for the function:
option :: (MonadPlus m) => [a] -> m a
option = foldr (mplus . return) mzero
which seems to come up quite a bit in my experience with nondet monads.

 - Cale

P.S. Oh, and don't get me started about the whole Functor subclass
thing, and the inclusion of join in the Monad class. Of course I want
those too. :)

P.P.S. This reopens the possibility for monad comprehensions, and the
more general versions of filter, concat, etc. in the 1.4 prelude -- I
wasn't actually around for Haskell 1.4, but I *really* think they'd be
a nice language feature to have, as I like to view monads as an
abstraction of *containers*, which that syntax/methodology emphasises.
I usually find that initially teaching monads to newcomers in terms of
containers is much more effective, and that syntax would allow for a
nice segue from lists to monads. If people think the error messages
are scary, we could provide a switch -fbeginner and a more 98-like
prelude to turn monad comprehensions and any other potentially scary
features off for the newcomers.
_______________________________________________
Haskell mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell
Reply | Threaded
Open this post in threaded view
|

Re: A collection of related proposals regarding monads

ajb@spamcop.net
G'day Cale.

Quoting Cale Gibbard <[hidden email]>:

> I personally feel that the inclusion of 'fail' in the Monad class is
> an ugly solution to the problem of pattern matching, and gives the
> incorrect impression that monads should have some builtin notion of
> failure.

So do I.

> We ought to be using MonadZero when we want to express failure, but it's
> gone!

I agree!

> What do people think of the following proposal?

I don't like it.

My feeling is that do { p <- xs; return e } should behave identically
(modulo the precise error message if the pattern match fails) to
map (\p -> e) xs.  Your proposal would make it into a map/filter
hybrid.

Speaking more practically, if a pattern match is technically refutable,
but I, as the programmer, intend it never to fail, then I shouldn't need
to use MonadZero.

I don't like fail either, but I must admit that I like allowing refutable
patterns.

> Thus, refutable pattern matches occuring in a do-expression would
> force the expression to be typed explicitly with MonadZero. This is
> probably a good thing, as it forces one to think about the
> consequences of not properly dealing with a pattern match in any monad
> which doesn't explicitly allow for failure.

Surely this is no different from not properly dealing with a pattern match
in a case statement?

> Even if this translation of do-syntax isn't accepted, I still think
> that we should have a separate MonadZero.

I agree.  I miss MonadZero.

> I'd also like to see the current use of MonadPlus split into MonadElse
> (or MonadOr) and MonadPlus, [...]

I agree, but I think such a proposal shouldn't be considered in isolation
from standardising some MTL-like library.

> Lastly, it would be nice to have some standard name for the function:
> option :: (MonadPlus m) => [a] -> m a
> option = foldr (mplus . return) mzero
> which seems to come up quite a bit in my experience with nondet monads.

I liked Oleg's suggestion: "choose", since it's the realisation of the
axiom of choice.  Though possible "mchoose" might be more appropriate.
Even more appropriate might be to rationalise some of these naming
conventions.

Cheers,
Andrew Bromage
_______________________________________________
Haskell mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell
Reply | Threaded
Open this post in threaded view
|

Re: A collection of related proposals regarding monads

Wolfgang Jeltsch
In reply to this post by Cale Gibbard
Am Mittwoch, 4. Januar 2006 21:54 schrieb Cale Gibbard:
> I personally feel that the inclusion of 'fail' in the Monad class is
> an ugly solution to the problem of pattern matching, and gives the
> incorrect impression that monads should have some builtin notion of
> failure.

I totally agree!

> [...]

Best wishes,
Wolfgang
_______________________________________________
Haskell mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell
Reply | Threaded
Open this post in threaded view
|

Re: A collection of related proposals regarding monads

Cale Gibbard
In reply to this post by ajb@spamcop.net
On 04/01/06, [hidden email] <[hidden email]> wrote:

> G'day Cale.
>
> Quoting Cale Gibbard <[hidden email]>:
>
> > I personally feel that the inclusion of 'fail' in the Monad class is
> > an ugly solution to the problem of pattern matching, and gives the
> > incorrect impression that monads should have some builtin notion of
> > failure.
>
> So do I.
>
> > We ought to be using MonadZero when we want to express failure, but it's
> > gone!
>
> I agree!
>
> > What do people think of the following proposal?
>
> I don't like it.
>
> My feeling is that do { p <- xs; return e } should behave identically
> (modulo the precise error message if the pattern match fails) to
> map (\p -> e) xs.  Your proposal would make it into a map/filter
> hybrid.
>
> Speaking more practically, if a pattern match is technically refutable,
> but I, as the programmer, intend it never to fail, then I shouldn't need
> to use MonadZero.
>
> I don't like fail either, but I must admit that I like allowing refutable
> patterns.
>
Okay, so how about we separate these?

do {p <- e; stmts} = e >>= \p -> do {stmts}
do {p <-: e; stmts} = let ok p = do {stmts}; ok _ = mzero in e >>= ok
(note the new symbol for monadically failing pattern match)

This allows the two meanings to be clearly separated, and only the
latter of the two will create a dependency on MonadZero, and it will
do so consistently.

 - Cale
_______________________________________________
Haskell mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell
Reply | Threaded
Open this post in threaded view
|

Re: A collection of related proposals regarding monads

Wolfgang Jeltsch
In reply to this post by ajb@spamcop.net
Am Mittwoch, 4. Januar 2006 22:30 schrieb [hidden email]:
> [...]

> Though possible "mchoose" might be more appropriate.

These leading m's are not nice in my opinion.

> Even more appropriate might be to rationalise some of these naming
> conventions.

Yes, we should remove those m's and use qualification where removal of those
m's leads to name clashes.

> Cheers,
> Andrew Bromage

Best wishes,
Wolfgang
_______________________________________________
Haskell mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell
Reply | Threaded
Open this post in threaded view
|

Re: A collection of related proposals regarding monads

ajb@spamcop.net
In reply to this post by Cale Gibbard
G'day all.

I wrote:

> > My feeling is that do { p <- xs; return e } should behave identically
> > (modulo the precise error message if the pattern match fails) to
> > map (\p -> e) xs.  Your proposal would make it into a map/filter
> > hybrid.

Which, of course, it is now.  I blame lack of caffeine.

Quoting Cale Gibbard <[hidden email]>:

> Okay, so how about we separate these?
>
> do {p <- e; stmts} = e >>= \p -> do {stmts}
> do {p <-: e; stmts} = let ok p = do {stmts}; ok _ = mzero in e >>= ok
> (note the new symbol for monadically failing pattern match)

Not happy.  If anything, it should be the former which gets the new
symbol, to make it the same as list comprehensions.  But this would
break backwards compatibility.

Cheers,
Andrew Bromage
_______________________________________________
Haskell mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell
Reply | Threaded
Open this post in threaded view
|

Re: A collection of related proposals regarding monads

David Menendez
In reply to this post by Cale Gibbard
Cale Gibbard writes:

> I personally feel that the inclusion of 'fail' in the Monad class is
> an ugly solution to the problem of pattern matching, and gives the
> incorrect impression that monads should have some builtin notion of
> failure. Indeed, it's becoming common to type the result of some
> function in an arbitrary monad in order to indicate the potential for
> failure, which is strictly speaking, not the right thing to do. (In a
> lot of cases, it's going to be no better than complete program
> failure)
>
> We ought to be using MonadZero when we want to express failure, but
> it's gone!

Yeah, I don't like fail either. In fact, I usually forget to define it,
even for instances of MonadPlus.

There are typically three ways to indicate error in existing monad
libraries, e.g.,

    mzero      :: MonadPlus m =>              m a
    fail       :: Monad m =>        String -> m a
    throwError :: MonadError e m =>      e -> m a
 
I would say that fail and throwError essentially have the same meaning,
but I distinguish them from mzero. To my mind, 'mzero' means "no
answer", whereas fail and throwError mean "something's wrong".

For example, my implementation of Nondet doesn't backtrack over errors:

    mzero        `mplus` m = m
    throwError e `mplus` m = throwError e

Should a pattern match failure call mzero or throwError? I was
originally going to say throwError, but now I'm not so sure. First,
MonadError is severely non-H98 (fundeps). Second, we would either need
the error type to belong to some class which includes pattern match
failures, or have a dedicated throwPatternMatchFailure method in
MonadError. Finally, you can write sensible code which backtracks on
pattern-match failure, e.g.,

    do ...
       Just a <- lookup ...
       ...

> Even if this translation of do-syntax isn't accepted, I still think
> that we should have a separate MonadZero.

I like the idea of a separate MonadZero. Do we know why it was combined
with MonadPlus? Were there efficiency concerns, or did people dislike
having to declare all those separate instances?

> I'd also like to see the current use of MonadPlus split into MonadElse
> (or MonadOr) and MonadPlus, as described at the bottom of
> http://www.haskell.org/hawiki/MonadPlus
> as it helps to clarify the distinction between backtracking-type
> failure and immediate failure in types. We could even put this
> distinction to good use in many monads which do support backtracking
> anyway:
>
> instance MonadElse [] where
>     [] `morelse` ys = ys
>     (x:xs) `morelse` ys = (x:xs)

With backtracking monads, you can use Oleg's msplit operator to get
morelse, soft-cut, and various other operations.

    class MonadPlus m => MonadChoice m where
        msplit :: m a -> m (Maybe (a, m a))

    mif :: MonadSplit m => m a -> (a -> m b) -> m b -> m b
    mif p t e = msplit p >>= maybe e (\(x,xs) -> t x `mplus` (xs >>= t))
   
    a `orElse` b = mif a return b

With non-backtracking monads, you can use throwError or just use mplus
and remind people that non-backtracking monads don't backtrack.

> Lastly, it would be nice to have some standard name for the function:
> option :: (MonadPlus m) => [a] -> m a
> option = foldr (mplus . return) mzero
> which seems to come up quite a bit in my experience with nondet
> monads.

Mine too. Someone else mentioned "choose", which seems nice. Or,
"fromList".

Incidentally, would GHC optimize "msum (map return xs)" to "foldr (mplus
. return) mzero xs"?

> P.S. Oh, and don't get me started about the whole Functor subclass
> thing, and the inclusion of join in the Monad class. Of course I want
> those too. :)

For the recond, my ideal hierarchy would look something like this:

    class Functor f where
        map :: (a -> b) -> f a -> f b
   
    class Functor f => Applicative f where
        return :: a -> f a
        ap     :: f (a -> b) -> f a -> f b
        lift2  :: (a -> b -> c) -> f a -> f b -> f c
       
        ap = lift2 ($)
        lift2 f a b = map f a `ap` b
   
    class Applicative m => Monad m where
        join  :: m (m a) -> m a
        (>>=) :: m a -> (a -> m b) -> m b
       
        join m = m >>= id
        m >>= f = join (map f m)
   
    class Monad m => MonadZero m where
        nothing :: m a
   
    class MonadZero m => MonadPlus m where
        (++) :: m a -> m a -> m a
       
    class MonadPlus m => MonadChoice m where
        msplit :: m a -> m (Maybe (a, m a))

I guess you could put "return" in its own class, PointedFunctor, between
Functor and Applicative, but I haven't seen a reason to. Even without
that, it's probably excessive.

--
David Menendez <[hidden email]> | "In this house, we obey the laws
<http://www.eyrie.org/~zednenem>      |        of thermodynamics!"
_______________________________________________
Haskell mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell
Reply | Threaded
Open this post in threaded view
|

RE: A collection of related proposals regarding monads

Simon Peyton Jones
In reply to this post by Cale Gibbard
 
| What do people think of the following proposal? Remove fail from the
| Monad class. Reinstate MonadZero as a separate class as in Haskell
| 1.4.

This was debated extensively during the Haskell 98 process.  I'm not
saying that we made the right decision then, but here's a link to (a
part of) the discussion.
       
http://www.cs.chalmers.se/~rjmh/Haskell/Messages/Decision.cgi?id=2

 I'm hoping someone has an archive of the whole mailing list they can
let us have (see my other message)

Simon
_______________________________________________
Haskell mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell
Reply | Threaded
Open this post in threaded view
|

RE: A collection of related proposals regarding monads

Simon Peyton Jones
In reply to this post by Cale Gibbard
Simon Marlow pointed me to mail-archive.com.  For some reason Google
doesn't seem to index this site, and it's not an easy site to navigate
around.

However, you can find the monadzero thread mostly on this page:
        http://www.mail-archive.com/haskell@.../thrd16.html
(search for MonadZero)

Simon

| -----Original Message-----
| From: [hidden email]
| [mailto:[hidden email]] On Behalf Of Simon Peyton-Jones
| Sent: 05 January 2006 09:06
| To: Cale Gibbard; Haskell Mailing List
| Subject: RE: [Haskell] A collection of related proposals
| regarding monads
|
|  
| | What do people think of the following proposal? Remove fail from the
| | Monad class. Reinstate MonadZero as a separate class as in Haskell
| | 1.4.
|
| This was debated extensively during the Haskell 98 process.  I'm not
| saying that we made the right decision then, but here's a link to (a
| part of) the discussion.
|
| http://www.cs.chalmers.se/~rjmh/Haskell/Messages/Decision.cgi?id=2
|
|  I'm hoping someone has an archive of the whole mailing list they can
| let us have (see my other message)
|
| Simon
| _______________________________________________
| Haskell mailing list
| [hidden email]
| http://www.haskell.org/mailman/listinfo/haskell
|
_______________________________________________
Haskell mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell
Reply | Threaded
Open this post in threaded view
|

Re: A collection of related proposals regarding monads

Conor McBride
In reply to this post by Wolfgang Jeltsch
Hi folks

Wolfgang Jeltsch wrote:

>Am Mittwoch, 4. Januar 2006 21:54 schrieb Cale Gibbard:
>  
>
>>I personally feel that the inclusion of 'fail' in the Monad class is
>>an ugly solution to the problem of pattern matching, and gives the
>>incorrect impression that monads should have some builtin notion of
>>failure.
>>    
>>
>
>I totally agree!
>  
>

I agree that 'fail' is a bit of a hack, but it has some sort of
rationale to it. Have you forgotten that Haskell has a builtin notion of
failure? If you choose to, you can see the 'fail' method simply as a way
to deprecate this notion (in which well typed programs go with a bang)
for a whimper-like notion of failure provided by the monad. The way it's
used is consistent with that view: 'fail' gets called in situations
which would make a pure [cough] computation bomb. I'm fairly grateful
for it, to be honest.

One thing which might help, if it were possible, is to allow subclasses
to provide default implementations for the methods of superclasses. And
then MonadZero (of which I'm broadly in favour, caveats another time)
can provide a default implementation of fail. Also Monad can provide a
default implementation of Applicative's methods; Applicative can provide
a default implementation of fmap, etc.

Whilst failure remains one of the basic freedoms, I think we should be
ready to live with it.

But yes, it is worth opening this can of worms again, I believe...

All the best

Conor
_______________________________________________
Haskell mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell
Reply | Threaded
Open this post in threaded view
|

Re: A collection of related proposals regarding monads

Cale Gibbard
In reply to this post by Cale Gibbard
On 05/01/06, David Menendez <[hidden email]> wrote:

> Cale Gibbard writes:
>
> > I personally feel that the inclusion of 'fail' in the Monad class is
> > an ugly solution to the problem of pattern matching, and gives the
> > incorrect impression that monads should have some builtin notion of
> > failure. Indeed, it's becoming common to type the result of some
> > function in an arbitrary monad in order to indicate the potential for
> > failure, which is strictly speaking, not the right thing to do. (In a
> > lot of cases, it's going to be no better than complete program
> > failure)
> >
> > We ought to be using MonadZero when we want to express failure, but
> > it's gone!
>
> Yeah, I don't like fail either. In fact, I usually forget to define it,
> even for instances of MonadPlus.
>
> There are typically three ways to indicate error in existing monad
> libraries, e.g.,
>
>     mzero      :: MonadPlus m =>              m a
>     fail       :: Monad m =>        String -> m a
>     throwError :: MonadError e m =>      e -> m a
>
> I would say that fail and throwError essentially have the same meaning,
> but I distinguish them from mzero. To my mind, 'mzero' means "no
> answer", whereas fail and throwError mean "something's wrong".
>
> For example, my implementation of Nondet doesn't backtrack over errors:
>
>     mzero        `mplus` m = m
>     throwError e `mplus` m = throwError e
>
> Should a pattern match failure call mzero or throwError? I was
> originally going to say throwError, but now I'm not so sure. First,
> MonadError is severely non-H98 (fundeps). Second, we would either need
> the error type to belong to some class which includes pattern match
> failures, or have a dedicated throwPatternMatchFailure method in
> MonadError. Finally, you can write sensible code which backtracks on
> pattern-match failure, e.g.,
>
>     do ...
>        Just a <- lookup ...
>        ...
>
> > Even if this translation of do-syntax isn't accepted, I still think
> > that we should have a separate MonadZero.
>
> I like the idea of a separate MonadZero. Do we know why it was combined
> with MonadPlus? Were there efficiency concerns, or did people dislike
> having to declare all those separate instances?
>
> > I'd also like to see the current use of MonadPlus split into MonadElse
> > (or MonadOr) and MonadPlus, as described at the bottom of
> > http://www.haskell.org/hawiki/MonadPlus
> > as it helps to clarify the distinction between backtracking-type
> > failure and immediate failure in types. We could even put this
> > distinction to good use in many monads which do support backtracking
> > anyway:
> >
> > instance MonadElse [] where
> >     [] `morelse` ys = ys
> >     (x:xs) `morelse` ys = (x:xs)
>
> With backtracking monads, you can use Oleg's msplit operator to get
> morelse, soft-cut, and various other operations.
>
>     class MonadPlus m => MonadChoice m where
>         msplit :: m a -> m (Maybe (a, m a))
>
>     mif :: MonadSplit m => m a -> (a -> m b) -> m b -> m b
>     mif p t e = msplit p >>= maybe e (\(x,xs) -> t x `mplus` (xs >>= t))
>
>     a `orElse` b = mif a return b
>
> With non-backtracking monads, you can use throwError or just use mplus
> and remind people that non-backtracking monads don't backtrack.

My main concern is that mplus means fairly different things in
different monads -- it would be good to be able to expect instances of
MonadPlus to satisfy monoid, left zero, and left distribution laws,
and instances of MonadElse to satisfy monoid, left zero and left
catch. It's just good to know what kinds of transformations you can do
to a piece of code which is meant to be generic.

>
> > Lastly, it would be nice to have some standard name for the function:
> > option :: (MonadPlus m) => [a] -> m a
> > option = foldr (mplus . return) mzero
> > which seems to come up quite a bit in my experience with nondet
> > monads.
>
> Mine too. Someone else mentioned "choose", which seems nice. Or,
> "fromList".
>
> Incidentally, would GHC optimize "msum (map return xs)" to "foldr (mplus
> . return) mzero xs"?
>
> > P.S. Oh, and don't get me started about the whole Functor subclass
> > thing, and the inclusion of join in the Monad class. Of course I want
> > those too. :)
>
> For the recond, my ideal hierarchy would look something like this:
>
>     class Functor f where
>         map :: (a -> b) -> f a -> f b
>
>     class Functor f => Applicative f where
>         return :: a -> f a
>         ap     :: f (a -> b) -> f a -> f b
>         lift2  :: (a -> b -> c) -> f a -> f b -> f c
>
>         ap = lift2 ($)
>         lift2 f a b = map f a `ap` b
>
>     class Applicative m => Monad m where
>         join  :: m (m a) -> m a
>         (>>=) :: m a -> (a -> m b) -> m b
>
>         join m = m >>= id
>         m >>= f = join (map f m)
>
>     class Monad m => MonadZero m where
>         nothing :: m a
>
>     class MonadZero m => MonadPlus m where
>         (++) :: m a -> m a -> m a
>
>     class MonadPlus m => MonadChoice m where
>         msplit :: m a -> m (Maybe (a, m a))
>
> I guess you could put "return" in its own class, PointedFunctor, between
> Functor and Applicative, but I haven't seen a reason to. Even without
> that, it's probably excessive.

Well, me too :)  Of course, this sort of thing (especially with the
inclusion of PointedFunctor and Applicative) brings us back to wanting
something along the lines of John Meacham's class alias proposal. I
remember there was a lot of commotion about that and people arguing
about concrete syntax. Was any kind of consensus reached? Do we like
it? How does it fare on this hierarchy? I think we need some provision
for simplifying instance declarations in this kind of situation.

It seems to be a common scenario that you have some finely graded
class hierarchy, and you really want to be able to declare default
instances for superclasses based on instances for subclasses in order
to not force everyone to type out a large number of class instances.

Another idea I've had for this, though I haven't really thought all of
the consequences out, (and I'm looking forward to hearing about all
the awful interactions with the module system) is to allow for default
instances somewhat like default methods, together with potentially a
little extra syntax to delegate responsibility for methods in default
instances. The start of your hierarchy could look something like:

----
class Functor f where
    map :: (a -> b) -> f a -> f b

class Functor f => PointedFunctor f where
    return :: a -> f a

    instance Functor f where
        require map
           -- this explicitly allows PointedFunctor instances to define map

class PointedFunctor f => Applicative f where
    ap     :: f (a -> b) -> f a -> f b
    lift2  :: (a -> b -> c) -> f a -> f b -> f c

    ap = lift2 ($)
    lift2 f a b = map f a `ap` b

    instance Functor f where
        require map

    instance PointedFunctor f => Functor f where
        map = ap . return

    instance PointedFunctor f where
        require return

class Applicative m => Monad m where
    join  :: m (m a) -> m a
    (>>=) :: m a -> (a -> m b) -> m b

    join m = m >>= id
    m >>= f = join (map f m)

    instance Functor m where
        require map

    instance PointedFunctor m where
        require return
        map f x = x >>= (return . f)

    instance PointedFunctor m => Applicative m where
        ap fs xs = do f <- fs
                      x <- xs
                      return (f x)
----

This is a little verbose, but the intent is to have classes explicitly
declare what default instances they allow, and what extra function
declarations they'll need in an instance to achieve them. Here I used
'require' for this purpose.

To be clear, in order to define an instance of Monad with the above
code, along with all its superclasses, we could simply define return
and (>>=) as we would in Haskell 98.

Since a definition of return is then available, an instance of
PointedFunctor would be inferred, with the default instance providing
an implementation of map which satisfies the requirement for the
default Functor instance for PointedFunctor. We then get a default
implementation of Applicative for free due to the instance of
PointedFunctor which we've been able to construct.

An alternate route here would be to just define return, map, and join.
The declaration for map leads to an instance of Functor. The default
instance of PointedFunctor requiring return is used, but the default
implementation of map there is ignored since the user has already
provided an explicit implementation of map. Bind is defined by the
default method provided using join and map, then an instance of
Applicative is constructed using bind and return.

Some things to note:
* The goal should always be to take implementations as early as they
become available, that is, if the user provides something it's taken
first, then each subclass gets a shot at it starting with the
bottom-most classes in the partial order and working up. We'll likely
run into problems with multiple inheritance, but I think that simply
reporting an error of conflicting instance declarations when two
incomparable default instances would otherwise be available is good
enough. Such situations are rare, and the user will always be able to
simply provide their own instance and resolve ambiguities.

* Class constraints on classes (like the Applicative constraint on the
Monad class above) are used to deny instances only after it's been
determined what instances are really available via defaulting. Since
the class definition for Monad provides a default instance for
Applicative, the user can avoid the trouble of declaring one, provided
that an instance for PointedFunctor can be constructed.

* What extra methods might be needed, and what defaults are available
are specified directly in the classes, rather than simply allowing any
instance to come along and mess with the class hierarchy by providing
implementations of arbitrary superclass methods.

 - Cale
_______________________________________________
Haskell mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell
Reply | Threaded
Open this post in threaded view
|

Re: A collection of related proposals regarding monads

Taral
On 1/5/06, Cale Gibbard <[hidden email]> wrote:
> class Applicative m => Monad m where
>     m >>= f = join (map f m)
>
>     instance PointedFunctor m where
>         require return
>         map f x = x >>= (return . f)

Um, it looks like map and (>>=) are recursive...

--
Taral <[hidden email]>
"Computer science is no more about computers than astronomy is about
telescopes."
    -- Edsger Dijkstra
_______________________________________________
Haskell mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell
Reply | Threaded
Open this post in threaded view
|

Re: A collection of related proposals regarding monads

Cale Gibbard
On 05/01/06, Taral <[hidden email]> wrote:

> On 1/5/06, Cale Gibbard <[hidden email]> wrote:
> > class Applicative m => Monad m where
> >     m >>= f = join (map f m)
> >
> >     instance PointedFunctor m where
> >         require return
> >         map f x = x >>= (return . f)
>
> Um, it looks like map and (>>=) are recursive...
>

The intent is that this is similar to other default method
declarations which aren't really recursive, though they may look like
it.

>From the Ord class in the Prelude, we have:

    compare x y
         | x == y    =  EQ
         | x <= y    =  LT
         | otherwise =  GT

    x <= y           =  compare x y /= GT
    ....

Which looks recursive, but it's not, because the user is required to
provide one of the two.
 - Cale
_______________________________________________
Haskell mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell
Reply | Threaded
Open this post in threaded view
|

Re: A collection of related proposals regarding monads

John Meacham
In reply to this post by Cale Gibbard
independent of anything else, giving up error messages on pattern match
failures in do notation is not acceptable. so, if the split were to
happen, having two methods in MonadZero, one which takes a string
argument, would be needed.

        John

--
John Meacham - ⑆repetae.net⑆john⑈
_______________________________________________
Haskell mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell
Reply | Threaded
Open this post in threaded view
|

Re: A collection of related proposals regarding monads

Cale Gibbard
On 05/01/06, John Meacham <[hidden email]> wrote:
> independent of anything else, giving up error messages on pattern match
> failures in do notation is not acceptable. so, if the split were to
> happen, having two methods in MonadZero, one which takes a string
> argument, would be needed.
>
>         John

Why does that have to be built into the class specification? Already,
GHC can give line/column numbers for failed pattern matches in
lambdas, it should be able to do the same for failed pattern matches
in do-syntax in a similar way.

I've never run into a case where I wanted the pattern match failure
error message in the form of a String, wrapped in my monad datatype
directly. If you were going to go that route, wouldn't a proper data
type be preferred anyway, rather than a String? I can't actually do
anything with that string other than to display it, since the report
doesn't standardise its structure.

Further, these cases seem sufficiently rare that if you're interested
in catching information about where the pattern match failed, and
using it in your program, you'd probably be better off actually
handling failure directly, rather than relying on some system built
into do-notation. Otherwise, what's wrong with just letting the thing
throw an exception with a meaningful error message?

The whole reason things switched to this from the way that they were
in Haskell 98 is that apparently people found it confusing that
refutable pattern matches in their do-blocks were causing these extra
MonadZero typeclass constraints. That may be true, though I'm not sure
it's really a language problem so much as a teaching problem.

Further, pattern matches can switch from being irrefutable to
refutable simply by modifing/extending a datatype, and some found it
confusing that they now had a bunch of MonadZero errors cropping up
after extending their type. Now, I think that this is a bit of a poor
example. In reality, you probably want those error messages -- they
serve as a major warning that your code is now probably incorrect, as
it fails to deal with a potential case in your data type. Improve the
error messages in the compiler if it's confusing.

(People should be more aware that algebraic data types are not meant
to be easily extensible. This is another good reason to want proper
records, as they should take care of situations in which one expects
data to be extended after the fact.)

Does anyone have a real example of a nontrivial use of fail where the
string argument was really important and which wouldn't be better
served by MonadError, or by just throwing an exception? Usually when I
define my monads, I try to ignore 'fail' as much as possible. In most
cases, I just leave it completely undefined, as there's no sensible
way to embed strings into my type, or even to fail properly at all.

Personally, I'd be most happy with just going back to the Haskell 1.4
way of handling do-notation, and I see the reasons for adopting 'fail'
in the first place as a bit specious.

 - Cale
_______________________________________________
Haskell mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell