Gist for Haskell
#Start-out
Booleans: &&, ||, not , ==, /=(not equal)
Basic functions(prefix or use `` for infix):
succ 8
returns 9
Function syntax
functions can't begin with uppercase letters.
[fname] [...args] = [expression]
Control
if [exp] then [exp] else [exp]
else is mandatory
use let [ex]
for in-line name-defining.
[ls]++[ls]
for list appending. prepending A:[list]
is much cheaper than appending.
[ls] !! index
Use !!
for list access
List functions
head [lst]
to extract the 1st element
tail [lst]
to extract the rest of the list except 1st elem.
last [lst]
to put the last elem
init [lst]
to take all but last elem. length [ls]
, null [ls]
check for emptiness. reverse [ls]
take # [ls]
take the first # elements drop # [ls]
drops the first # elements and returns the list. maximum/minimum [ls]
sum/product []
elem E [ls]
checks for E's membership in the list
ranges
[2..20]
or [2,4,..10]
for range with a step
[2,4..]
infinite list
list comprehension
[ [expression] | x <- [range] , [condition] ]
Tuples
similar to tuples in C++. can be heterogeneous. fst (a,b)
and snd (a,b)
are 2 tuple element access functions
a useful function zip
(like Py) zip [ls1] [ls2]
creates a list with pairs
Haskell is static-typing language. :t FOO
use :t
to tell the type of an expression. ::
means 'has the type of '
Functions also have types. When writing our own functions, we can choose to give them an explicit type declaration.
addThree :: Int -> Int -> Int -> Int
addThree x y z = x + y + z
type variable. That means that a can be of any type. This is much like generics in other languages,
ghci> :t head
head :: [a] -> a
A typeclass is a sort of interface that defines some behavior of a type
ghci> :t (==)
(==) :: (Eq a) => a -> a -> Bool
We see a new thing here, the =>
symbol. Everything before the => symbol is called a class constraint. We can read the previous type declaration like this: the equality function takes any two values that are of the same type and returns a Bool. The type of those two values must be a member of the Eq class (this was the class constraint).
Basic Typeclasses
Eq
for equity-checkable
Ord
for ordering
Show
has a string representation. a useful function: show [exp]
like repr
in Python. shows the string repr of an expression.
Read
and read
is the opposite. It interprets given representation and decodes it
ghci> read "True" || False
True
ghci> read "8.2" + 3.8
12.0
ghci> read "5" - 2
3
ghci> read "[1,2,3,4]" ++ [3]
[1,2,3,4,3]
type annotation
ghci> :t read
read :: (Read a) => String -> a
See? It returns a type that's part of Read but if we don't try to use it in some way later, it has no way of knowing which type. That's why we can use explicit type annotations. Type annotations are a way of explicitly saying what the type of an expression should be. We do that by adding :: at the end of the expression and then specifying a type. Observe:
ghci> read "5" :: Int
5
ghci> read "5" :: Float
5.0
ghci> (read "5" :: Float) * 4
20.0
sayMe :: (Integral a) => a -> String
sayMe 1 = "One!"
sayMe 2 = "Two!"
sayMe 3 = "Three!"
sayMe 4 = "Four!"
sayMe 5 = "Five!"
sayMe x = "Not between 1 and 5"
you can also pattern match in list comprehensions.
ghci> let xs = [(1,3), (4,3), (2,4), (5,3), (5,6), (3,1)]
ghci> [a+b | (a,b) <- xs]
[4,7,6,8,11,4]
Lists themselves can also be used in pattern matching. You can match with the empty list [] or any pattern that involves : and the empty list
The x:xs pattern is used a lot, especially with recursive functions. But patterns that have : in them only match against lists of length 1 or more.
head' :: [a] -> a
head' [] = error "Can't call head on an empty list, dummy!"
head' (x:_) = x
Notice that if you want to bind to several variables (even if one of them is just _ and doesn't actually bind at all), we have to surround them in parentheses.
tell :: (Show a) => [a] -> String
tell [] = "The list is empty"
tell (x:[]) = "The list has one element: " ++ show x
tell (x:y:[]) = "The list has two elements: " ++ show x ++ " and " ++ show y
tell (x:y:_) = "This list is long. The first two elements are: " ++ show x ++ " and " ++ show y
you can't use ++ in pattern matches.
max' :: (Ord a) => a -> a -> a
max' a b
| a > b = a
| otherwise = b
bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| bmi <= 18.5 = "You're underweight, you emo, you!"
| bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"
| bmi <= 30.0 = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
where bmi = weight / height ^ 2
We use where
clause for local variables and functions
The names we define in the where section of a function are only visible to that function, so we don't have to worry about them polluting the namespace of other functions. Notice that all the names are aligned at a single column. If we don't align them nice and proper, Haskell gets confused because then it doesn't know they're all part of the same block.where
bindings aren't shared across function bodies of different patterns see below
initials :: String -> String -> String
initials firstname lastname = [f] ++ ". " ++ [l] ++ "."
where (f:_) = firstname
(l:_) = lastname
The form is let <bindings> in <expression>
. The names that you define in the let part are accessible to the expression after the in part. Let bindings let you bind to variables anywhere and are expressions themselves, but are very local, so they don't span across guards. Just like any construct in Haskell that is used to bind values to names, let bindings can be used for pattern matching.
cylinder :: (RealFloat a) => a -> a -> a
cylinder r h =
let sideArea = 2 * pi * r * h
topArea = pi * r ^2
in sideArea + 2 * topArea
The difference is that let bindings are expressions themselves. where bindings are just syntactic constructs.
They can also be used to introduce functions in a local scope:
If we want to bind to several variables inline, we obviously can't align them at columns. That's why we can separate them with semicolons.
ghci> 4 * (let a = 9 in a + 1) + 2
42
ghci> [let square x = x * x in (square 5, square 3, square 2)]
[(25,9,4)]
ghci> (let a = 100; b = 200; c = 300 in a*b*c, let foo="Hey "; bar = "there!" in foo ++ bar)
(6000000,"Hey there!")
case expression of pattern -> result
pattern -> result
pattern -> result
...
EXAMPLE:
head' :: [a] -> a
head' xs = case xs of [] -> error "No head for empty lists!"
(x:_) -> x
Whereas pattern matching on function parameters can only be done when defining functions, case expressions can be used pretty much anywhere
describeList :: [a] -> String
describeList xs = "The list is " ++ case xs of [] -> "empty."
[x] -> "a singleton list."
xs -> "a longer list."
They are useful for pattern matching against something in the middle of an expression. Because pattern matching in function definitions is syntactic sugar for case expressions, we could have also defined this like so:
describeList :: [a] -> String
describeList xs = "The list is " ++ what xs
where what [] = "empty."
what [x] = "a singleton list."
what xs = "a longer list."