© Stefania Loredana Nita and Marius Mihailescu 2019
Stefania Loredana Nita and Marius MihailescuHaskell Quick Syntax Referencehttps://doi.org/10.1007/978-1-4842-4507-1_7

7. Functions

Stefania Loredana Nita1  and Marius Mihailescu1
(1)
Bucharest, Romania
 

In the previous chapters, you mainly worked with predefined functions from different libraries in Haskell. Now, it’s time to write your own functions. In this chapter, you will learn about pattern matching, guards, clauses, higher-order functions, and lambda expressions used in functions.

Let’s start with a simple function that adds two numbers.
add :: Integer -> Integer -> Integer
add x y =  x + y
main = do
   putStrLn "Adding two numbers:"
   print(add 3 7)
On the first line is the function declaration, which tells you the type of inputs and outputs, and on the second line is the function definition. As in the other programming languages, Haskell begins to compile the code from the main function. The result is as follows:
Adding two numbers:
10

Haskell Files

Haskell files can have an .hs or .lhs extension. The .lhs files have a literate format; in other words, they include commentary, and the lines that begin with > are considered part of the code. To write a Haskell file, open a text editor and place your code in it; then save the file with an .hs extension. To load a file into GHCi, first change the current directory to the directory that contains the .hs file using the :cd command . So, if you save your Haskell files in the path C:Haskell; you can proceed as follows:
Prelude> :cd C:Haskell
In C:Haskell, let’s say you have the file Main.hs. You can load this file into GHCi using the :load or :l command.
Prelude> :l Main.hs
[1 of 1] Compiling Main             ( Main.hs, interpreted )
Ok, one module loaded.
*Main>

Note that Prelude> changes to *Main> .

GHC finds the file that contains the module M by looking at the name of the file. So, module M must be in the M.hs file. This rule is broken when the files are loaded using the :load command . In this case, the module name can be different from the file name, but the file must contain that module. If a source file is modified, it needs to be saved and then reloaded using :reload , as shown here:
 *Main> :reload
[1 of 1] Compiling Main             ( Main.hs, interpreted )
Ok, one module loaded.
To import external modules, such as modules from libraries (as you will see in the following chapters), you use the import command.
Prelude> import Data.Maybe
Prelude Data.Maybe>
Of course, modules can be imported in other modules (for example, in source files) using the following syntax:
module Module_Name where
    import Module1
    import Module2
...

The command import has some variations that allow you to import some parts of a module, give a module an alias, and so on.

As with many programming languages, Haskell has a function called main, which specifies that an I/O action will be performed. Open the Main.hs file and type the following:
main = putStrLn "Learning about Haskell main function"
Then save it, load it, and run it.
Prelude> :l Main.hs
[1 of 1] Compiling Main             ( Main.hs, interpreted )
Ok, one module loaded.
*Main> main
Learning about Haskell main function
To write more I/O actions in main, you use the do block . Open the Main.hs file again and type the following:
main = do
    putStrLn "Are you enjoying Haskell?"
    answer <- getLine
    putStrLn ("You answered: " ++ answer)
Save the file and reload it. Then run it again, as shown here:
*Main> :reload
*Main> main
Are you enjoying Haskell?
sure
You answered: sure

Pattern Matching

Pattern matching means the program checks whether some data matches a certain pattern and then acts accordingly. A function can have different bodies for different patterns. Pattern matching can be applied on any data type. Check out this example:
day :: (Integral a) => a -> String
day 1 = "Monday"
day 2 = "Tuesday"
day 3 = "Wednesday"
day 4 = "Thursday"
day 5 = "Friday"
day 6 = "Saturday"
day 7 = "Sunday"
day x = "The week has only 7 days!"
You’ll get the following:
*Main> day 7
"Sunday"
*Main> day 0
"The week has only 7 days!"
*Main> day 3
"Wednesday"
When day is called, the matching begins from the bottom. When there is a match, the corresponding body is chosen. Note that the function contains a default pattern. If you do not put a default pattern and if the function’s parameter does not fall into any defined pattern, then you will get an exception. If you comment out or delete the last line of the function, you get the following result:
*Main> day 0
"*** Exception: Days.hs:(2,1)-(8,16): Non-exhaustive patterns in function day
This function can be written using an if..then..else statement, but it would be pretty difficult to follow. This statement is self-explanatory: if the condition is met, then choose the value from the first branch, else choose the value from the second branch. Here’s a short example:
numbers :: (Integral a) => a -> String
numbers x =
 if x < 0 then "negative"
 else "positive"
*Main> numbers 8
"positive"
*Main> numbers  (-1)
"negative"

For negative numbers, don’t forget to put them inside parentheses.

Case Expressions

Case expressions are simple. The general syntax is as follows:
case expression of pattern -> result
                   pattern -> result
                   ...
Let’s write the day function using case, as shown here:
day :: (Integral a) => a -> String
day x = case x of 1 -> "Monday"
                  2 -> "Tuesday"
                  3 -> "Wednesday"
                  4 -> "Thursday"
                  5 -> "Friday"
                  6 -> "Saturday"
                  7 -> "Sunday"
                  _ -> "The week has only 7 days!"
Then write the following:
Prelude> :l Day.hs
[1 of 1] Compiling Main             ( Day.hs, interpreted )
Ok, one module loaded.
*Main> day 7
"Sunday"
*Main> day 10
"The week has only 7 days!"

Guards

You can use guard to test whether a value has a certain property. Guards are alternative to else..if statements , making the code easier to write and follow. Let’s continue with a sign example, shown here:
sign :: (RealFloat a) => a -> String
sign x
 | x < 0     = "negative"
 | x == 0    = "zero"
 | otherwise = "positive"
