Search code examples
haskellbytestringhttp-conduit

Clumsy Looking Type Signature for ByteStrings in Haskell HTTP Response


I'm experimenting with the http-conduit library and have this simple example:

#!/usr/bin/env stack
{- stack
    --resolver lts-7.12
    --install-ghc
    runghc
    --package http-conduit
-}
{-# LANGUAGE OverloadedStrings #-}

import Network.HTTP.Conduit
import Data.ByteString.Lazy.Internal

getUrl :: IO (Data.ByteString.Lazy.Internal.ByteString) ---eeew!
getUrl = do
  resp <- simpleHttp "http://www.stackoverflow.com"
  return resp

I understand from this post that I should prefer a response as a ByteString to a [Char] or String. I assumed the OverloadedStrings pragma might make this less of an issue, but doesn't appear to change with respect to my out-type.

The function works fine and dutifully prints out a simple http response for the homepage of SO, but that type signature looks really ugly:

getUrl :: IO (Data.ByteString.Lazy.Internal.ByteString)

And I have to say I see very few things like that from internet examples (we've got more dots than an elliptical Java import, man). Is that right? If I want to return a reponse and then start parsing on it, say with HXT or tagsoup or attoparsec, is this the right approach or type signature?

I notice, for example, this gets even uglier when I start to add the ability to take arguments, such as supplying a different URL:

import Network.HTTP.Conduit
import Data.ByteString.Lazy.Internal

-- alright, first arg is now string for my url...
getUrl :: String -> IO (Data.ByteString.Lazy.Internal.ByteString)
getUrl url = do
  resp <- simpleHttp url
  return resp


main :: IO (ByteString)  -- what?!  inside the () ?
main = do
  getUrl "https://www.stackoverflow.com"

This seems unwholesome. How should I understand how to structure this correctly?


Solution

  • You can always write types with their qualified path. However, as you've already imported Data.ByteString.Lazy.Internal and unless you have some other ByteString type in scope (like the strict one) you can simply omit the Data.ByteString.Lazy.Internal:

    getUrl :: IO ByteString
    

    Also, unless you have some particular need to import Internal, I recommend you just import Data.ByteString.Lazy (which also exports the lazy ByteString type you are using). If you have both strict and lazy ByteString in scope, I'd import them qualified:

     import qualified Data.ByteString as BS
     import qualified Data.ByteString.Lazy as BL
    

    Then BS.ByteString is the strict type and BL.ByteString the lazy one - no need to write out the full Data.ByteString.ByteString or Data.ByteString.Lazy.ByteString.

    It is also worth mentioning (thanks @duplode) that either of the bytestring modules is usually best imported qualified since they defines a whole bunch of functions that clash with the Prelude (and with each other).

    Finally, note that parens around a type by itself does nothing. (ByteString) and ByteString are identical. As such, I would not include them.


    Unrelated to your question, but some remarks on your monadic code:

    • Every time you write something like

      do x <- e
         return x
      

      it can be replace with just e.

    • Every time you write something like

      do e
      

      it can be replaced with e.