A minimal fluff introduction to Haskell showing enough tools to build a command line application to singularize every word in multiple lines. First presented at the first meeting of the Atlanta Functional Programming Users Group
GHCI is the repl that comes with GHC.
Once GHC is installed, invoke it with by typing GHCI at the command line.
You should set a prompt denoted by Prelude
At the prompt we can evaluate any Haskell expression
For instance, we can do simple mathematical operations
We can also demonstrate Haskell's string syntax
Or actually print a string to standardout
To see the myriad of commands available in GHCI, type colon h
To quit GHCI, type colon q at the prompt
To compile a haskell program from a file, create a dot hs file with the program name you want.
Declare a haskell module of the name Main and define the main function with in.
When you compile your program, GHC will look in the Main module for the main function.
When it is run, your program will start in the main function.
Use the --make option to GHC to compile your program into an executable.
GHC will create an executable file with the same name as the source file, but
with no extension. Execute this file to see your program simply execute it
It should not be surprising that functions play a central role in functional programming.
Let's define some.
In GHCI, we must use the let syntax to define a function
Start with the let keyword, followed by the function name
next list the parameter names, followed by spaces
then an equals sign an another space
and on the right side write the expression that defines the value of the function.
We can use the function simply by typing it's name followed by any parameters
Inside a haskell source file we can define top level functions simply using the equals syntax.
There is no need to scope the definition inside of a let expression.
You can try out functions from source file in GHCI using the load command.
Type the name of the module you would like to load and GHCI will make its functions available to you
One of the most important data structures in Haskell is the list.
Lists are written as comma delimited elements enclosed in square braces.
The indexing operator can be used to look up elements in a list
The head function accepts a list and returns its first element
The tail function accepts a list and returns a new list without the head
The length function accepts a list and returns its length
The plus plus function concatenates two lists
The colon function adds an item to the front of a list
Strings are lists too. Lists of characters.
So we can use head to get the first character of a string
we can use tail to strip off the first character of a string
and length to get the length of a string
of course, the other list functions work on strings too
There are lots and lots of list functions available in GHC.
Consult the API docs to find out more about the options available
GHCI can help us investigate the haskell type of an expression
Using the T command Followed by a haskell expression will show the type of that expression
Functions are expressions too, so we can find out their type
the T command followed by the function name will show you its type
The arrow separates the types in the function signature.
The last type is the return type, the rest are parameter type.
This function accepts two Bools and returns a String, AKA list of Char
There is a very good reason why the parameter and return types are both separated by arrows,
but we won't go into that now."
There's a lot more that you can do with pattern matching other than deal with tuples.
You can pattern match lists constructed with collun.
Start with the Name to bind the first list element to
follow it with the collun list constructer
and then the Name to bind the rest of the list to.
Now we can put a list value in.
and see what values were bound
The first value in the list, one, was bound to 'x'. The
rest of the list was bound to 'xs'"
This pattern match reverses the operation of the colon function,
which we can use to build the list back up
You can use pattern matching define multiple branches of a function.
Ignore the curly braces and semi-colluns. They're here to make GHCI happy.
First we can define than 'f' of the empty list is simply the empty list.
This is our termination condition for a recursiv definition.
With the empty case taken care of, we can safely pattern match a non-empty
list and compute on it.
Now we have a fuction that multiplies every item in a list by 2
If we define a function that ends up failing to match a pattern,
we will get an error message at runtime.
Because haskell is lazy, we can define an infinitely recursiv function.
Let's define a function that starting at 'n' and counts up by step 's', returning a List.
The first element of the list is simply 'n'
The remainder of the list is defined by recursivly calling 'f', using the next number in the list
as the new starting point
In a strict language we would recurs infinitely as soon as we used the 'f' function.
In Haskell, however, as long as we only ask for a finite portion of the list we are ok
If we try to print out the entire list, then our program goes into a infinite loop.
It will keep printing values forever, until we interrupt it with 'control c'.
To make this program work we'll need four pure functions and a main function
Functions: singularize, singularizeWords, singularizeLines, singularizeContent
Let's start with the 'singularize' function. It’s type is String -> String
The singuralization scheme is going to be very naive: it will simply strip of a single trailing 's' character
We'll implement the function with recursion, starting with the base case. **Problem is recursive**
Using pattern matching on base case: handle when the recursion has no more characters left
In this case, we are done so we will simply return an empty list.
Let's start on the second case for the 'singuralize' function. - will remove the ‘s’ character
When we encounter a list which is just an 's', we want to handle it specially. Pattern = [‘s’]. Return []
The final branch of the function will handle the recursion. We'll Use pattern matching again to pull
off the first character from the list. The pattern match is (x:xs)
We'll also use the colon function to build the result. We'll simply start with the character matched by 'c',
since we don't need to do any operations on it.
Next is the collun function, which will be followed by the rest of the word being singularized.
We complete our singuralization by recursing back using the remainder of the word.
The 'singularizeWords' function will use recursion to singularize each word in a list
Again, we have the base case of an empty list, which we implement using pattern matching.
And we implement the recursion using pattern matching to extract the first word from the list.
We will singuralize a single word at a time and using recursion to singularize the rest of the list.
The 'singularizeLines' function will break each line in a list into words and singuralize them.
Again, we use pattern matching for the base case of the recursion.
And to extract the first line from the list from the rest of the list.
In this case, we use the 'words' function to break the line into words,
which we then singularize. Then we join the result back together with 'unwords'.
The collun function will build a new list start with this line of singuralized words.
And the rest of the list is build using recursion again.
the 'singularizeContent' function will break content into lines to be singuralized
in this case we don't need to do any pattern matching because we don't need recursion."
We simply use 'lines' to break up the content, use the 'singuralizeLines' function to process them, and 'unlines' to join them back together.
Finally we get to the 'main' function. Until now every function in our application has been pure. We need a
little bit of impurity to link it all together. We need to read the content from standard-in,
process it, and print it back out.
We'll use 'getContents' to lazily read all the content from standard-in. The contents gets unpacked from the IO type into our binding, 's', as a String.
We'll use the 'singularizeContent' content function to process the contentbeing read from standard-in. And finally we use 'print string line' to print it out.