24
votes

I wrote some code a while ago which uses OverloadedStrings to create ByteStrings from hex-encoded String literals, which it decodes using the functions provided by base16-bytestring. This worked fine, but it seems I didn't understand it as well as I thought.

The thing that has me completely confused is this. Why does

{-# LANGUAGE OverloadedStrings #-}

import Data.ByteString.Base16 ()
import qualified Data.ByteString as B

plaintext = "The message" :: B.ByteString

main = print plaintext

compile and run OK, but if I remove the import for Data.ByteString.Base16 then it fails to compile (similar to this question):

test.hs:6:13:
No instance for (Data.String.IsString B.ByteString)
  arising from the literal `"The message"'

According to the Haskell Wiki, an import like this is "useful for only importing instances of typeclasses and nothing else", but as far as I can see, the base16-bytestring source code doesn't define any typeclass instances, just the encode and decode functions.

How does the import provide the necessary instance of IsString for the code to compile?

1

1 Answers

33
votes

In Haskell, typeclass instances are always exported and imported--you can't hide them. This is usually referred to as the "open world assumption".

This means that typeclass instances are also exported transitively: if you import a library with an instance, it gets exported from your module as well.

In this case, the IsString instance is in Data.ByteString.Char8, which is imported by Data.ByteString.Base16. You should be able to replace your import with:

import Data.ByteString.Char8 ()

There is a nice SO question giving some information on the open world assumption, if you're interested.