Haskell Programming: Expressions
- Read Ch. 1 of Real World Haskell
- This also includes parts of Ch. 2
- Why Haskell?
- Functional programming:
- function: named expression/action
- FP: programs = collection of functions
- instead of writing a loop to process data, write operations
that returns new collections based on the old ones
- Haskel: can be a "pure functional" programming language - no
side-effects
- Easier to reason about - don't have to worry calling a method
changes some data at random
- Easier to parallelize - each node can compute its own result
without other nodes being able to change data the node depends on
- Type system will help us determine which are pure functions
- Sophisticated type system - one of the most powerful
- Popular among programming languages people because of the
sophisticated theory behind the language
- Download and install GHC: See the course home page.
- Using Repl.it is a better solution for many;
see this page
- Open
ghci
- the interactive Haskell
environment. Note: multi-line copy/paste does not always work
with ghci
and Haskell; you may need to type the code
- Enter
:set prompt "ghci> "
to make the prompt
more consistent.
- Alternative: replit
- Enter expressions:
- 5 + 3 * 4
- 1.2 / 3.0, 3 / 4, 9 / 3
- Note almost every language has slightly different rules with
division!
- 4.5 * 2
- 18 ^ 75
- 5 - 8
- These were all infix operators: operator between terms
- Operations can also be written using prefix:
(/) 3 4
- Note: no commas between arguments
- Negative numbers are a bit different in Haskell
-
is a prefix operator (as well as an infix operator!)
- -5: works as expected
- Try:
5 * -4
- Haskell is confused
- Fix:
5 * (-4)
- Note negative numbers are rare in a
lot of code...
- In most cases, can drop spaces, but consider 5*-3
- Booleans
True && False
- It's important these are upper case - it has to do with
how users can define their own types
True || False
- Not:
True && 1
- Key: Haskell type system works to ensure no errors with a minimal
amount of explicit typing by the programmer
- Haskell does not allow treating numbers, other data as boolean
values
- 1 == 1, 3 < 4, 5 >= 4.999
- Not equal:
2 /= 3
- not True
- Precedence
- Every operator has a precedence level between 1 and 9, 9 is highest
:info (+)
:info (*)
:info (&&)
:info (^)
- note infor
instead
of infol
- right to left associativity for ^
- Constants, variables
pi
e
- "not in scope" means not defined yet
let e = exp 1
:info exp
e ** e
(can't use ^
)
- Lists
[5, 8, 13, 21]
["alpha", "beta", "cat"]
- note type
- try mixing:
[5, 8, "eat"]
- range:
[5..20]
- stepped range:
[2, 4..20]
, [2, 4..19]
- also:
[4, 10..33]
- even floats, but be cautious: [0.5, 1..4], [1.0..1.8]
For the 1.8, Haskell has rounded to 2.0 - doing this sort of
thing with floats is dangerous in any language
- Concatenating lists: [1..5] ++ [20..25], [True, False] ++
[True], [True, False] ++ [], [1] ++ ["a"]
- Both lists must have the same type
- To add a value to the start of a list:
1 : [2,3]
- construct: construct a (bigger) list from an element
and a list; typically read as cons
- Note: [1,2]:3 is an error! - second argument must be
a list of the first (or the empty list)
- More simple types
- Compare "a", 'a'
putStrLn "hello"
- let b = ['s', 't', 'r', 'i', 'n', 'g']
- putStrLn b
- "" == []
- Documentation:
- Single line comment: start with
--
and go to end of
line:
-- computing the remainder after dividing by a number:
mod 50 3
-- or as an infix operation:
50 `mod` 3
- Multi-line comment: start with
{-
and go to -}
{-
Author: Hasker
Program: Greeting
Source: A translation of an important C program written by
Brian Kernighan in 1974 at Bell Labs - see the
Wikipedia Page
-}
main = putStrLn "hello, world"
Haskell Types
- Execute
:set +t
- give more type information
'c'
it :: Char
means it
has the
type Char
- Note:
it
is the last expression you entered; can save
it for future use: 3 * 4 + 5
, then let result = it
- This is only relevant in
ghci
; you would not use it
in code
:module +Data.Ratio
- add module for ratio numbers
- Can abbreviate
:module
as :m
11 % 29
- ratio of integers
1 % 4 + 2 % 3
3.4 % 8.5
- Textbook points out the verbose types are distracting;
disabling:
:unset +t
- More directly:
:type 'a'
, or "foo"
then :type it
- The
3.4 % 8.5
- need to specify the types; Haskell can't
determine it in this case
Haskell Type System
- First characteristic: statically typed
- [what are the advantages?]
- [what are the advantages of dynamic typing?]
- Second characteristic: sophisticated type inference
- In many cases, do not need to specify the types of variables,
parameters
- In fact, in some ways it is actually more "expressive" than
dynamically typed languages
- Basic types:
Char
- Unicode character
Bool
- True, False
Int
- 32, 64, or other-sized integers
Integer
- unbounded-size integers
Double
- floats
- Applying a function to values:
odd 3
- note no parentheses; they are generally only
used for precedence
odd 6
- Known as function application
compare 3 4
compare 3 2
compare 2 3 == LT
- don't need parens since
application is high, but can add them if you like: (compare 2 3)
== LT
- They are necessary:
compare (sqrt 3) (squrt 6)
:type odd
:type compare
-
Processing lists
head [1,2,3]
tail [1,2,3]
tail words
tail []
- Note result type depends on the list type
:type head
a
: type of anything, so head
takes a list
of x to an x value; eg:
:type "abc"
- the square brackets indicate a list
- In contrast, tuples
- (3, 4)
:type (3,4)
:type (4, "hi")
:type (4, True, "hi")
:type ()
- tuple of 0 elements
take 2 [10..20]
drop 3 [10..20]
fst (1, 'a')
snd (1, 'a')
- Caution: tuples in Python are really the equivalent of lists in Haskell
- Haskell (and others):
Further in to functions
:type lines
- try it:
lines "the quick\nred fox\njumps dogs
lines
: convert a string into lines
- Note this function is pure: all results can be explained by the
inputs, it changes no other data in the system
- Consider
putStrLn
: not pure
- what is likely modified by this code?
:type putStrLn
- the IO
indicates it has
side effects
- Writing functions:
add a b = a + b
- Left hand side of =: name of function and arguments
- Right: body
- Function name, variables must start with lower case
double x = x + x
- In Haskell, once a variable is bound to a value, it cannot be
changed (in the same program)
=
is for defining functions, not for assignment like
in Java!
- Try it:
x = 4
x = 5
- Conditional in Haskell:
if/then/else
- must ALWAYS have
the else
if True then 3 else 4
if False then 3 else 4
if True then 3
- Everything is an expression, and an expression always has to have a
value
- Both branches much have the same type:
if True then 4 else 'x'
- It will convert one branch if a more general type works:
if
True then 4 else 5.5
- Note:
if
is not a statement in Haskell; it's
really closer to the C++/Java ? :
operator:
x = a > 5 ? y : z;
would translate into Haskell as
x = if a > 5 then y else z
- Writing a version of the built-in function
drop
:
- Rules for
if
:
- expression must be of type
Bool
- also a predicate
then
: if predicate is True, this part (the
"true branch") is evaluated and
returned as the if
expression result
else
: if predicate is False, this part (the
"false branch") is evaluated and returned as the if
expression result
- The types of the true and false branches must be compatible.
- Note: the above essentially defines the semantics
for
if
in words - we will give more detailed ways later
- Can't drop the false branch - then the
if
expression
would have no value, making giving the true and false branches different types
- Exercise: change myDrop to return an empty list if n is
negative
(the behavior for n == 0 is unchanged)
- Simply reload it when done:
:load myDrop.hs
- An alternative way to write
myDrop
(the original
version): patternDrop.hs
- Haskell works its way through the list, finding the one that
matches the arguments
- it then evaluates the expression associated with the match
- Powerful: allows us to explicitly list all options
- Fast: compiler does exactly the necessary matches; in some cases
an
if
is significantly less efficient!
- In fact, often the compiler can tell which branch will be executed
in a call (it can tell the list is not empty, for instance) and so it
can optimize around that choice, ensuring pipelines are not emptied, etc.
Function Types
- See the Ch. 2 discussion on type systems - I will cover this later
but it's good review if you don't remember the pros and cons of static
typing, dynamic typing, or terms like strong typing
:type lines
-- note the :: used to introduce its type
- General form of a function type declaration:
fun
:: argument types -> result type
- Example:
stutter :: String -> String
-- code:
stutter :: String -> String
stutter s = s ++ " " ++ s
:type stutter
[Char]
- list of characters
- More generally: list of items: [item]
- Recall
fst (6,7)
and snd (6,7)
- what
should the type be?
:type fst
a
, b
: type variables
- Allows any type - known as polymorphic - many forms
- What would
:type snd
show?
- Consder
reverse [8, 9, 3]
and reverse "xyz"
- [What should
:type reverse
give?]
- What would be the type of
last
(try: last
[1..20]
) be?
- Power: no unnecessary constraints
- Where do we see type variables in C++?
Consider
<typename X>
void swap(X &a, X &b) {
X tmp = a;
a = b;
b = tmp;
}
- Explain
:type sqrt
- read Floating a =>
as "given a floating-point value a, ..."
Defining New Types
- Chapter 3
- Creating a type for a bookstore or library:
data BookInfo = Book Int String [String]
deriving (Show)
BookInfo
- a type constructor
Book
- value constructor
- Both
BookInfo
, Book
must start with upper
case letter - this is how Haskel distinguishes types from variables
Book
takes an integer, string, and list of strings and
creates a value of type BookInfo
deriving (Show)
- allows GHCI to print values
x = Book 1923 "Algebra of Programming" ["Jim", "Jane"]
- effectively,
Book
creates and returns a new value of a
type, sort of like a function
- See
samples/book.hs
for computing an average year across a collection of books
Example: Cards:
data Card = Spade Int
| Club Int
| Heart Int
| Diamond Int
deriving (Show)
A function testing card color:
isBlack (Spade x) = True
isBlack (Club x) = True
isBlack (Diamond x) = False
isBlack (Heart x) = False
This uses the type constructors to identify alternative paths; the system
executes the first matching path.
Using it:
queen = Heart 12
isBlack queen
If spades are 1-13, clubs 14-26, hearts 27-39, diamonds 40-52,
write a pattern-based function converting a card to its number
Review
- basic goals of Haskell
- expressions
- introduction to types
- introduction to writing functions
- Defining your own types in Haskell
- Future: defining recursive types; but first we will use this to
specify domain-specific languages