Book Image

Haskell Cookbook

Book Image

Haskell Cookbook

Overview of this book

Haskell is a purely functional language that has the great ability to develop large and difficult, but easily maintainable software. Haskell Cookbook provides recipes that start by illustrating the principles of functional programming in Haskell, and then gradually build up your expertise in creating industrial-strength programs to accomplish any goal. The book covers topics such as Functors, Applicatives, Monads, and Transformers. You will learn various ways to handle state in your application and explore advanced topics such as Generalized Algebraic Data Types, higher kind types, existential types, and type families. The book will discuss the association of lenses with type classes such as Functor, Foldable, and Traversable to help you manage deep data structures. With the help of the wide selection of examples in this book, you will be able to upgrade your Haskell programming skills and develop scalable software idiomatically.
Table of Contents (13 chapters)

Working with pure functions and user-defined data types

In this recipe, we will work with pure functions and define simple user-defined data types. We will represent a quadratic equation and its solution using user-defined data types. We will then define pure functions to find a solution to the quadratic equation.

Getting ready

  1. Create a new project quadratic using the following command:
      stack new quadratic

Change into the project folder.

  1. Delete src/Lib.hs and create a new file src/Quadratic.hs to represent the quadratic equation and its solution.
  1. Open the quadratic.cabal file, and in the section library, replace Lib by Quadratic in the tag exposed-modules:
      library
hs-source-dirs: src
exposed-modules: Quadratic
build-depends: base >= 4.7 && < 5
default-language: Haskell2010

How to do it...

  1. Open Quadratic.hs and add a module definition to it:
       module Quadratic where
  1. Import the standard module Data.Complex to help us represent a complex solution to the quadratic equation.
  2. Define the data type to represent the quadratic equation:
      data Quadratic = Quadratic { a :: Double, b :: Double, 
c :: Double }
deriving Show

This represents the quadratic equation of the form a∗x2+b∗x+c=0a∗x2+b∗x+c=0. a, b, and c represent the corresponding constants in the equation.

  1. Define the data type for representing the root:
      type RootT = Complex Double

This represents that the complex data type parameterized by Double. RootT is synonymous to type Complex Double (similar to typedef in C/C++).

  1. A quadratic equation has two roots, and hence we can represent both the roots as follows:
      import Data.Complex
type RootT = Complex Double
data Roots = Roots RootT RootT deriving Show
  1. Implement the solution. We will take a top-down approach to create a solution. We will define a top-level function where we will implement a function assuming lower level details:
      roots :: Quadratic -> Roots

This shows that the roots function takes one argument of type Quadratic, and returns Roots.

  1. Implement three cases mentioned next:
      -- | Calculates roots of a polynomial and return set of roots
roots :: Quadratic -> Roots

-- Trivial, all constants are zero, error roots are not defined
roots (Quadratic 0 0 _) = error "Not a quadratic polynomial"

-- Is a polynomial of degree 1, x = -c / b
roots (Quadratic 0.0 b c) = let root = ( (-c) / b :+ 0)
in Roots root root

-- b^2 - 4ac = 0
roots (Quadratic a b c) =
let discriminant = b * b - 4 * a * c
in rootsInternal (Quadratic a b c) discriminant

We have referred to the rootsInternal function, which should handle case A, B, and C for the case III.

  1. Implement the rootsInternal function to find all roots of the quadratic equation:
      rootsInternal :: Quadratic -> Double -> Roots
-- Discriminant is zero, roots are real
rootsInternal q d | d == 0 = let r = (-(b q) / 2.0 / (a q))
root = r :+ 0
in Roots root root

-- Discriminant is negative, roots are complex
rootsInternal q d | d < 0 = Roots (realpart :+ complexpart)
(realpart :+ (-complexpart))
where plusd = -d
twoa = 2.0 * (a q)
complexpart = (sqrt plusd) / twoa
realpart = - (b q) / twoa

