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:

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:

        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:

        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.

        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)