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
:quitor:qto exit:?for help:set prompt "ghci> "Change the default prompt fromPreludetoghci:set -WallShow all warnings. Useful for non-exhaustive pattern matching:!to run bash command:! clearto clear screen under Linux
:l filename.hsto load a haskell file into the active session.:infoor:igives 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:} :browseThe list of type signatures and functions loaded from the current module. We can also use:browse ModuleNamefor a specific module:sprintthat 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,putStrLnandputStrto print to the console.putStr :: String -> IO ()displays strings to console. That is, It only takes arguments of typeStringputStrLn :: String -> IO ()Similar toputStrwith a new line at the endprint :: Show a => a -> IO (), similar toputStrLnbut 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 2would yield2as expected. - Prefix functions can be turned into infix functions by wrapping them with a backtick. So we can do
1max2 - Infix functions can be turned into prefix functions by wrapping them with parenthesis
(+) 1 2 - You can do
max 1 2 + 3which 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 -> ato(a, a) -> a - currying a function is opposite of uncurrying. That is turning
(a, a) -> atoa -> a -> a - We can use
curryanduncurryto 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 * 2is 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 = (elemlist), 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 functionelemto 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 -> c1 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
showconverts a some type to a Stringreadconverts a string into some other typeflipreverses 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 toaandas
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,lastandinitwork as expectedheadfirst element of the listtailall but the first element in the listlastthe last element in the listinitall but the last element in the list
nullchecks 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 elemsimilar to contains in Scala.2elem[1,2,3]would yieldTruereplecate 2 3creates a list by replicating3two times.zipcombines two lists into a single list of tupleszipWithapplies 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+cto snap out of itrepeat 'a'An infinite list of a’scycle [1,2]similar to repeat but for a list- Use something like
taketo get a few numbers of an infinite sequence
Comprehension
List comprehension has the format [operation | list, filters].
- The list
x <- ywhereyis a list - The operation, where a function is applied to
x - The filter, which is a predicate that we run against
x1 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
xitself 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
fstandsndrespectively zipof 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
:typeor:tin 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
Intis bounded by±2147483647, whileIntegeris not. The former is more efficientFloat,Double,BoolandCharbehave as expected.[Char]is the representation of stringsRationalRepresents rational numbers, such as 5/7. 4/2 will be stored as 2/1FixedFixed decimal point number. 1.00, has two points, etc.:t maxresults inmax :: Ord a => a -> a -> a. It first defines the types of argumentsOrd athen denotes taking two arguments and one returna -> a -> a:t headresults 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 ais 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 | Trueis the Bool type declaration, calleddata declarationBoolis a type ConstructorFalseorTrueare two value constructors|indicates a sum type. That is Bool, has either of the two values.- In English:
- Declare type
Bool - Declare two values
TrueandFalse - Assign for Type
Boolthe two valuesTrueandFalse. In a way that an identifier of typeBoolcan either beTrueorFalse.
- 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 IntFoo and Int are totally different.newtype Foo = Foo IntFoo and Int are different at compile time but same at runtime.type Foo = IntFoo and Int are same at compile time and runtime.
Type Classes
Common type classes
Eq,Ordfor equality and orderingShow,Readforshowandreadfunctions respectivelyEnumFor representing enumerators- Types in this class include
(),Bool,Char,Ordering,Int,Integer,FloatandDouble. succandpredare functions that use this type
- Types in this class include
Boundedfor types that have min and max bounds. We can domaxBound :: Int, and there is alsominBoundNum, which in turn includesIntegralandFloatingfromIntegralfunction 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] -> ais 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
aandbare called type variables. Those variables can be constrained by a type classmaxBound :: Bounded a => aor nothead :: [a] -> a
Creating Type Class instances
- We can automatically derive
Eq,Ord,Enum,Bounded,Read, andShowusing the keywordderivingwith some constraints. - To define a type class ,we make instance for a given type and use the keyword
instancefor 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 Realwould give usclass (Num a, Ord a) => Realwhich means, for something to have an instance ofReal, it must first have instances ofNumandOrd. I omitted the rest of the result of:i Real, which indicates what other functionsRealrequire
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 theNumtype 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.