undefined
.
Eventually, the complete
version will be made available.
Basics
A literate Haskell file is one where the file name ends in .lhs
, and all lines prefixed by >
are Haskell source code. All other lines are ignored by the Haskell compiler. The html is generated directly from the lhs version of the lecture notes. Feel free to download the source code and play with it yourself after class. You'll need to turn in a version of this file for the zeroth homework assignment.
Every Haskell file begins with a few lines naming the module (this name must start with a capital letter and be the same as the file name) and (optionally) importing definitions from other modules.
To load this file into ghci, you will need to install the hunit
library first. At your commandline, run the following:
cabal v1-install hunit
Understanding a Haskell program is about substituting equals by equals
Functional programming means that the semantics of a program can be described mathematically. One principle of mathematics is called Leibniz equality: in any context, we can replace an object with something equal to it. Therefore, in Haskell, we reason about computation by reasoning about the equality of (sub-)programs.
So, if we want to know the value of an arithmetic expression, we only need to find some number that is equal to it.
3 * (4 + 5)
{ 4+5 is equal to 9, by addition so we can replace it }
3 * 9
{ by multiplication }
27
That's it!
We can give names to Haskell expressions:
and ask ghci to calculate their values, just as we did above.
What is Abstraction?
Pattern Recognition
31 * (42 + 56)
70 * (12 + 95)
90 * (68 + 12)
Generalize to a function by defining an equation
The important question is not "What does this function do?" but, instead "What does this function mean?" We can reason about that meaning using what we know about equality.
pat 31 42 56
{ function call, replace x y & z in right-hand side by 31 42 and 56 }
31 * (42 + 56)
{ addition }
31 * 98
{ multiplication }
3038
Functions, like pat
, are the core abstraction mechanisms in functional programming.
The GHC System
Interactive shell "ghci"
:load Lec1.hs
expression
:type expression (:t)
:info variable (:i)
Emacs with haskell-mode
load file ^C-c ^C-l
Package management: "cabal"
Download and install libraries:
cabal v1-install hunit
Download and install Haskell applications:
cabal v1-install hlint
Elements of Haskell
- Everything is an expression
- Expressions evaluate to values
- Every expression has a type
Basic types
It is good style to annotate the type of every declaration in a Haskell program. This helps with error messages, as Haskell operators are often overloaded.
We can also annotate the type of the expression directly.
Function types
The type of a function taking an input of type A
and yielding an output of type B
is written as
A -> B
For example, the pos
function determines whether an Int
is strictly greater than zero.
Multi-argument function types
The type of a function taking inputs of type A1
, A2
, and A3
and returning a result of type B
is written as
A1 -> A2 -> A3 -> B
For example, the arith
function takes three Int
s as arguments and returns an Int
as a result.
Symbolic vs. alphabetic names
Symbolic identifiers (i.e. +
and *
) are infix by default.
Parentheses around a symbolic name turn it into a regular name.
For example, if we want to define an alphabetic name for the addition function, we can do so.
And we can call operations in parentheses just like "standard" functions, by writing their arguments afterwards.
Likewise we can use alphabetic name in backquotes as infix.
Making Haskell DO something
Programs often interact with the world:
- Read files
- Display graphics
- Broadcast packets
- Run test cases and print success or failure
They don't just compute values.
How does this fit with values & equalities above?
Note, we've gotten far without doing any I/O. That's fairly standard in Haskell. Working with GHCi means that we can see the answers directly, we don't need an action to print them out. However, a standalone executable needs to do something, so we demonstrate that here.
I/O via an "Action" Value
"IO actions" are a new sort of sort of value that describe an effect on the world.
IO a -- Type of an action that returns an `a` (a can be anything!)
Obligatory Hello World
Actions that do something but return nothing have the type IO ()
.
putStr :: String -> IO ()
So putStr
takes in a string and returns an action that writes the string to stdout.
GHCi can execute actions interactively.
Give it a try in ghci.
ghci> hw
Hello World!
ghci>
Alternatively, we can compile our program as an executable and run it.
The only way to "execute" the action (without using ghci), is to make it the value of name "main
".
The batch compiler "ghc" compiles and run large programs. There must be a definition of main
somewhere.
Compile and run:
ghc -o hello -main-is Lec1 Lec1.lhs
(Note: There can also be multiple source files in a Haskell application, and if the one that includes main
is called Main.lhs
you can leave off the -main-is
flag. There are also much more sophisticated ways to manage the compilation of Haskell applications, especially those built from libraries and multiple source files.)
Just 'do' it
How can we do many actions? By composing small actions.
The do
syntax allows us to create a compound action that sequences one action after another. The definition of many
below is a compound action that outputs the three strings in order. (Try it out in ghci!)
> many :: IO ()
> many = do putStr "Hello" -- each line in the sequence
> putStr " World!" -- must be an IO action
> putStr "\n" -- don't forget the newline
Note: white-space is significant here. The do
notation sequences actions, but each action in the sequence must start at the same character offset: all of the putStr
s must be lined up.
Sometimes people put the do
on a line by itself and then start the list of actions on the next line. This saves column width in larger developments.
Example: Input Action
Actions can also return a value.
getLine :: IO String
This action reads and returns a line from stdin. We can name the result as part of a do
sequence, with this notation
x <- action
Here x
is a variable that can be used to refer to the result of the action in later code.
> query :: IO ()
> query = do putStr "What is your name? "
> n <- getLine
> let y :: String
> y = "Welcome to CIS 552 " ++ n
> putStrLn y
Note, when we sequence actions of type IO ()
there is no need to name the result. These actions do not return anything interesting. We could name the result if we wanted (such as m
below); but because of its type we know that m
will always be a special value, written ()
and called "unit".
> query' :: IO ()
> query' = do m <- putStr "What is your name? "
> n <- getLine
> putStrLn ("Welcome to CIS 552 " ++ n)
> st <- query2
> return ()
Note that you cannot name the last action in a sequence. Names are there so that you can use the result later. If you want to return the value instead, the last action should be a return
.
> query2 :: IO String -- compare this type to `query` above.
> query2 = do putStr "What is your name? "
> n <- getLine
> return n
Furthermore, there is no need to name a value if it is just going to be returned right away. This version is equivalent.
Example: Testing Actions
The hunit
library contains definitions for constructing unit tests for your programs. To use this library you must first install it with the cabal
tool. This library defines the Test
type for test cases.
> t1 :: Test
> t1 = 3 ~?= 1 + 2 -- check that the expected value `3`
> -- matches the result of the computation
To run the test case, we need to use the function
runTestTT :: Test -> IO Counts
This is an action that runs the test case(s) and returns a data structure (of type Counts
) recording which ones pass and fail. If we print
this data structure, we can see whether the test passes or fails.
Homework Zero
First, follow the homework instructions for HW #0 online. Then replace undefined
below with your answers, make sure that you can compile this module and submit this file.
Your github userid.
What do you hope to get from CIS 552? What do you expect to learn from the course?
Now read Lec2 before the next class.