I was working on the following small fragment of code:
import           Control.Monad
import           Data.Aeson
import qualified Data.HashMap.Strict as HashMap
import           Data.Map (Map)
import qualified Data.Map as Map
import           GHC.Generics
-- definitions of Whitelisted, WhitelistComment and their FromJSON instances
-- omitted for brevity
data Whitelist = Whitelist
  { whitelist :: Map Whitelisted WhitelistComment
  } deriving (Eq, Ord, Show)
instance FromJSON Whitelist where
  parseJSON (Object v) =
    fmap (Whitelist . Map.fromList) . forM (HashMap.toList v) $ \(a, b) -> do
      a' <- parseJSON (String a)
      b' <- parseJSON b
      return (a', b')
  parseJSON _ = mzero
when I realised that I can rewrite the do block in applicative style:
instance FromJSON Whitelist where
  parseJSON (Object v) =
    fmap (Whitelist . Map.fromList) . forM (HashMap.toList v) $ \(a, b) ->
      (,) <$> parseJSON (String a) <*> parseJSON b
  parseJSON _ = mzero
and with that I could also replace forM with for. Before making the change above I switched to for first:
instance FromJSON Whitelist where
  parseJSON (Object v) =
    fmap (Whitelist . Map.fromList) . for (HashMap.toList v) $ \(a, b) -> do
      a' <- parseJSON (String a)
      b' <- parseJSON b
      return (a', b')
  parseJSON _ = mzero
and to my surprise this still compiled. Given the definition of  for:
for :: (Traversable t, Applicative f) => t a -> (a -> f b) -> f (t b)
I thought the Applicative constraint would prevent me from using do notation / return in the action passed to for.
I'm clearly missing something fundamental here, either in terms of what for signature really implies, or how the code I posted is interpreted by the compiler, and would appreciate any help understanding what's going on.