Types are a way of categorizing values.
There are several basic types.
There are those for numbers: integers, fractional values, etc.
There are booleans, specifically True
or False
.
Then, there are the types that we are talking about in this article: Char
or character
and String
.
A String is a list of characters.
You can check the type of a value with the :type
or :t
command:
Prelude> :type 'r'
'r' :: Char
Notice that the character is in single quotes. This is a requirement for a character.
Char
includes alphabetic characters, Unicode, symbols, etc.
As mentioned in a previous article, ::
is read as "this has the type". In this case, 'r' has the type Char
.
Let's try a string of text:
Prelude> :type "Hello!"
"Hello!" :: [Char]
String is a type synonym for a list of characters.
The square brackets around Char
are syntactic sugar for a list.
Strings are wrapped in double quotes.
Printing "Hello!":
Prelude> print "Hello!"
"Hello!"
You can print different types using the print
function.
These are prints specific to String
:
Prelude> putStrLn "Hello!"
Hello!
Prelude> putStr "Hello!"
Hello!Prelude>
putStr
and putStrLn
are similar to print
, but they print the String
without double quotes.
This is because they have a different type than print
does.
Enter the following into strings.hs
:
module Strings where
stringPrinter :: IO ()
stringPrinter = putStrLn "Hello!"
Load it into GHCi:
Prelude> :load strings.hs
[1 of 1] Compiling Strings ( strings.hs, interpreted )
Ok, one module loaded.
*Strings> stringPrinter
Hello!
*Strings>
stringPrinter
has the type IO ()
.
IO stands for input/output.
Printing to the screen requires that the output of a module be wrapped in the IO
type.
In the REPL, it implicitly understands and implements IO
without you having to specify it.
Begin another file called strings2.hs
:
module Strings2 where
main :: IO ()
main = do
putStrLn "Let's count!"
putStr "zero, one"
putStr "two, three, and"
putStrLn " four!"
*Strings2> :load strings2.hs
[1 of 1] Compiling Strings2 ( strings2.hs, interpreted )
Ok, one module loaded.
*Strings2> main
Let's count!
zero, one, two, three, and four!
The do
syntax allows for sequencing actions.
It is most commonly used to sequence the actions that make up your program.
The Ln
in putStrLn
indicates that it starts a new line.
Concatenating means to link together.
The result of concatenating two strings is a combined version.
Make a new file called concatenating.hs
:
module Concatenating where
greeting :: String
greeting = "Hello" ++ " Haskell"
hello :: String
hello = "Hello"
haskell :: String
haskell = "Haskell"
main :: IO ()
main = do
putStrLn greeting
putStrLn combined
where
combined = concat [hello, " ", haskell]
*Concatenating> main
Hello Haskell
Hello Haskell
concat
.(++) is an infix operator. When we need to refer to an infix operator in an position that is not infix, we put parenthesis around it.
concat
is a normal function. Parentheses aren't necessary.
Let's take a look at the types for (++) and concat
:
++ has the type [a] -> [a] -> [a]
concat has the type [[a]] -> [a]
If you're using GHC 7.10 or higher, the type signature is Foldable t => t [a]
. For our current purposes, just read it as being [[a]]
.
concat
says that it takes a list of lists as input and will return a list.
The result from concat
is flattened.
Prelude> concat [[0, 1, 2], [3], [4], [5, 6]]
[0,1,2,3,4,5,6]
Prelude> concat ["I", " like ", "turtles"]
"I like turtles"
Let's break down the (++) type:
(++) :: [a] -> [a] -> [a]
-- [1] [2] [3]
[a]
. This type is a list of elements of some type a
. The function does not know what a
is. a
will be made known as some point in the life-cycle.[a]
. A list of elements of some type a
. Because the variables are the same a == a
, they must remain the same.[a]
.The type variable a
is polymorphic because it can represent various types.
Again, a
must equal a
:
Prelude> "hello" ++ " haskell" -- same types
"hello haskell"
Prelude> "hello" + [1] -- different types, will not work
<interactive>:17:1: error:
• No instance for (Num [Char]) arising from a use of ‘+’
• In the expression: "hello" + [1]
In an equation for ‘it’: it = "hello" + [1]
<interactive>:17:12: error:
• No instance for (Num Char) arising from the literal ‘1’
• In the expression: 1
In the second argument of ‘(+)’, namely ‘[1]’
In the expression: "hello" + [1]
Top-level declarations are not nested within anything else, and they are in scope throughout the whole module.
Local declarations are nested within some other expression and are not visible outside that expression.
module TopOrLocal where
topLevelFunction :: Integer -> Integer
topLevelFunction x =
x + y + topLevelNumber
where
y = 5
topLevelNumber :: Integer
topLevelNumber = 5
*TopOrLocal> topLevelFunction 5
15
You can access topLevelFunction
and topLevelNumber
anywhere in the module. You could also import them from another module.
y
is effectively invisible outside of topLevelFunction
; you cannot access it anywhere else in the module.
Since a String
is a list, we can use standard list operations on it:
-- The (:) operator builds a list
Prelude> 'C' : "layton"
"Clayton"
-- head returns the first element of a list
Prelude> head "Clayton"
'C'
-- tail returns the list with the head chopped off
Prelude> tail "Clayton"
"layton"
-- take returns the specified number of elements from a list starting from the left side
Prelude> take 2 "Clayton"
"Cl"
-- drop returns the remainder of the list after the specified numbers of elements has been dropped
Prelude> drop 4 "Clayton"
"ton"
-- The infix operator, (!!), returns the element at the specified index (indices start from 0)
Prelude> "Clayton" !! 0 -- same as: (!!) "Clayton" 0
'C'
Haskell List Functions - https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-List.html
Haskell Book - https://haskellbook.com/