-- discriminant is positive, all roots are real
rootsInternal q d = Roots (root1 :+ 0) (root2 :+ 0)
where plusd = -d
twoa = 2.0 * (a q)
dpart = (sqrt plusd) / twoa
prefix = - (b q) / twoa
root1 = prefix + dpart
root2 = prefix - dpart

Open src/Main.hs. We will use the Quadratic module here to solve a couple of quadratic equations. Add the following lines of code in Main.hs:

      module Main where

import Quadratic
import Data.Complex

main :: IO ()
main = do
putStrLn $ show $ roots (Quadratic 0 1 2)
putStrLn $ show $ roots (Quadratic 1 3 4)
putStrLn $ show $ roots (Quadratic 1 3 4)
putStrLn $ show $ roots (Quadratic 1 4 4)
putStrLn $ show $ roots (Quadratic 1 0 4)
  1. Execute the application by building the project using stack build and then executing with stack exec – quadratic-exe in the command prompt. You will see the following output:
      Roots ((-2.0) :+ 0.0) ((-2.0) :+ 0.0)
Roots ((-1.5) :+ 1.3228756555322954) ((-1.5) :+
(-1.3228756555322954))
Roots ((-1.5) :+ 1.3228756555322954) ((-1.5) :+
(-1.3228756555322954))
Roots ((-2.0) :+ 0.0) ((-2.0) :+ 0.0)
Roots ((-0.0) :+ 2.0) ((-0.0) :+ (-2.0))

How it works...

A quadratic equation is represented by ax^2 + bx + c = 0. There are three possible cases that we have to handle:

Case Condition Root 1 Root 2 Remarks
I a = 0 and b = 0 ERROR ERROR
II a = 0 x = -c/b Not applicable Linear equation
III a and b are non-zero, delta = b2 - 4ac
III-A delta = 0 -b/(2a) -b/(2a) Perfect square
III-B delta > 0 (-b+sqrt(delta))/(2a) (-b-sqrt(delta))/(2a) Real roots
III-C delta < 0 (-b+sqrt(delta))/(2a) (-b-sqrt(delta))/(2a) Complex roots

We will define a module at the top of the file with the Quadratic module where the name of the module matches file name, and it starts with a capital letter. The Quadratic module is followed by the definition of module (data types and functions therein). This exports all data types and functions to be used by importing the module.

We will import the standard Data.Complex module. The modules can be nested. Many useful and important modules are defined in the base package. Every module automatically includes a predefined module called Prelude. The Prelude exports many standard modules and useful functions. For more information on base modules, refer to https://hackage.haskell.org/package/base.

The user-defined data is defined by the keyword data followed by the name of the data type. The data type name always start with a capital letter (for example, data Quadratic).

Here, we will define Quadratic as follows:

    data Quadratic = Quadratic { a :: Double, b :: 
Double, c :: Double }
deriving Show

There are several things to notice here:

  • The name on the left-hand side, Quadratic, is called type constructor. It can take one or more data types. In this case, we have none.
  • The name Quadratic on the right-hand is called data constructor. It is used to create the value of the type defined on the left-hand side.
  • {a :: Double, b :: Double, c :: Double } is called the record syntax for defining fields. a, b and c are fields, each of type Double.
  • Each field is a function in itself that takes data type as the first argument and returns the value of the field. In the preceding case, a will have the function type Quadratic -> Double, which means a will take the value of type Quadratic as the first argument and return the field a of type Double.
  • The definition of data type is followed by deriving Show. Show is a standard type class in Haskell and is used to convert the value to String. In this case, Haskell can automatically generate the definition of Show. However, it is also possible to write our own definition. Usually, the definition generated by Haskell is sufficient.

We will define root as type Complex Double. The data type Complex is defined in the module Data.Complex, and its type constructor is parameterized by a type parameter a. In fact, the Complex type is defined as follows:

    data Complex a = a :+ a

There are several things to notice here. First, the type constructor of Complex takes an argument a. This is called type argument, as the Complex type can be constructed with any type a.

