Babra Cunningham
Babra Cunningham

Reputation: 2967

Explicit type conversion?

This is an example function:

import qualified Data.ByteString.Lazy as LAZ
import qualified Data.ByteString.Lazy.Char8 as CHA
import Network.Wreq

makeRequest :: IO (Network.Wreq.Response LAZ.ByteString)
makeRequest = do 
   res <- get "http://www.example.com" 
   let resBody = res ^. responseBody :: CHA.ByteString
   --Do stuff....
   return (res)

I'm struggling to understand the exact purpose of CHA.ByteString in this line:

let resBody = res ^. responseBody :: CHA.ByteString

Is this explicitly stating the type must be CHA.ByteString? Or does it serve another role?

Upvotes: 4

Views: 155

Answers (2)

leftaroundabout
leftaroundabout

Reputation: 120711

Yes, this is just explicitly stating that the type must be CHA.ByteString. This does (by itself) not incur any sort of conversion, it's just a hint for the compiler (and/or the reader) that res must have this type.

These kinds of local annotations are needed when a value is both produced from a function with polymorphic result, and only consumed by functions with polymorphic argument. A simple example:

f :: Int -> Int
f = fromEnum . toEnum

Here, toEnum converts an integer to a arbitrary enumerable type – could for instance be Char. Whatever type you'd choose, fromEnum would be able to convert it back... trouble is, there is no way to decide which type should be used for the intermediate result!

No instance for (Enum a0) arising from a use of ‘fromEnum’
The type variable ‘a0’ is ambiguous
Note: there are several potential instances:
  instance Integral a => Enum (GHC.Real.Ratio a)
    -- Defined in ‘GHC.Real’
  instance Enum Ordering -- Defined in ‘GHC.Enum’
  instance Enum Integer -- Defined in ‘GHC.Enum’
  ...plus 7 others
In the first argument of ‘(.)’, namely ‘fromEnum’
In the expression: fromEnum . toEnum
In an equation for ‘f’: f = fromEnum . toEnum

For some simple number classes, Haskell has defaults, so e.g. fromIntegral . round will automatically use Integer. But there are no defaults for types like ByteString, so with a polymorphic-result function like responseBody, you either need to pass the result to a monomorphic function that can only accept CHA.ByteString, or you need to add an explicit annotation that this should be the type.

Upvotes: 6

Ingo
Ingo

Reputation: 36339

The notation x :: T reads expression x has type T

This may be necessary in the presence of type classes and higher ranked types to enable the compiler to type check the program. For example:

main = print . show . read $ "1234"

is ambiguous, since the compiler cannot know which of the overloaded read functions to use.

In addition, it is possible to narrow the type the compiler would infer. Example:

1 :: Int

Finally, a type signature like this is often used to make the program more readable.

Upvotes: 4

Related Questions