Please Buy now at Leanpub
Written and illustrated by GetContented.
Published on 2016-06-05.
putStrLn. It lets us output any
String on the screen.
What if we want to print out a number instead of a
We have the following definition for an expression that is adding two numbers, and we want to print the resulting value out in a program.
number :: Integer number = 100390 + 29389
Integer, by the way, is the type of unbounded whole numbers in Haskell. Unbounded means they have no upper or lower limit (or bounds). In contrast, the
Int that this document is being prepared on now is 9223372036854775807.)
So, we know that the
putStrLn :: String -> IO () function takes a
String, and leaves us with an
IO () value.
We don’t have a
String, though, we have an
Integer value. How can we match these up so we can print out our number on the screen?
In order to answer that, let’s first look at the type of the
(+) function. We know from experience that it takes a number and another number and returns their sum as a number, but the actual type isn't something we’ve seen, and includes some special new stuff for us. Let’s look:
(+) :: Num a => a -> a -> a
Ok, breathe. Let’s first look at the right side:
a -> a -> a. Why all the
Well, we know from all the
(->)’s that this means it’s a function that takes two values of type “
a”, and returns a third “
a”. What is “
a”, though? It’s what’s called a type variable. That means it can be any type. If it starts with a lowercase letter, it’s a type variable. If it starts with a capital, it’s an actual type, or a typeclass, which we'll explain soon (Num is a typeclass in
Num a => a).
One important point about type variables is that while they can be any type at all, all the “
a”s must still be the same type as each other when using the function! So if we used an
Integer value as our first argument, so saying that the first type
(+), then we would need to use an
Integer as our second argument, too:
-- both types must be the same, -- so this will be fine: goodNumber = (3 :: Integer) + (5 :: Integer) -- this would cause a type error willNotWork = (3 :: Int) + (5 :: Integer)
If we don't specify the type of our numbers, Haskell's type inference works it out for us, which saves a lot of time and hassle.
Ok, so here is another way to write the
(+) function, which shows you that variables don’t necessarily have to be named
-- it's a lot smaller to write -- 'a' than 'theNumber' (+) :: Num theNumber => theNumber -> theNumber -> theNumber
Now we have to think about the “
Num a =>” part. That can be read as “
a is constrained to types which are instances of the
Num typeclass”. This mouthful means that the
(+) function can take two arguments of any type at all (here we’re naming them
a), as long as that type (again, here called
a) is an instance of a typeclass called
Num. Luckily for us, all numbers are!
A typeclass is not a concrete type like
String. It’s a way of tagging many types (a “class” of them, if you like) so that we can have functions or values that work with many similar types that do similar things, but that are actually different. Let's take a look:
-- a small int 5: intFive = 5 :: Int -- a "floating-point" value of 10.3 floatTenPointThree = 10.3 :: Float -- add them together with (+). This will not work... -- because both types are concretized (or specialized) errorResult = intFive + floatTenPointThree -- add them together with (+). This will work result = (fromIntegral intFive) + floatTenPointThree -- the result is 15.3
Here we’re using
fromIntegral to build an “unspecialised”
Num a => a version of the
Int value of
intFive so we can subsequently add it to
floatTenPointThree. The value
5 :: Int is not of type
Float, so the types won’t match unless we do this. However, if either of the values are of type
Num a => a, then
(+) will typecheck because it can match both types together (by concretizing the
Num a => a type to
Integer types are all instances of the
Num typeclass. There are many numeric types in Haskell such as these. To be able to do arithmetic functions on different numeric typed values, they are tagged as
Num which allows us to define each of the simple arithmetic functions for each type differently, but use them all with the same name and interchange values.
This “tagging” is called making a type an instance of a typeclass. When a programmer does this, they provide a definition against the particular type, for the functions that the typeclass requires.
So we can see that
(+) can add a
Float or an
Integer to a
Num a => a without a problem. The
Num typeclass is, in this way, like a kind of contract that programmers of a type can decide to kind of "subscribe to" which gives them the ability to write implementations of the functions that the
Num typeclass provides. In turn, the typeclass system gives that type the ability to work with all the other types that are instances of that typeclass.
Num typeclass means there actually isn’t only one definition for the functions for addition:
negate, etc but rather that each type — that is, each instance of
Num — has its own definition for each of these functions.
What does all of this have to do with printing our number on the screen? Remeber, that problem we started the chapter with?
Well, there’s a typeclass called
Show (with a big S), and this provides a single function:
show (with a small s), that can take any instance of
Show, and makes a
String version of it. Let’s look at the type of the
-- takes a "showable" thing -- and returns a String show :: Show a => a -> String
We see that show is a function which takes a single argument of any type (the “
a” type variable above) constrained to the
Show typeclass, and returns a
String. That single argument is anything that has an instance of
Show defined for it. We know this because of the
Show a => constraint.
Printing things to the screen is such a common thing to do that many types have an instance of
Show already, including of course,
Int, so getting back to the first program of this chapter, we can just apply
show to our
Integer and then pass it to
putStrLn. Let’s see how:
number :: Integer number = 100390 + 29389 main :: IO () main = putStrLn (show number)
Parentheses are needed on
show number because
putStrLn only takes one argument, and the function application precedence rules mean that taking them off would give it two. Precedence is a fancy-pants word that simply means “which things come before or after which other things”. If we left off the parentheses, we would have this:
putStrLn show number, which Haskell would see as “apply
putStrLn to the value
show, and then apply that to the value
putStrLn takes only
String values, and
show is a function, so that would definitely be a type error.
We have one last trick up our sleeves to show you (pardon the pun). It’s the
putStrLn :: String -> IO (), but rather than taking a String, it can take a value whose type is any instance of
Show! Let’s see a version of our program that uses
print :: Show a => a -> IO () rather than
number :: Integer number = 100390 + 29389 main :: IO () main = print number
See if you can work out what the following program does.
number1 :: Num a => a number1 = 1 + 5 + 7 + 3 + 2 number2 :: Num a => a number2 = number1 * number1 main :: IO () main = print number2
Hint: don’t get caught up by the types of the values. This will probably be confusing, and should confuse you at least a little bit. We’ll explain what’s going on later, however, the important thing is just see if you can work out what the program will do when you run it.
If you’ve enjoyed reading this, please consider purchasing a copy at Leanpub today
Please follow us, and check out our videos Follow @HappyLearnTutes
Also, Volume 2 is now in beta and being written! Show your support and register your interest at its Leanpub site.