The second thing to note is how the data constructor is defined. The data constructor's name is not alphanumeric, and it is allowed.

Note that the data constructor takes two parameters. In such a case, data constructor can be used with infix notation. That is, you can use the constructor in between two arguments.

The third thing to note is that the type parameter used in the type constructor can be used as a type while defining the data constructor.

Since our quadratic equation is defined in terms of Double, the complex root will always have a type Complex Double. Hence, we will define a type synonym using the following command:

    type RootT = Complex Double

We will define two roots of the equation using the following command:

data Roots = Roots RootT RootT deriving Show

Here, we have not used the record syntax, but just decided to create two anonymous fields of type RootT with data constructor Roots.

The roots function is defined as follows:

    roots :: Quadratic -> Roots

It can be interpreted as the roots function has a type Quadratic -> Roots, which is a function that takes a value of type Quadratic and returns a value of type Roots:

  • Pattern matching: We can write values by exploding data constructor in the function arguments. Haskell matches these values and then calls the definition on the right-hand side. In Haskell, we can separate the function definition using such matching. Here, we will use pattern matching to separate cases I, II, and III, defined in the preceding section. The case I can be matched with value (Quadratic 0 0 _) where the first two zeros match fields a and b, respectively. The last field is specified by _, which means that we do not care about this value, and it should not be evaluated.
  • Raising an error: For the first case, we flag an error by using function error. The function error takes a string and has a signature (error :: String -> a) which means that it takes a String and returns value of any type a. Here, it raises an exception.
  • let .. in clause: In the case II as mentioned in the preceding section, we use let ... in clause.
        let root = ( (-c) / b :+ 0)
in Roots root root

Here, the let clause is used to bind identifiers (which always start with a lowercase letter; so do function names). The let clause is followed by the in clause. The in clause has the expression that is the value of the let…in clause. The in expression can use identifiers defined in let. Furthermore, let can bind multiple identifiers and can define functions as well.

We defined rootsInternal as a function to actually calculate the roots of a quadratic equation. The rootsInternal function uses pattern guards. The pattern guards are explained as follows:

  • Pattern guards: The pattern guards are conditions that are defined after a vertical bar | after the function arguments. The pattern guard defines a condition. If the condition is satisfied, then the expression on the right-hand side is evaluated:
        rootsInternal q d | d == 0 = ...

In the preceding definition, d == 0 defines the pattern guard. If this condition is satisfied, then the function definition is bound to the expression on the right-hand side.

  • where clause: The rootsInternal function also uses the where clause. This is another form of the let…in clause:
        let <bindings>
in <expression>

It translates to the following lines of code:

        <expression>
where
<bindings>

In Main.hs, we will import the Quadratic module and use the functions and data type defined in it. We will use the do syntax, which is used in conjunction with the IO type, for printing to the console, reading from the console, and, in general, for interfacing with the outside world.

The putStrLn function prints the string to the console. The function converts a value to a string. This is enabled because of auto-definition due to deriving Show.

We will use a data constructor to create values of Quadratic. We can simply specify all the fields in the order such as, Quadratic 1 3 4, where a = 1, b = 3, and c = 4. We can also specify the value of Quadratic using record syntax, such as Quadratic { a = 10, b = 30, c = 5 }.

Things are normally put in brackets, as shown here:

   putStrLn (show (roots (Quadratic 0 1 2)))

However, in this case, we will use a special function $, which simplifies the application of brackets and allows us to apply arguments to the function from right to left as shown:

    putStrLn $ show $ roots (Quadratic 0 1 2)

Source formatting

You must have also noticed how the Haskell source code is formatted. The blocks are indented by white spaces. There is no hard and fast rule for indenting; however, it must be noted that there has to be a significant white space indent for a source code block, such as the let clause or the where clause. A simple guideline is that any block should be indented in such a way that it is left aligned and increases the readability of the code. A good Haskell source is easy to read.