Now that we have written a palindrome-testing function, what we want to do next is turn this into a program instead of just a function.

About I/O

A Haskell program needs to have an “entry point” called main. Normally, when we write Haskell, the order that we add stuff to a file doesn’t matter. We can define functions and types in any order that we think of them. But when we call main to run a program – within the definition of main, the ordering does matter, because calling main sets off a sequence of events. So everything that we write after the main name has to be in the order we want it to happen when we run the program. To help with this, we can use some special syntax (the do keyword).

We’re going to see that now, but we won’t really be examining it yet or getting into the mechanics of it until later. When we do get to those explanations, you’ll see that it’s not really special to main, exactly, it’s special to something called monads and, in this case, the IO type that appears in the type of every main action. The type declaration usually looks like this:

main :: IO ()

IO stands for input/output, and it’s something all programs have to be able to do: take inputs, print outputs to a screen, sending messages over a network, and so forth. Doing things like this is what makes code a program, rather than just a collection of functions. In Haskell, IO is a type which is quite different from what you might expect in other programming languages in which I/O is implicit and pervasive.

isPalindrome, having the type String -> Bool, doesn’t do any sort of I/O. When we use it the REPL, GHCi does some I/O for us (it prints the result), but isPalindrome itself, having the type it has, can’t prompt a user for input, print to the screen by itself, etc. Those are IO things.

Reading input

OK, enough about that. Our main action here will be very small for the time being. We only have one function, after all! But we want to take “user” input – the users will only be ourselves for now, but that’s plenty of users for our first program.

We’re going to use another Prelude function in order to do this. Let’s look at it for a moment. It’s called getLine and we’ll ask to see its type first.

λ> :type getLine
getLine :: IO String

So here we see that IO type that we mentioned – getLine is an IO String. The String is what we want to work with for our isPalindrome function, but since we want the String input to come from user input this is what we need – the ‘I’ of ‘IO’ stands for ‘input’. Let’s see what happens in GHCi when we use getLine.API documentation for getLine

Type getLine at the prompt and hit return.

λ> getLine

The cursor moves to the next line down, and it’s waiting for you to type something. Type in your name. You don’t need to add quotation marks or anything. Hit return.

λ> getLine

GHCi runs an implicit “print” command that prints the input to the screen. You provided some input, and now GHCi prints some output. It will add quotation marks to your name because the result of that printing is a String, and strings in Haskell have quotation marks around them.

Using the input

OK, so the first thing we know we need to do in our main action is use this getLine to get some user input, and we know we can get a String from that. We can’t directly get a String input from it, though. An IO String is not the same thing as a String! GHCi will tell us that if we try to apply isPalindrome to getLine.

λ> isPalindrome getLine

<interactive>:20:14: error:
    • Couldn't match type ‘IO String’ with ‘[Char]’
      Expected type: String
        Actual type: IO String

The expected type is the type that isPalindrome needs as input; the actual type is, well, the actual type of the value we tried to pass to it. The type isPalindrome expects as input is String but getLine delivers an IO String. Those are not the same type. So we need to figure out how to make those work together.

The first part of the solution to this problem is going to be found in that “special syntax” for main we mentioned earlier. So let’s start on that. do is the keyword (not main) that allows us to use this special syntax, so we need to make sure to get that in there. The type of main in Haskell is almost always IO ()The empty parentheses represents a value called “unit”, so we pronounce IO () as “IO unit”. which tells us that main does input/output and its only “return value” is output to a screen or some other kind of effect (in this program, the effect is printing the result of the function evaluation to the screen).

The first thing we’ll do within the do block is pick a variable name – we’ve chosen to name it word – then write this leftwards arrow <-, which is part of the special do syntax, and getLine.

main :: IO ()
main =
    word <- getLine

This left arrow binds the String from getLine to this variable name, word. Now the IO hasn’t disappeared, but using this arrow to bind just the String to a variable name allows us to pass that String as an input to a function like isPalindrome.

