This section will be used to familiarize you with the Haskell interactive command line. Before we introduce you to the interactive command line, we will introduce the optional configuration file that you can create in ~/.ghci
in your home folder. We have configured ours with the following code:
:set prompt "> "
The preceding code tells the interactive command line to display a single >
as the prompt. You can start the interactive command line using the ghci
command. Here is what you will see when the command line is started:
$ ghci GHCi, version 7.4.1: http://www.haskell.org/ghc/ :? for help Loading package ghc-prim ... linking ... done. Loading package integer-gmp ... linking ... done. Loading package base ... linking ... done. >
You can execute simple equations using either the familiar infix notation, or the functional notation:
> 2 + 2 4 > (+) 2 2 4 > 2 + 4 * 5 22 > (+) 2 $ (*) 4 5 22
Note that we need to use $
here in order to tell Haskell that the (*) 4 5
multiplication portion is an argument to the (+) 2
addition portion.
This introductory problem will serve as a way of explaining the features of the Haskell language that are used repeatedly in this book. The problem is that we wish to know the location of the vowel characters of a given word, for example, in the word apple
, there are two vowels (the first and fifth letters). Given a string apple
, we should return a list of [1, 5]. We will go through the thought process of solving this problem and turn our solution into a function. You can use the elemIndices
function that can be found in the Data.List
module, but we will chose not to do so for teaching purposes.
First, we will declare a variable to store our word. In this example, we will use the word apple
:
> let word = "apple"
We will assign a number to each letter in our word using zip
and an infinite list of numbers. The zip
function will perform a pair-wise merge of two lists to create a list of tuples. A tuple is a type of list structure that can store types in a heterogeneous manner. In the following code, we will combine the integer and character types:
> zip [1..] word [(1,'a'),(2,'p'),(3,'p'),(4,'l'),(5,'e')]
The expression [1..]
is an infinite list of numbers. If you type this in the interactive command line, numbers will appear until you decide to stop it. By using it in conjunction with zip
, we only take what we need. There are five letters in apple
. So, we only take five elements from our infinite list. This is an example of lazy evaluation at work.
Next, we will filter our list to remove anything that is not a vowel character. We will do this with the help of the filter
function, which requires us to pass a lambda function with the rule that defines what is allowed in the list of values:
> filter ((_, letter) -> elem letter "aeiouAEIOU") $ zip [1..] word [(1,'a'),(5,'e')]
Let's take a closer look at the lambda expression that is within the parentheses that begin with (
and end with )
. Using the :t
option, we can inspect how Haskell interprets this function:
> :t (_, letter) -> elem letter "aeiouAEIOU" (_, letter) -> elem letter "aeiouAEIOU" :: (t, Char) -> Bool
The function requires a pair of values. The first value in the pair is identified with _
, which indicates that this is a wild card type. You can see that Haskell identifies it with the t
generic type. The second value in the pair is identified by letter
, which represents a character in our string. We never defined that letter was a Char
type, but Haskell was able to use type inference to realize that we were using the value in a list to search for the value among a list of characters and thus, this must be a character. This lambda expression calls the elem
function, which is a part of the Data.List
module. The elem
function returns a Bool
type. So, the return type of Bool
is also inferred. The elem
function returns true if a value exists in a list. Otherwise, it returns false.
We need to remove the letters from our list of values and return a list of only the numbers:
> map fst . filter ((_, letter) -> elem letter "aeiouAEIOU") $ zip [1..] word [1,5]
The map
function, like the filter, requires a function and a list. Here, the function is fst
and the list is provided by the value returned by the call to the filter. Typically, tuples consist of two values (but this is not always the case). The fst
and snd
functions will extract the first and second values of a tuple, as follows:
> :t fst fst :: (a, b) -> a > :t snd snd :: (a, b) -> b > fst (1, 'a') 1 > snd (1, 'a') 'a'
We will add our newly crafted expression to the LearningDataAnalysis01
module. Now, open the file and add the new function towards the end of this file using the following code:
-- Finds the indices of every vowel in a word. vowelIndices :: String -> [Integer] vowelIndices word = map fst $ filter ((_, letter) -> elem letter "aeiouAEIOU") $ zip [1..] word
Then, return to the Haskell command line and load the module using :l
:
> :l LearningDataAnalysis01 [1 of 1] Compiling LearningDataAnalysis01 ( LearningDataAnalysis01.hs, interpreted ) Ok, modules loaded: LearningDataAnalysis01.
In the next few chapters, we will clip the output of the load
command. Your functions are now loaded and ready for use on the command line:
> vowelIndices "apple" [1,5] > vowelIndices "orange" [1,3,6] > vowelIndices "grapes" [3,5] > vowelIndices "supercalifragilisticexpialidocious" [2,4,7,9,12,14,16,19,21,24,25,27,29,31,32,33] > vowelIndices "why" []
You can also use the median
function that we used earlier. In the following code, we will pass every integer returned by vowelIndices
through fromIntegral
to convert it to a Double
type:
> median . map fromIntegral $ vowelIndices "supercalifragilisticexpialidocious" 20.0
If you make changes to your module, you can quickly reload the module in the interactive command line by using :r
. This advice comes with a warning—every time you load or reload a library in Haskell, the entire environment (and all your delicately typed expressions) will be reset. You will lose everything on doing this. This is typically countered by having a separate text editor open where you can type out all your Haskell commands and paste them in the GHCi interpreter.