Types are how we group a set of values together that share something in common.
Every value has a type.
Mathematical sets are similar to types.
Data declarations are how datatypes are defined.
Type constructors are the name of the type and is capitalized.
Data constructors are the values that inhabit the type they are defined in.
Let's take the Bool
datatype for instance:
-- The defintion of Bool
data Bool = False | True
[1] [2] [3] [4]
Bool
. This is name of the type and shows up in type signatures.False
.|
or pipe indicates a sum type or logical or
. A Bool
is either True
or False
.True
.The entire thing is called a data declaration.
Data declarations do not always follow the same pattern.
For example, some data declarations may have arguments, or they may use and instead of or.
You can find the datatype definition for built-in types using the :info
command:
Prelude> :info Bool
data Bool = False | True -- Defined in ‘GHC.Types’
Integral Numbers
Int
: A fixed-precision integer. Limited by a minimum and maximum which means that it cannot be arbitrarily small or large.Integer
: Also for integers, but can support arbitrarily large numbers.Fractional Numbers
Float
: A single-precision floating point number. Double
: A double-precision floating point number. It has double the amount of bits with which to describe numbers as Float
.Rational
: A fractional number that represents a ratio of two integers.Scientific
: A space efficient scientific number type. Represented using scientific notation. These numeric datatypes all have instances of a typeclass called Num
.
A typeclass is a way of adding functionality to types that is reusable across all the types that have instances of that typeclass.
The Num
typeclass provides standard operators such as (+), (-), (*)
.
An instance defines how the functions work for that specific type.
You can find the bounds of any value that has an instance of this particular typeclass by using maxBound
and minBound
:
Prelude> minBound :: Int
-9223372036854775808
Prelude> maxBound :: Int
9223372036854775807
We can compare values to determine if they are equal, less than, or greater than:
Prelude> 5 > 2 -- 5 is greater than 2
True
Prelude> 5 < 4 -- 5 is less than 4
False
Prelude> 5 == 5 -- 5 is equal to 5
True
Prelude> 5 /= 1 -- 5 is not equal to 1
True
Let's look at the type information for some of these infix operators:
Prelude> :info (==)
(==) :: Eq a => a -> a -> Bool
Prelude> :info (>)
(>) :: Ord a => a -> a -> Bool
We have some typeclass constraints again.
Eq
is a typeclass that includes every thing that can be compared and determined to be equal in value.
Ord
is a typeclass that includes all things that can be ordered.
Letters can be ordered and compared too:
Prelude> 'a' == 'z'
False
Prelude> 'a' == 'a'
True
Prelude> 'a' < 'b'
True
Prelude> 'a' > 'b'
False
Prelude> "Clayton" > "Seb"
False
Bool
is included with the Prelude.
Remember, Bool
has two type constructors: False
and True
.
There are a few infix operators that deal directly with boolean logic.
First, boolean conjunction (&&)
:
Prelude> True && True
True
Prelude> (8 > 4) && (4 > 5)
False
When using (&&)
, both have to be true.
Boolean disjunction (||)
:
Prelude> True || False
True
Prelude> (5 == 5) || (5 > 10)
True
Prelude> (8 < 4) || (1 > 2)
False
When using (||)
, only one has to be true.
Haskell doesn't have 'if' statements, rather it has if expressions.
Prelude> let t = "Truthy"
Prelude> let f = "Falsey"
Prelude> if True then t else f
"Truthy"
if True
evaluates to True
, so it returns t
.
The basic structure looks like this:
if CONDITION
then A
else B
If the condition evaluates to True
, then A
is the result, otherwise B
is the result.
A more advanced example:
module AddTwentyIfCool where
originalNumber :: Integer
originalNumber = 5
addIfCool :: String -> Integer
addIfCool coolness =
if cool coolness
then originalNumber + 20
else originalNumber
where
cool val =
val == "This is REALLY cool"
*AddTwentyIfCool> addIfCool "This is REALLY cool"
25
*AddTwentyIfCool> addIfCool "Not cool"
5
A tuple is a type that allows you to store and pass around multiple values within a single value.
We refer to tuples by the number of values in the tuple. If there are two values, then it is a two-tuple.
Let's grab some information on a tuple:
Prelude> :info (,)
data (,) a b = (,) a b
There are two parameters: a
and b
.
These are applied to concrete values at the term level.
This is a product type where you must supply both arguments to construct a value.
Let's do some basic operations on a tuple:
Prelude> let intTup = (1 :: Integer, 2 :: Integer) -- Define a two-tuple
Prelude> :t intTup
intTup :: (Integer, Integer) -- Type
Prelude> fst intTup -- Grabs the first argument in the tuple
1
Prelude> snd intTup -- Grabs the second argument in the tuple
2
Prelude> import Data.Tuple -- Importing Data.Tuple because swap isn't available in Prelude
Prelude Data.Tuple> swap intTup -- Swapping the arguments
(2,1)
Let's implement fst
and snd
for ourselves:
fst' :: (a, b) -> a
fst' (a, b) = a
snd' :: (a, b) -> b
snd' (a, b) = b
This uses a concept called pattern matching.
Lists are another type used to contain multiple values within a single value.
Lists are different from tuples in three key ways:
[]
syntax.Here is an example of a list:
Prelude>let awesomeName = "Clayton"
Prelude>let coolNames = [awesomeName, "Seb", "Helena"]
Prelude>coolNames
["Clayton","Seb","Helena"]
Prelude>:t coolNames
coolNames :: [[Char]]
coolNames
is a list of Char
values because it is a list of strings, and, as we know, String
is a type alias for [Char]
.
I'm going to write another article solely on lists because they are so complex.
There are various functions and constructs that can be used with lists.
Lists - https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-List.html
Tuples - https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-Tuple.html
Bool - https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-Bool.html
if-else-then - https://wiki.haskell.org/If-then-else
Haskell Book - https://haskellbook.com/