Haskell extract hostname from URLType

Viewed 163

I'm using Network.URL and importing a URL from a String:

url_type $ fromJust $ importURL "https://google.com/"
Absolute (Host {protocol = HTTP True, host = "google.com", port = Nothing})

Now to get the host I can pattern match and use the host function:

url' :: URLType -> Host
url' (Absolute host) = host

host $ url' $ url_type $ fromJust $ importURL "https://google.com"

That certainly would work, but URLType is defined as:

data URLType
  = Absolute Host
  | HostRelative
  | PathRelative

In the url' function, only Absolute Host is taking into consideration, and it'll fail if the url is HostRelative or PathRelative. How can I make a url' function to extract the host whether given or not?

1 Answers

Original answer

I think all you need is making your url' a monadic function which returns a Maybe Host, composing url_type with return to make another monadic function which returns a Monad (which in this specific use happens to be a Maybe too), and then making a monadic chain using the >>= operator:

import Network.URL
import Data.Maybe (fromJust)

url' :: URLType -> Maybe Host
url' (Absolute host) = Just host
url' _ = Nothing

output :: Maybe Host
output = importURL "https://google.com" >>= return . url_type >>= url'

Notice that output is a Maybe Host. You cannot get a Host if you accept that the whole operation can fail. If you can assume the operation cannot fail, then you're ok with fromJust, but you would be asking the question, if this was the case, I believe.

Improvement

In a comment is pointed out that >> return . url_type is bit gross. I'm new to Haskell, so I basically wrote the first thing that came to my mind. And probably I chose a monadic style because I've been playing with and looking at example of monads more than applicatives. Plus, I'm still to new to Haskell to comfortably use functorial, applicative, and monadic styles together: I just didn't think of writing the expression as suggested in the comment, as mine looked ok to me. Below I "reintepret" the comment based on my knowledge.

What is a bit ugly is the part importURL "https://google.com" >>= return . url_type, because the operator >>=, whose type is in general Monad m => m a -> (a -> m b) -> m b, is "forcing" its second argument to return a Monad, even though the "wannabe" second argument url_type doesn't, thus forcing us to compose it with return in order to honor the a -> m b part of the signature. What we would really prefer, is to sent url_type inside the monad provided by importURL "bla", operate on that inside, and leave the result there, inside the monad; i.e. we would in fact just use importURL "bla" as Functor (which it is, because it is a Monad). So how do we do it? We do so by means of fmap:

output = (fmap url_type $ importURL "https://google.com") >>= url'

where I had to use $ to prevent fmap from eagerly swallowing importURL, thus leaving the string alone. We've not finished yet: fmap has an infix equivalent, <$> (notice that $ and <$> have nothing to do with each other), so we can rewrite the above as

output = url_type <$> importURL "https://google.com" >>= url'

At this point, I'd say, we have an expression which is even uglier than before, with the input in the middle of it. But =<< to the resque. It is essentially flip (>>=), so we can flip the two operands, thus obtaining the expression that was suggested in the comment:

output = url' =<< url_type <$> importURL "https://google.com"
Related