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)