Haskell is a statically typed pure functional programming language. This is more of a cheat sheet for basic haskell syntax and constructs. I will not describe in any details what is pure functional programming or what is static typing. Scala, Kotlin and now Java are a mixed paradigm programming languages, which are mixture of OOP with Functional programming. If you have used Scala or a modern version of Java or Kotlin, you should find familiar constructs. The only thing you will not find , is classes , objects or any other OOP constructs. You will find something called Type Classes that has nothing to do with classes in OOP, you can think of it as type categories or the closest OOP thing to it is interfaces.
This is also not a tutorial, which means, the sections are not ordered.
- Getting Started
- Basic Syntax
- Functions
- Pattern Matching
- Conditionals
- Collections
- Tuples
- Types
- Type Classes
- Resources
Getting Started
The easiest way to get started with Haskell is to install Haskell Platform which includes a compiler, a build system, analysis and profiling tools, most commonly used packages among others.
After installing Haskell Platform, you can start the Glasgow Haskell Compiler in interactive mode by running ghci
. Once run it will great you with Prelude>
prompt. Prelude
is a library of functions that are preloaded for you to use.
The Interactive Mode error messages are clear and very useful. For example, I tried 1 != 2
to check for inequality. I got a detailed error message, not only telling me that !=
doesn’t exist “in scope”, but also suggesting using /=
for inequality.
1
2
3
4
5
<interactive>:8:3: error:
• Variable not in scope: (!=) :: Integer -> Integer -> t
• Perhaps you meant one of these:
‘>=’ (imported from Prelude), ‘==’ (imported from Prelude),
‘/=’ (imported from Prelude)
Useful commands in Interactive mode
:quit
or:q
to exit:?
for help:set prompt "ghci> "
Change the default prompt fromPrelude
toghci
:set -Wall
Show all warnings. Useful for non-exhaustive pattern matching:!
to run bash command:! clear
to clear screen under Linux
:l filename.hs
to load a haskell file into the active session.:info
or:i
gives information about a given “name”, if it is an operator for example it may yield its precedence and associativity.- To write a block of code we surround it by
:{
and:}
:browse
The list of type signatures and functions loaded from the current module. We can also use:browse ModuleName
for a specific module:sprint
that is s-print. It prints a value without forcing its evaluation.
Basic Syntax
Naming Conventions
- Function names are camelCased
- Types are PascalCased
Bits and pieces
- Indentation: Code which is part of some expression should be indented further than the beginning of that expression. Indentation Summary
- comments start with
--
- String is treated like a list of characters, thus we can
h:"ello world"
- To give a value an explicit type we use
::
, e.g.5 :: Int
- Assigning a value to variable with explicit type would be something like
a = 5 :: Int
- Assigning a value to variable with explicit type would be something like
Basic IO
- We have
print
,putStrLn
andputStr
to print to the console.putStr :: String -> IO ()
displays strings to console. That is, It only takes arguments of typeString
putStrLn :: String -> IO ()
Similar toputStr
with a new line at the endprint :: Show a => a -> IO ()
, similar toputStrLn
but works with other types. It prints the String representation of any type, given that the type implementsShow
(Think of Java’stoString
)
IO ()
means an input/output action (side effect) that has no meaningful return type denoted by unit()
Functions
- functions can be called without parenthesis or commas even with multiple arguments. For example
max 1 2
would yield2
as expected. - Prefix functions can be turned into infix functions by wrapping them with a backtick. So we can do
1
max
2
- Infix functions can be turned into prefix functions by wrapping them with parenthesis
(+) 1 2
- You can do
max 1 2 + 3
which is same as(max 1 2) + 3
. Please, use some form of the latter, don’t depend on precedence rules, for the sake of mere mortals like myself, and yourself few weeks down the road. - to define a function
doubleMe x = x + x
. $
operator is the lowest in precedence. It allows its right side to be evaluated before the left.- The
->
is the type constructor for functions in Haskell. It takes arguments and has no data constructor. functions are values - Functions take one argument and return one result.
- Multi-argument functions are a series of curried functions
- We partially apply the function to the first argument, returning another function that is partially applied to the second and so forth.
- Uncurrying a function would be to turn the curried application into using a tuple for arguments. That is turning
a -> a -> a
to(a, a) -> a
- currying a function is opposite of uncurrying. That is turning
(a, a) -> a
toa -> a -> a
- We can use
curry
anduncurry
to do exactly that to functions. Either will return a new function that is curried or uncurried accordingly. - When we apply a function to some value, we say that the function parameter is bound to its argument
Lambdas (Anonymous Functions)
- Lambdas are useful for example if we are passing a predicate to a filter or a function to a map for example
\ x -> x * 2
is a lambda, that can be used for example asmap (\ x -> x * 2) lst
.\
is basicallyλ
as both symbols look similar, the former is easier to type
Section of an infix operator
- Special syntax for partial application of infix operators
- For example
^
(2^)
left section is equivalent to\x -> 2 ^ x
(^2)
right section is equivalent to\x -> x ^ 2
- This becomes interesting:
containedInList = (
elem
list)
, which now tests if an element belongs to a list, which saves us an intermediate step to switch arguments explicitly. Note we changed the prefix functionelem
to infix by surrounding it by backticks1 2 3 4
rightSection = (++ " end") leftSection = ("start " ++) rightSection "hello" -- Results in: "hello end" leftSection "hello" -- Results in "start hello"
Composition
- we use
.
higher order function to compose two functions (.) :: (b -> c) -> (a -> b) -> a -> c
1 2 3 4
f1 x = "(f1 " ++ x ++ ")" f2 x = "(f2 " ++ x ++ ")" f = f1 . f2 f "x" -- Result in "(f1 (f2 x))"
- In other words
(f1 . f2) x = f1(f2(x))
Useful functions
show
converts a some type to a Stringread
converts a string into some other typeflip
reverses the parameters of two argument functions.catMaybes :: [Maybe a] -> [a]
flattens a list of Maybes
Pattern Matching
- Pattern matching can be done simply by declaring the function definition then the list of possible matches.
1 2 3
factorial :: (Integral a) => a -> a factorial 0 = 1 factorial n = n * factorial (n - 1)
We can pattern match against Tuples as well
1 2
multiple :: (Integral a) => (a, a) -> a multiple (a1, a2) = a1 * a2
That is opposed to
multiple a =(fst a) * (snd a)
Pattern matching against lists would be []
for empty list(a1:a2:[])
or[a1, a2]
first two elements of two elements list(a1:a2:_)
first two elements for a long list, ignoring the rest of the list(a:as)
which is head:tailall@(a:as)
Will get us all the elements of a list in addition toa
andas
If we want to match any, we use _
. This is particularly useful if we want an exhaustive matching as opposite to partial matching.
We can use constructors to deconstruct (think unapply
in Scala` a value)
1
2
3
4
data Foo = Bar | Baz Int
f :: Foo -> Int
f Bar = 1
f (Baz x) = x - 1
Conditionals
If expression
1
2
3
4
greaterThan10 x =
if x > 10
then "greater than 10"
else "nope"
Which we can also write in one line as
1
greaterThan10 x = if x > 10 then "greater than 10" else "nope"
Case expression
1
2
3
4
5
6
f :: Int -> String
f x = case x of
1 -> "One"
2 -> "Two"
3 -> "Three"
_ -> "Not one, two or three"
Guard
1
2
3
4
water temperature
| temperature <= 0 = "solid"
| temperature >= 100 = "gas"
| otherwise = "liquid"
guard block is the code evaluating to Bool between |
and =
Collections
List
- We can only have lists of the same type.
[1,2,3]
creates a list, however it is a syntactic sugar for1:2:3:[]
, where[]
denotes an empty list- Similar to Scala
head
,tail
,last
andinit
work as expectedhead
first element of the listtail
all but the first element in the listlast
the last element in the listinit
all but the last element in the list
null
checks if a list is empty. I think that[]
is similar to Scala’snil
- Common list functions:
length
,reverse
,take
,drop
,maximum
,minimum
,sum
,product
,splitAt
- Common list higher order functions:
takeWhile
,dropWhile
,map
,filter
elem
similar to contains in Scala.2
elem
[1,2,3]
would yieldTrue
replecate 2 3
creates a list by replicating3
two times.zip
combines two lists into a single list of tupleszipWith
applies a function to each corresponding elements of the two input list and return a list of results
1
2
3
4
numbers = [1,2,3,4]
numbers ++ numbers -- concatination
0:numbers -- cons operator: adds an element in front of a list
numbers !! 0 -- Get the first element in the list (at index 0)
Range
[1..3]
defines a range from 1 to 3 all included.[1,4..10]
defines a range with a step of 3.[4,3..1]
We have to define a step to go in descending order['a'..'f']
it works for characters too
Infinite
One cool thing lazy evaluation gives us, is infinite lists
[1..]
Yes, that counts up… forever, and it actually runs with no errors… but you will need toctrl+c
to snap out of itrepeat 'a'
An infinite list of a’scycle [1,2]
similar to repeat but for a list- Use something like
take
to get a few numbers of an infinite sequence
Comprehension
List comprehension has the format [operation | list, filters]
.
- The list
x <- y
wherey
is a list - The operation, where a function is applied to
x
- The filter, which is a predicate that we run against
x
1 2
[x | x <- [1,2], x > 1] duplicatePositiveNumbers xs = [x * 2 | x <- xs, x > 0]
- We can have multiple predicates
[x | x <- [1..10], even x, x > 5, x < 9]
- We can have multiple lists
[show x ++ "-" ++ show y | x <- [1..3], y <- [4]]
which would result in["1-4","2-4","3-4"]
- We can have multiple lists and mutiple predicates
[show x ++ "-" ++ show y | x <- [1..10], y <- [4], x > 5, even x, x <9]
- We can discard
x
itself if we don’t care about itsum[1 |_ <- "hello"]
which would return the length of “hello” - List comprehensions can be nested
[sum y | y <- [ [1 | _ <- xs]| xs <- ["hello", "world!"]]]
, which would yield[5,6]
the lengths of the two strings.
Tuples
- Tuples are surrounded by parenthesis
(1,2)
- The type of a tuple includes the number of elements and their types
- Paris have two useful functions to extract the first and second elements
fst
andsnd
respectively zip
of two lists, result in a list of tuples- The new list will have the length of the shortest of the original lists
zip [1..] [4, 5, 6]
results in[(1,4),(2,5),(3,6)]
even though the first list is infinite
Types
- Use
:type
or:t
in the interactive shell to inspect the type of something- For example
:t 'a'
results in'a' :: Char
::
means has the type or of type
- For example
Int
is bounded by±2147483647
, whileInteger
is not. The former is more efficientFloat
,Double
,Bool
andChar
behave as expected.[Char]
is the representation of stringsRational
Represents rational numbers, such as 5/7. 4/2 will be stored as 2/1Fixed
Fixed decimal point number. 1.00, has two points, etc.:t max
results inmax :: Ord a => a -> a -> a
. It first defines the types of argumentsOrd a
then denotes taking two arguments and one returna -> a -> a
:t head
results inhead :: [a] -> a
. Note the absence of type declaration. This is a variable type, similar to generics in a way.=>
Anything before that symbol in type definitions is called class constraint
Useful types
Maybe a = Nothing | Just a
is used when a value may or may not exist. Similar to nullable types in Kotlen and Optional in Scala
Declaring a new type
data Bool = False | True
is the Bool type declaration, calleddata declaration
Bool
is a type ConstructorFalse
orTrue
are two value constructors|
indicates a sum type. That is Bool, has either of the two values.- In English:
- Declare type
Bool
- Declare two values
True
andFalse
- Assign for Type
Bool
the two valuesTrue
andFalse
. In a way that an identifier of typeBool
can either beTrue
orFalse
.
- Declare type
Data constructor
For data Foo = Bar Int String
, Bar
is data constructor with the signature Bar :: Int -> String -> Foo
. It is a function (constructor) that takes two argument and returns data.
type vs. data vs. newtype
data Foo = Foo Int
Foo and Int are totally different.newtype Foo = Foo Int
Foo and Int are different at compile time but same at runtime.type Foo = Int
Foo and Int are same at compile time and runtime.
Type Classes
Common type classes
Eq
,Ord
for equality and orderingShow
,Read
forshow
andread
functions respectivelyEnum
For representing enumerators- Types in this class include
()
,Bool
,Char
,Ordering
,Int
,Integer
,Float
andDouble
. succ
andpred
are functions that use this type
- Types in this class include
Bounded
for types that have min and max bounds. We can domaxBound :: Int
, and there is alsominBound
Num
, which in turn includesIntegral
andFloating
fromIntegral
function turns Integer like values to floating point like values. Its type isfromIntegral :: (Num b, Integral a) => a -> b
Polymorphism
- There are three kinds of types concrete, constrained polymorphic and parametric polymorphic
- type classes are a form of constrained or ad-hoc polymorphism
head :: [a] -> a
is parametrically polymorphic function. It specifies how the types of inputs and outputs are in relation to one another, without specifying exactly what those types are.- In the signature, the lower cased letters such as
a
andb
are called type variables. Those variables can be constrained by a type classmaxBound :: Bounded a => a
or nothead :: [a] -> a
Creating Type Class instances
- We can automatically derive
Eq
,Ord
,Enum
,Bounded
,Read
, andShow
using the keywordderiving
with some constraints. - To define a type class ,we make instance for a given type and use the keyword
instance
for example1 2 3
data TypeConstructor = DataConstructor instance Eq TypeConstructor where DataConstructor == DataConstructor = True
- This is more or less the same as
data TypeConstructor = DataConstructor deriving Eq
:i Real
would give usclass (Num a, Ord a) => Real
which means, for something to have an instance ofReal
, it must first have instances ofNum
andOrd
. I omitted the rest of the result of:i Real
, which indicates what other functionsReal
require
Creating our own Type Classes
1
2
3
4
5
6
7
8
9
class CanPlayFootball a where
yes :: a -> Bool
instance CanPlayFootball Int where
yes 10 = True
yes _ = False
a = yes (5 :: Int) -- Will be false
b = yes (10 :: Int) -- Will be true
Other considerations around type classes
- type defaulting is a way for the compiler to solve ambiguous type problems. In Prelude for example there is
default Num Integer
, which defaults to a specific concrete type for theNum
type class. - An orphan instance is when an instance is defined for a typeclass and a data type but not in the same module in either the typeclass of the data type. If we don’t own the data type we should newtype it.
Comments powered by Disqus.