Loading Different Strings

In the last couple of articles we've explored the Text and ByteString types. These provide alternative string representations that are more efficient when compared to the basic "list of characters" definition. But we've also seen that it can be a minor pain to remember all the different ways we can convert back and forth between them. And in fact, not all conversions are actually guaranteed to work the way we would like.

Now let's imagine our program deals with input and output. Now as a beginner, it's easy to think that there are only a couple primary functions for dealing with input and output, and that these only operate on String values:

putStrLn :: String -> IO ()

getLine :: IO String

If your program actually makes use of Text, you might end up (as I have in many of my programs) constantly doing something like T.pack <$> getLine to read the input as a Text, or putStrLn (unpack text) to print it out.

When you're dealing with files, this makes it more likely that you'll mess up lazy vs. strict semantics. You'd have to know that readFile is lazy, and readFile', an alternative, is strict.

readFile :: FilePath -> IO String

readFile' :: FilePath -> IO String

But you don't need to resort to always using the String type! All of our alternative types have their own IO functions. You just have to know which modules to use, and import them in a qualified way!

With Text, there are separate IO modules that offer these options for us:

import qualified Data.Text as T
import qualified Data.Text.IO as TI
import qualified Data.Text.Lazy as TL
Import qualified Data.Text.Lazy.IO as TLI

TI.putStrLn :: T.Text -> IO ()
TI.getLine :: IO T.Text

TLI.putStrLn :: TL.Text -> IO ()
TLI.getLine :: IO TL.Text

The modules also contain functions for reading and writing files as well, all with the appropriate Text types.

With ByteString types, these functions live in the primary module, and they aren't quite as exhaustive. There is putStr, but not putStrLn (I'm not sure why).

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

B.putStr :: B.ByteString -> IO ()
B.getLine :: IO B.ByteString

BL.putStr :: BL.ByteString -> IO ()
BL.getLine :: IO BL.ByteString

So, when you're program is using the more sophisticated string types, use the appropriate input and output mechanisms to spare yourself the inefficiencies (both performance-wise and in terms of code clarity) that come with using String as a go-between!

If you've never really written a Haskell program with input and output before, you'll need to have some idea of how Stack works so you can get all the pieces working. Take our free Stack mini-course to learn how everything works, and stay tuned for more tips on Strings and other useful areas of Haskell!

Previous
Previous

Fusion Powered Strings!

Next
Next

Taking a Byte out of Strings