main :: IO ()
main =
    word <- getLine
    isPalindrome word

But this doesn’t quite work yet, because we haven’t sufficiently dealt with the IO that, as we said, didn’t really go away.

Printing output

As we said, GHCi does the printing for you when you evaluate stuff at the prompt by implicitly running a print command.

λ> isPalindrome "tacocat"

When we apply a function in the REPL, the isPalindrome function itself isn’t what’s printing the output. GHCi is doing that implicitly, and that’s why we see True on the screen. But our main action needs to represent an entire program that can do everything it needs to do on its own without GHCi, so we have to handle that print command explicitly. So, let’s look at that next.

Query the type of print.API documentation for print

λ> :type print
print :: Show a => a -> IO ()

We can read this, we’ve seen this kind of type before. print is a function that can take as input one value of any type that is a member of the Show typeclass, and it will return this IO () – it will return an effect. As we said about the type of main the practical meaning of IO () for us is that it returns an output to the screen.

Let’s stop and think for a moment. print can print anything to the screen as long as it is a member of the Show class. We can tell you that most built-in types in Haskell are members of the Show class (with one very important exception that we’ll discuss later), but what we want to teach you is how to find out for yourself if the print function fits our need.

Querying instances

We saw in an earlier session that there’s an :info command that can give us information. That seems helpful, but we need to think about what kind of information we need. Let’s be clear about the pieces to this puzzle we’re trying to put together:

  • The output of isPalindrome is a Bool value.
  • The final type of main is IO () (output to the screen).
  • print can take some kinds of inputs and return printing to the screen, so it might work as the way to glue the output of isPalindrome into the IO structure we need.

We can only know if it will work if we know whether Bool is a member of the Show class that print needs as input. So, that’s what we need to ask. We can query it as :info Bool or :info Show – either one will get us the information we want.

λ> :info Bool
data Bool = False | True 	-- Defined in ‘GHC.Types’
instance Eq Bool -- Defined in ‘GHC.Classes’
instance Ord Bool -- Defined in ‘GHC.Classes’
instance Show Bool -- Defined in ‘GHC.Show’
λ> :info Show
class Show a where
  showsPrec :: Int -> a -> ShowS
  show :: a -> String
  showList :: [a] -> ShowS
  {-# MINIMAL showsPrec | show #-}
  	-- Defined in ‘GHC.Show’
instance (Show a, Show b) => Show (Either a b)
  -- Defined in ‘Data.Either’
instance Show a => Show [a] -- Defined in ‘GHC.Show’
instance Show Word -- Defined in ‘GHC.Show’
instance Show GHC.Types.RuntimeRep -- Defined in ‘GHC.Show’
instance Show Ordering -- Defined in ‘GHC.Show’
instance Show a => Show (Maybe a) -- Defined in ‘GHC.Show’
instance Show Integer -- Defined in ‘GHC.Show’
instance Show Int -- Defined in ‘GHC.Show’
instance Show Char -- Defined in ‘GHC.Show’
instance Show Bool -- Defined in ‘GHC.Show’

What we’re looking for is this line, which appears in the output of both :info queries above:

instance Show Bool

This means “Bool has an instance of Show” – In other words, Bool is a member of the Show class. So thanks to print’s polymorphic type, it will work for us!

Finishing up

Revise main by applying print to the result of isPalindrome word – that is the result we want printed to the screen. We’ll put parentheses around that expression and print on the outside.

main :: IO ()
main =
    word <- getLine
    print (isPalindrome word)

Load the file into GHCi (or reload, if you already have it loaded) and try it out! Run main, enter some input which may or may not be a palindrome, and press enter.

λ> :reload
[1 of 1] Compiling Main    ( palindrome.hs, interpreted )
Ok, one module loaded.

λ> main
it :: ()

λ> main

Make sure you have what we’ve done so far typed into a file; we’ll be expanding upon it in the next sessions.

Join Type Classes for courses and projects to get you started and make you an expert in FP with Haskell.