Search code examples
haskelltypeserror-handlingtype-systemsoption-type

Can't match type Maybe vs not Maybe on Network.URI


Say I want to parse an environment variable, and default to localhost in its absence, using https://hackage.haskell.org/package/network-2.3/docs/Network-URI.html

I can write a function like so:

parseRabbitURI :: Text -> Maybe URI.URI
parseRabbitURI "" = URI.parseURI "amqp://guest:guest@127.0.0.1/"
parseRabbitURI uri = (URI.parseURI . toS) uri

This works fine. Now let's say I want to handle errors. I note that parseURI returns a Maybe so ostensibly I just need to pattern match on that. So I create a custom Error:

data CustomError = MyCustomError Text deriving(Show)

I create a helper function:

parsedExtractor
  :: MonadError CustomError.MyCustomError m
  => Text
  -> Maybe URI.URI
  -> m(URI.URI)
parsedExtractor originalString Nothing = throwError $ FlockErrors.FailedToParseURI originalString
parsedExtractor _ (Just uri) = do
  pure uri

Finally, I modify my initial function:

parseRabbitURI :: MonadError CustomError.MyCustomError m => Text -> m(URI.URI)
parseRabbitURI "" = URI.parseURI "amqp://guest:guest@127.0.0.1/" >>= parsedExtractor "amqp://guest:guest@127.0.0.1/"
parseRabbitURI uri = (URI.parseURI . toS) uri >>= parsedExtractor uri

This fails to compile with:

• Couldn't match type ‘URI.URI’ with ‘Maybe URI.URI’
  Expected type: URI.URI -> Maybe URI.URI
    Actual type: Maybe URI.URI -> Maybe URI.URI
• In the second argument of ‘(>>=)’, namely ‘parsedExtractor uri’
  In the expression: (URI.parseURI . toS) uri >>= parsedExtractor uri
  In an equation for ‘parseRabbitURI’:
      parseRabbitURI uri
        = (URI.parseURI . toS) uri >>= parsedExtractor uri

| 23 | parseRabbitURI uri = (URI.parseURI . toS) uri >>= parsedExtractor uri |

And for the life of me I can't figure out why. If the initial implementation returns a Maybe, why is it converting to an unwrapper URI.URI which I can't then pass?

Crucially, when I change the pattern on parsedExtractor to expect a string, it also fails to compile with the inverse message (

Couldn't match expected type ‘URI.URI’
                      with actual type ‘Maybe URI.URI’

I feel like I must be missing something completely fundamental. What is going on here?


Solution

  • And for the life of me I can't figure out why. If the initial implementation returns a Maybe, why is it converting to an unwrapper URI.URI which I can't then pass?

    To refer the definition of >>= from Control.Monad, it has type signture:

    (>>=) :: m a -> (a -> m b) -> m b
    

    Now, compare to the expression:

    (URI.parseURI . toS) uri >>= parsedExtractor uri
    

    We have:

    m a        ~ (URI.parseURI . toS) uri
    (a -> m b) ~ parsedExtractor uri
    

    Since (URI.parseURI . toS) uri return type Maybe URI.URI and Maybe is an instance of Monad, so

    m a ~ Maybe URI.URI 
    

    and

    (a -> m b) ~ (URI.URI -> m b) 
    

    and m b can be infered to m (URI.URI), so the function (i.e. parsedExtractor uri) after >>= expected to has type as:

    (URI.URI -> m (URI.URI))
    

    But actual is not.