Learning Haskell: Basic Datatypes

posted 2 years ago

What are Types?

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.

Anatomy of a Data Declaration

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]
  1. The type constructor for the datatype. In this case, it is Bool. This is name of the type and shows up in type signatures.
  2. Data constructor for the value False.
  3. | or pipe indicates a sum type or logical or. A Bool is either True or False.
  4. Data constructor for the value 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’

Numeric Types

Integral Numbers

  1. Int: A fixed-precision integer. Limited by a minimum and maximum which means that it cannot be arbitrarily small or large.
  2. Integer: Also for integers, but can support arbitrarily large numbers.

Fractional Numbers

  1. Float: A single-precision floating point number.
  2. Double: A double-precision floating point number. It has double the amount of bits with which to describe numbers as Float.
  3. Rational: A fractional number that represents a ratio of two integers.
  4. 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

Comparing Values

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 Cool

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.

Conditionals with if-then-else Chaining

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

Tuples

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

Lists are another type used to contain multiple values within a single value.

Lists are different from tuples in three key ways:

  1. All elements of a list must be the same type.
  2. Lists have their own distinct [] syntax.
  3. The number of values that will be in the list isn't specified in the type.

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.

Sources

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/