Haskell
Haskell is a purely-functional, statically typed programming language. Haskell
is elegant, concise, and lazy. Haskell can be compiled (ghc
) or interpreted
(ghci
). Haskell files use the .hs
extension. Information on this page is
taken from Learn You a Haskell for Great Good!
Functions
Functions in Haskell are pure. Pure functions have no side-effects. If a function is called twice with the same parameters, it's guaranteed to return the same result (referential transparency).
Infix Functions
Infix functions (in contrast to prefix functions) are sandwiched between their parameters.
-- Arithmetic 50 * 100 - 4999 5 / 2 -- Boolean algebra not (False || True && True) -- Equality tests 5 == 5 5 /= 10
Prefix Functions
In Haskell, prefix functions are called by writing the function name, a space
and then the parameters, separated by spaces. Two parameter functions may be
called as an infix function using backticks (e.g. 92 `div` 10
). Note that
function application has highest precedence (e.g. succ 9 * 10
<==>
(succ 9) * 10
).
succ 8 -- 9 min 9 10 -- 10
Writing Functions
doubleMe x = x + x conanO'Brien = "It's a-me, Conan O'Brien!"
Functions that don't take any parameters are called definitions (or names). We cannot change names and functions once we've defined them.
Control Flow
Haskell's if
is an expression instead of a statement. Since expressions
must return a value, the else
is mandatory in Haskell.
doubleSmallNumber x = if x > 100 then x else x*2
Lists
In Haskell, lists are a homogeneous data structure; list elements must be the same type. Strings are syntactic sugar for character lists.
[1,2,3,4] -- Concatenation [1,2,3,4] ++ [9,10,11,12] -- Cons operator 'A':" SMALL CAT" -- Indexing "Steve Buscemi" !! 6
Common list functions include head
, tail
, last
, init
(get all but
last), length
, null
(test if empty), reverse
, take
, drop
, maximum
,
minimum
, sum
, product
, and elem
(test for membership).
Ranges
[1..5] -- [1,2,3,4,5] -- Step [3,6..15] -- Infinite lists take 5 [4,8...]
cycle
, repeat
, and replicate
are useful functions for infinite lists.
List Comprehensions
List comprehensions are similar to set comprehensions in mathematics (e.g. \(S = \{2 \cdot x \mid x \in \mathbb{N}, x \leq 10\}\)). Haskell's list comprehensions can accept any number of lists and predicates.
[x | x <- [50..100], x `mod` 7 == 3] -- [52,59,66,73,80,87,94]
Tuples
Tuples, unlike lists, don't have to be homogeneous. Tuples also have a
specific length defined in their type. Denote tuples with parenthesis
((1,2)
). fst
and snd
are useful functions that operate on pairs. zip
is a function that produces pairs.
Types
Haskell has a static type system; the type of every expression is known at
compile time. The :t
command in ghci
gives an expression's type
(e.g. "Hi" :: [Char]
). ::
is read as "has type of."
Type | Description |
---|---|
Int |
Bounded integer. |
Integer |
Unbounded (but less efficient) integer. |
Float |
Single precision floating-point. |
Double |
Double precision floating-point. |
Bool |
Boolean (True or False ). |
Char |
Characters denoted by single quotes. |
When writing functions, we can choose to given them an explicit type declaration (generally good practice).
removeNonUppercase :: [Char] -> [Char] removeNonUppercase st = [c | c <- st, c `elem` ['A'..'Z']]
Haskell has type inference. However, we must sometimes add a type annotation
(e.g. read "5" :: Int
).
Type Variables
A type variable is a lowercase symbol that can take any type. Type
variables appear in type declarations (e.g. a
in head :: [a] ->
a
). Functions that have type variables are called polymorphic functions.
Typeclasses
A typeclass is a sort of interface that defines some behavior. A class
constraint appears in a type declaration and assigns a typeclass. For
example the ==
function has the type declaration (==) :: (Eq a) => a -> a
-> Bool
.
Typeclass | Functions | Description |
---|---|---|
Eq |
== , /= |
Types that support equality testing. |
Ord |
> , < , etc |
Types that have an ordering. |
Show |
show |
Types that can be presented as strings. |
Read |
read |
Opposite of Show. |
Enum |
succ , pred |
Sequentially ordered types. |
Bounded |
minBound , maxBound |
Types with an upper and lower bound. |
Num |
+ , - , etc |
Numeric values. |
Integral |
Only integral (whole) numbers. | |
Floating |
Only floating point numbers. |
Pattern Matching
When defining functions, you can define separate bodies for different patterns. Patterns are checked from top to bottom. The program will crash if no patterns match.
factorial :: (Integral a) => a -> a factorial 0 = 1 factorial n = n * factorial (n - 1)
Pattern matching can also be used on tuples (e.g. (x, y)
) and lists
(e.g. (x:xs)
)
As patterns let one break up according to a pattern and keep a reference
to the whole thing. This is done by putting a name and an @
in front of a
pattern. For example, all@(x:xs)
.
Guards
A guard is a boolean expression indicated with a pipe. A series of guards
can appear after a function's name and parameters. If a guard evaluates to
True
, the corresponding function body is used. The last guard may be
otherwise
as a catch all. Use guards instead of patterns when testing for a
boolean condition.
max' :: (Ord a) => a -> a -> a max' a b | a > b = a | otherwise = b
A where
clause can appear after guards. This keyword can define several
names and functions which will be visible across the guards. A let, in
contrast, won't span across guards. Where bindings are a syntactic construct,
lets are expressions.
Let Bindings
A let binding is an expression that defines local variables. The form is
let <bindings> in <expression>
.
cylinder :: (RealFloat a) => a -> a -> a cylinder r h = let sideArea = 2 * pi * r * h topArea = pi * r ^2 in sideArea + 2 * topArea
Let bindings can also appear in list comprehensions. Notice that in
is
omitted.
[bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]
Case Expressions
Case expressions are expressions that perform pattern matching. Note that pattern matching on parameters in function definitions is syntactic sugar for case expressions.
case expression of <pattern> -> <result> <pattern> -> <result> <pattern> -> <result> ...
Higher Order Functions [todo move above]
A higher order function is a function that either that accepts functions as parameters or returns functions. Indicate function parameters with parenthesis:
applyTwice :: (a -> a) -> a -> a applyTwice f x = f (f x)
All Haskell functions that accept several parameters are curried functions. A function that takes two arguments is really a function that takes one argument and returns another function. If we call a function with too few parameters we get a partially applied function. Infix functions can be partially applied using sections. To section an infix function, simply surround it with parentheses and only supply a parameter on one side.
map
, filter
, foldl
, and foldr
are higher order functions. foldl1
and
foldr1
are similar to foldl
and foldr
, but don't require an explicit
starting value. They cause runtime errors if called with empty lists. scanl
and scanr
are also similar to foldl
and foldr
. They report all the
intermediate accumulator states in the form of a list.
Lambda Expressions
To make a lambda, write a \
, parameters, ->
, and the function
body. Lambdas are normally surrounded by parenthesis unless we mean for them
to extend all the way to the right.
\x y -> x + y
Function Application
$
is an infix function known as function application. Whereas normal
function application (putting a space between two things) has a really high
precedence, the $
function has the lowest precedence. $
can help avoid
parenthesis: sum (map sqrt [1..130])
could be written as sum $ map sqrt
[1..10]
.
$
can be treated like any other function:
map ($ 3) [(4+), (10*), (^2), sqrt]
Function Composition
The .
function performs function composition. Here's an example.
map (\x -> negate (abs x)) [5,-3,-6,7,-3,2,-19,24] -- Now with composition... map (negate . abs) [5,-3,-6,7,-3,2,-19,24]
Composition is also convenient for point-free style. Functions in the point-free style avoid mentioning their actual arguments.
sum xs = foldr (+) 0 xs -- Point-free style! sum' = foldr (+) 0
Composition is glue to form more complex functions. It allows us to write
let fn = f . g . h
instead of let fn x = f (g (h x))
.
Modules
A Haskell module is a collection of related functions, types and
typeclasses. A Haskell program is a collection of modules where the main
module loads up the other modules and then uses the functions defined in them
to do something. The Haskell standard library is split into modules. See this
handy reference. Use import
to load a module. In GHCi, load a module with
:m
.
-- Make Data.List exports available in the global namespace. import Data.List -- We can choose not to import certain functions. import Data.List hiding (nub) -- Qualified imports help avoid name clashes. import qualified Data.Map -- We can also choose to rename the module. import qualified Data.Map as M
Modules export functions. These functions act as a sort of interface to the module. At the beginning of a module specify the name and function exports.
-- This would be at the top of Geometry.hs module Geometry ( cubeVolume , cubeArea ) where
Modules can also be given hierarchical structures. We just need to specify the
directory name as part of the module name. For example, the module name could
be Geomertry.Cube
if we had a Cube.hs
file in a Geometry
folder.
Custom Types [todo move above]
Use the data
keyword to define a type. Here's Bool
from the standard
library:
data Bool = False | True
The parts after the =
are value constructors. The |
is read as
or. Both the type name and the value constructors have to be capital
cased. Value constructors are functions that take parameters and return a
value of a data type.
data Point = Point Float Float data Shape = Circle Point Float | Rectangle Point Point Circle (Point 0 0) 5
Notice that when defining a point, we used the same name for the data type and the value constructor. This has no special meaning, although it's common if there's only one value constructor.
Exporting Types
To export types from your modules, write the type and parenthesis with value
constructors separated by commas. If you want to export all the value
constructors, just write ..
.
module Shapes ( Point(..) , Shape(..) ) where
You can choose to not export any value constructors by just writing the type
in the export statement (e.g. Shape
). Data.Map
uses this approach. You
must use auxiliary functions (like Map.fromList
) to create maps.
Record Syntax
Use record syntax when a constructor has several fields and it's not obvious which field is which. Use curly brackets, then the name of the field, a double colon, and the type. Record syntax also makes Haskell automatically create field lookup functions.
data Person = Person { name :: String , age :: Int , height :: Float } deriving (Show) -- New syntax for constructing values. Person {name="Elliot", age=26, height=6.0}
Type Parameters
Type constructors take types as parameters to produce new types. Here's an example from the standard library:
data Maybe a = Nothing | Just a
a
in this example is a type parameter and Maybe
is a type
constructor. []
is another example of a type constructor; we must give
[]
a type parameter (e.g. [Int]
). We usually use type parameters when
the type that's contained inside the data type's various value constructors
isn't really that important for the type to work.
Note that we don't usually put type constraints on the parameters of data declarations, because you'll have to put them into the function type declarations either way.
Derived Instances
Haskell can automatically derive the behavior for Eq
, Ord
, Enum
,
Bounded
, Show
, and Read
if we use the deriving
keyword when making
our type. For example, make a type part of the Show
typeclass to allow
Haskell to print it. Just add deriving (Show)
to the end of a data
declaration.
Type Synonyms
Type synonyms just give some types different names so that they make more
sense to someone reading our code and documentation. Use the type
keyword
to create a type synonym. Here's how the standard library defines String
as a synonym for [Char]
.
type String = [Char]
Type synonyms can also be parameterized.
Typeclasses (part 2)
Typeclasses are like interfaces - they define behavior. Here's the
definition of Eq
in the standard prelude:
class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool x == y = not (x /= y) x /= y = not (x == y)
class Eq a where
means we're defining a new typeclass called Eq
. It's
not mandatory to implement the function bodies. Here's we've chosen to
define them in terms of mutual recursion. Let's try making an instance of
Eq
.
data TrafficLight = Red | Yellow | Green instance Eq TrafficLight where Red == Red = True Green == Green = True Yellow == Yellow = True _ == _ = False
The class
keyword defines new typeclasses and the instance
keyword is
for making our types instances of typeclasses. Because ==
was defined in
terms of /=
and vice versa in the class declaration, we only had to
overwrite one of them in the instance declaration. This is the minimal
complete definition for the typeclass - the minimum number of functions
that we have to implement so that our type can behave like the class
advertises.
You can also make typeclasses that are subclasses of other typeclasses. The
first part of the class declaration for Num
is as follows.
class (Eq a) => Num a where ...
Subclassing is really just a class constraint on a class definition.
Input and Output
The putStrLn
function has the type declaration putStrLn :: String -> IO
()
. An I/O action carries out an action with a side effect. This action
will be performed when we give it a name of main
and run our program.
main = do putStrLn "Hello, what's your name?" name <- getLine putStrLn ("Hey " ++ name ++ ", you rock!")
The do
syntax glues together several I/O actions into one. By convention, we
don't usually specify a type declaration for main. \
The <-
construct binds an I/O action's result value to a name. getLine
has
the type declaration getLine :: IO String
. So in the example above, name
will have the type String
. In Haskell, return
makes an I/O action out of
a pure value. Return is the opposite of <-
. For example return "haha"
will
have a type IO String
. Haskell's return
is nothing like the return in most
other languages.
To run a program you can either compile it and then run the produced
executable file by doing ghc --make helloworld
and then ./helloworld
or
you can use the runhaskell
command like so: runhaskell helloworld.hs
and
your program will be executed on the fly.
Useful functions for dealing with I/O are putStr
, putChar
, print
,
getChar
, when
, sequence
, mapM
, forM
, forever
, getContents
, and
interact
.
Here's a program that reads a file and prints the contents:
import System.IO main = do handle <- openFile "girlfriend.txt" ReadMode contents <- hGetContents handle putStr contents hClose handle
We can also do this with the withFile
function:
import System.IO main = do withFile "girlfriend.txt" ReadMode (\handle -> do contents <- hGetContents handle putStr contents)
We also have the nice constructs readFile
, writeFile
, and appendFile
.