*Main> sign 3.5
"positive"
*Main> sign (-7)
"negative"

As you can see in this example, to use guards, you mark them with pipes. The evaluation begins with the bottom expression and continues until a match is found. Note that we have the default case marked by the otherwise keyword; any value that does not meet any of the previous conditions will get the default. You can think of guards as Boolean expressions, where otherwise is always evaluated as True. Pay attention to what’s after the parameters in the function definition. Note that you don’t put an equal sign.

Clauses

In this section, you’ll learn about the where clause and let..in clause.

Let’s begin with the where clause . As example, think about the quadratic equation defined as ax2 + bx + c = 0. The solutions of the equation depend on a discriminant computed as Δ = b2 − 4ac. If Δ > 0, you will obtain two real solutions: x1, x2. If Δ = 0, you will obtain two real identical solutions: x1 = x2. Otherwise, the equation does not have real solutions. You can think of the three parameters a,b,c as a triple and the solutions as a pair. Observe that you need Δ in more parts of the algorithm. You can resolve it as follows:
quadraticEq :: (Float, Float, Float) -> (Float, Float)
quadraticEq (a, b, c) = (x1, x2)
 where
  x1 = (-b - sqrt delta) / (2 * a)
  x2 = (-b + sqrt delta) / (2 * a)
  delta = b * b - 4 * a * c
Let’s test this.
*Main> quadraticEq (1, 2, 1)
(-1.0,-1.0)
*Main> quadraticEq (1, 1, 1)
(NaN,NaN)
*Main> quadraticEq (1, 4, 0)
 (-4.0,0.0)

The names defined in the where clause are used only in the function, so they will not affect other functions or modules. Be careful about the indentation; all the names should be aligned properly. Do not use the Tab key to add large spaces. Also, the names defined in the body of a function pattern will not be visible by the body of the same function for another pattern. If you need a name to be visible in all patterns, you need to declare it globally. You can also define a function in the where clause.

Another useful clause is let..in . Suppose you want to compute the volume of a quadrilateral pyramid. You know that $$ V=frac{A_bcdot h}{3} $$, where Ab is the area of the basis. You can proceed as follows:
pyramivVol :: (RealFloat a) => a -> a -> a
pyramivVol l h =
 let area = l^2
 in  (area * h)/3
The names defined in the let clause are available just in the in. You can find the volume also using the where clause. The difference between them is that the let bindings are expressions themselves, while where bindings are syntactic constructs. In previous chapters, you used the let..in clause to define functions and constants in GHCi. There, you ignored the in part, which means that the names were available through the entire session. This clause can be used almost anywhere, not only in functions.
Prelude> 5 * (let a = 2 in a^2) + 7
27
Prelude> [let cube x = x^3 in (cube 6, cube 3)]
[(216,27)]
Prelude>  (let x = 100; y = 200 in x+y, let l="Anne "; f = "Scott" in l ++ f)
(300,"Anne Scott")

The in part can miss when the visibility of the names are predefined. This clause can be also used in list comprehension, inside a predicate, but the names will be visible only in that predicate.

Lambda Expressions

There are times when you need to use a function just once in your entire application. To not complete with names, you can use anonymous blocks called lambda expressions. A function without definition is called a lambda function, and it is marked by the character. Let’s take a look:
main = do
   putStrLn "The square of 2 is:"
   print ((x -> x^2) 2)
Inside print, we defined the expression x->x^2 and call it for the value 2. The output is as follows:
The square of 2 is:
4

Infix Functions

In Haskell, the functions are called by typing the name of the function, followed by the arguments. But there are functions that don’t follow this rule, such as mathematical operators. Actually, you can call an operator, followed by the two arguments, but this is unnatural. Therefore, a function that stands between its two arguments is called an infix function .
Prelude> (+) 2 2
 4
 Prelude> (*) 5 6
 30
Let’s define an infix function, as shown here:
Prelude> let concatAndPrint a b = putStrLn $ (++) a b
 Prelude> concatAndPrint "abc" "def"
 abcdef
 Prelude> "abc" `concatAndPrint` "def"
 abcdef

Note that the infix function is marked by `` signs and is between its two arguments. Usually, an infix function is used with two parameters.

Higher-Order Functions

“A higher-order function is a function that takes other functions as arguments or returns a function as result.”1 Let’s define the following function:
multiplyList m [] = []
multiplyList m (y:ys) = m*y : multiplyList m ys
*Main> multiplyList 3 [2, 5, 7]
[6,15,21]

Note that it takes two inputs, a number and a list, and the output is a list resulting from multiplying the number with the elements.

A higher-order function is multiplyListBy3.
multiplyListBy3 = multiplyList 3
*Main> multiplyListBy3 [10, 20, 30]
[30,60,90]

The function multiplyListBy3 takes now one input, namely, a list, because you know that m=3 when multiplyList is called inside multiplyListBy3.

Summary

In this chapter, you learned how to write source code files and how to use them in GHCi. In addition, you learned how to use pattern matching and guards in functions and what the difference is between them. You also saw that in Haskell you can use a case expression. Next, you learned how to use clauses in functions. You also learned about lambda expressions and how you can write a function that is used just once in your entire application. Finally, you worked with infix functions and higher-order functions.

References

  1. 1.
     
  2. 2.
     
  3. 3.

    P. Hudak, J. Peterson, and J. Fasel, A Gentle Introduction to Haskell 98 (1999)

     
  4. 4.

    R. Bird, Introduction to Functional Programming Using Haskell, vol. 2 (Prentice Hall Europe, 1998)

     
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.150.219