Opened 2 years ago

Last modified 9 months ago

#13511 new feature request

ApplicativeDo return case doesn't handle lets

Reported by: mnislaih Owned by:
Priority: normal Milestone: 8.10.1
Component: Compiler Version: 8.0.2
Keywords: ApplicativeDo Cc: simonmar
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: None/Unknown Test Case:
Blocked By: Blocking:
Related Tickets: Differential Rev(s):
Wiki Page:

Description

We ran into this bug in production, this is a simplified reproduction. The following program will not type check, requiring an unnecessary Monad instance:

{-# LANGUAGE ApplicativeDo, GeneralizedNewtypeDeriving #-}

import Data.Functor.Identity
import Data.Monoid

newtype A x = A (Identity x) deriving (Functor, Applicative)

shouldWork :: A ()
shouldWork = do
  a <- pure ()
  b <- pure ()
  let ab = a <> b
  return ab
pepe:~/code/snippets$ ghci ApplicativeDoBug.hs 
GHCi, version 8.0.2: http://www.haskell.org/ghc/  :? for help
[1 of 1] Compiling Main             ( ApplicativeDoBug.hs, interpreted )

ApplicativeDoBug.hs:10:14: error:
    • No instance for (Monad A) arising from a do statement
    • In the expression:
        do { a <- pure ();
             b <- pure ();
             let ab = a <> b;
             return ab }
      In an equation for ‘shouldWork’:
          shouldWork
            = do { a <- pure ();
                   b <- pure ();
                   let ab = ...;
                   return ab }
Failed, modules loaded: none.

There is a simple workaround, which worked for us in production:

workaround :: A ()
workaround = do
  a <- pure ()
  b <- pure ()
  return $
    let ab = a <> b
    in ab

I asked in #ghc and it seems this is not yet fixed in HEAD.

Change History (13)

comment:1 Changed 2 years ago by RyanGlScott

Cc: simonmar added
Keywords: ApplicativeDo added

comment:2 Changed 2 years ago by rwbarton

Type: bugfeature request

The user's guide makes it pretty clear that a case like this is not currently expected to work.

comment:3 Changed 2 years ago by mnislaih

The user's guide doesn't explicitly mention let bindings, it only says:

In general, the rule for when a do statement incurs a Monad constraint is as follows. If the do-expression has the following form:

do p1 <- E1; ...; pn <- En; return E

where none of the variables defined by p1...pn are mentioned in E1...En, then the expression will only require Applicative. Otherwise, the expression will require Monad. The block may return a pure expression E depending upon the results p1...pn with either return or pure.

Or were you referring to something else ?

comment:4 Changed 2 years ago by bgamari

Milestone: 8.4.1
Summary: ApplicativeDo incorrectly requiring MonadApplicativeDo return case doesn't handle lets

Perhaps the users guide language could be improved. Currently the return case is a rather narrow, purely syntactic rewrite which does not handle lets.

comment:5 Changed 2 years ago by mnislaih

If we stick to the definition of ApplicativeDo in the users guide, and given that join is not needed here, I think it is fair to say that this is a bug:

The language option -XApplicativeDo enables an alternative translation for the do-notation, which uses the operators <$>, <*>, along with join as far as possible.

comment:6 Changed 2 years ago by simonpj

simonmar: do you know what is happening here?

comment:7 Changed 2 years ago by simonmar

The do expression doesn't match the requirements as described in the docs, so I don't think it's a bug. Perhaps we could expand the heuristics to handle this case, but then the docs would get more complicated, and where does it end?

comment:8 Changed 2 years ago by mnislaih

Fair enough. I wasn't aware that Applicativedo didn't handle let bindings, and neither was anyone in #ghc for what it's worth. The docs ought to make this more evident.

comment:9 Changed 2 years ago by simonmar

It depends what you mean by "doesn't handle". It does have some heuristics to collect let bindings and prevent them from causing spurious dependencies, but that's aimed more at using Applicative with Monad, rather than the Applicative-only context.

If there's a way to expand the desugaring that would also be simple to describe, then I wouldn't be averse to adding it.

comment:10 Changed 20 months ago by bgamari

Milestone: 8.4.18.6.1

This ticket won't be resolved in 8.4; remilestoning for 8.6. Do holler if you are affected by this or would otherwise like to work on it.

comment:11 Changed 20 months ago by chshersh

I experienced similar issue with -XApplicativeDo while using -XRecordWildCards and optparse-applicative library. What I wanted to write:

data Options = Options { reportTime :: Integer }

optionsParser :: Parser Options
optionsParser = do
    hours   <- option auto $ long "hours"
    minutes <- option auto $ long "minutes"
    let reportTime = hours * 60 + minutes
    pure Options{..}

What helped is to write it like this:

data Options = Options { reportTime :: Integer }

optionsParser :: Parser Options
optionsParser = do
    hours   <- option auto $ long "hours"
    minutes <- option auto $ long "minutes"
    pure Options{ reportTime = hours * 60 + minutes }

comment:12 Changed 15 months ago by bgamari

Milestone: 8.6.18.8.1

These will not be addressed in GHC 8.6.

comment:13 Changed 9 months ago by osa1

Milestone: 8.8.18.10.1

Bumping milestones of low-priority tickets.

Note: See TracTickets for help on using tickets.