Haskell Notes [Scratchpad]
[Personal Notes]
Functional
Haskell is a functional language. If you have an imperative language background, you'll have to learn a lot of new things. Hopefully many of these new concepts will help you to program even in imperative languages.
Smart Static Typing
Instead of being in your way like in C, C++ or Java, the type system is here to help you.
Purity
Generally your functions won't modify anything in the outside world. This means, it can't modify the value of a variable, can't get user input, can't write on the screen, can't launch a missile. On the other hand, parallelism will be very easy to achieve. Haskell makes it clear where effects occur and where you are pure. Also, it will be far easier to reason about your program. Most bugs will be prevented in the pure parts of your program. Furthermore pure functions follow a fundamental law in Haskell: > Applying a function with the same parameters always returns the same value.
Laziness
Laziness by default is a very uncommon language design. By default, Haskell evaluates something only when it is needed. In consequence, it provides a very elegant way to manipulate infinite structures for example.
Indentation
..is important in Haskell. Like in Python, a bad indentation could break your code!
Remark: Recursion is generally perceived as slow in imperative languages. But it is generally not the case in functional programming. Most of the time Haskell will handle recursive functions efficiently.
Introduction
Arithmetic:
x^y -> for Integrals
x**y -> for any type of number (float or double)
sum[] = 0
sum (n:ns) = n + sum ns
#recursive function for summing numbers. Sum of empty list is zero.
:type functionName
# You get the type of a function
- You cannot add or multiply numbers of vars of different types. You can specify the type using the :: operator. For example,
double x :: int = x * 2 is a function that only that takes integers
- Scripts are saved with .hs extension. To run a script type:
#ghci script.hs.
If you load a script by simply invoking its name, its functions are in the scope, but if you modify the script while working in ghci, you need to :reload.
- GHCi commands:
:load | loads a script
:reload | reloads current script
:set editor name | sets the default editor
:edit name.hs | edit name.hs using default editor
:edit | edit current script with default editor
:type expr | type of expr
:? | show all commands
:quit | quit GHCi
- White space and identation matter in Haskell. Either write with the same level of identation or write everything on one line. Use spaces and not tabs in Haskell for identation. Tabs will generate errors. If necessary convert them to 8 spaces.
- Comments are -- and extend to the end of the line. These are single line comments. Multi-line comments are {- -} and basically encapsulate multiple lines.
Lists
Lists in Haskell are basically linked-lists. Each element contains the value of the element as well as a pointer to the next element in the list. Empty lists are represented by []. The operator Cons : takes two arguments on left and right and makes a list out of them.
Syntax:
let mylist = 1 : ( 2: (3: [] ) )
is equal to
let mylist = [1,2,3]
0:mylist
[0,1,2,3]
mylist:4
[1,2,3,4]
Generators:
[1..4] = [1,2,3,4]
[1,3..10] = [1,3,5,7,9]
[2,3,5,7,11..100] => ERROR! I am not so smart!
[ [10,9..1] = [10,9,8,7,6,5,4,3,2,1]
Mapping:
Instead of using indexing on a list, we use the map function.
map functionName listName
newlist = map double mylist # map returns a new list not modifying the old one.
newlist = map (\x -> x * 2) [1,2,3]
Generating Lists:
let positiveInts = [1..]
let firstTenPositive = [1..10]
let firstTenEven = [2,4..20]
let alphabet = ['a'..'z']
List comprehension (a la Python)
let columns = ['a'..'f']
let rows = [1..6]
let coords = [(column, row) | column <- columns, row <- rows]
List Functions:
head [1,2,3] = 1
tail [1,2,3] = [2,3]
take 2 [7,8,9] = [7,8]
drop 2 [7,8,9] = [9]
sum [2,3,4] = 9
product [2,3,4] = 24
zip [1,2,3] "abc" = [(1,'a'),(2,'b'),(3,'c')]
unzip [(1,'a'),(2,'b'),(3,'c')] = ([1,2,3],"abc")
filter (/= 2) [1,2,3] = [1,3]
last[1,2,3] = 3
init[1,2,3] = [1,2] --removes the last element from a list
: The Cons.truct operator, basically construcsts a list from two lists [1,2,3]:[4,5,6] or 1:[4,5,6].
Strings:
Lists of chars. 'H' : 'ello' ~ 'Hello'. Anything that applies to strings applies to lists.
length("Hello" ++ "World")
Conditional:
if expr==True
then expr1;
expr2;
else
expr1;
expr2;
Note that the else is not optional.
Parentheses:
Use $ to get rid of parentheses. Example: print $ "Hello World"
Variables
Functions
functionaName :: ReturnType a => Arg1 -> Arg2 [Currying]
- Function: functionName arg1 arg2 ... argN = expr1;expr2;
- Function application has higher precedence than all other operations. Sometimes parentheses are needed to clear up confusion. f(x) in mathematics is f x in Haskell. f(x,y) is f x y in Haskell. f(g(x)) is f (g x), notice the difference. Parentheses are needed otherwise it would be interpreted as f(g,x) instead of f(g(x)).
- Function names: Begins with lower case, followed by n+ upper or lower case chars or numbers as well as `. If you see a function argument end with s it means the argument is a list (by convention), for example, double xs = map (*)
- Lambda functions:
double xs = map (\x -> x * 2) xs
# Here the argument is a list xs
# We use the map function to apply a lambda / anonymous function (\x -> expr) to list xs
- In Haskell, a function is a mapping of a type or more (0..n) to another type or set of types. Think of it as a set theory mapping between two groups. T1 -> T2 means a function that takes arguments of Type 1 to return arguments of Type 2.
- When you type :type double x = x * 2, you will get double :: Double -> Double, meanining double :: T1 -> T2, the :: operator means of type.
-- By default:
f g h x ⇔ (((f g) h) x)
-- the $ replace parenthesis from the $
-- to the end of the expression
f g $ h x ⇔ f g (h x) ⇔ (f g) (h x)
f $ g h x ⇔ f (g h x) ⇔ f ((g h) x)
f $ g $ h x ⇔ f (g (h x))
-- (.) the composition function
(f . g) x ⇔ f (g x)
(f . g . h) x ⇔ f (g (h x))
Useful Function Notation:
x :: Int ⇔ x is of type Int
x :: a ⇔ x can be of any type
x :: Num a => a ⇔ x can be any type a
such that a belongs to Num type class
f :: a -> b ⇔ f is a function from a to b
f :: a -> b -> c ⇔ f is a function from a to (b→c)
f :: (a -> b) -> c ⇔ f is a function from (a→b) to c
Polymorphic functions: Some functions are polymorphic such as length. Their type signatures include a "type variable", usually a lower case var(s). Think of it as Generic functions in other languages.
Curried / Partial application: Putting + in parentheses turns it into a curried function, which means (+) 1 2 3 means (1) (+ 2 3) means (+ 1 5) means 6. [TBC]
Types and Classes
Basic Types:
Bool: Either True or False
Char: Any char in the unicode system, as well as \n and \t.
String: Sequence of chars. Strings must be enclosed in double quotes.
Int (fixed-precision integers): -2^63 to 2^63 -1. Outside this range is unexpected results. 2^ 63 + 1 :: Int forces the expression to be of type Int, hence you will get a negative number. Int is unsigned.
Integer – arbitrary-precision integers. Basically it fits (like BigNum) any number as long as your memory fits that number. Int has hardware support in most computers and is therefore faster (fixed-precision is supported by hardware). Arbitrary precision like
Integer is in Software and therefore slower.
Float:Single-precision, depending on the compiler. Try sqrt 2 :: Float.
Double: double-precision of float. Try sqrt 2 :: Double.
List: Same-types enclosed in brackets, seperated by comma, for example [1,2,3]. Length[T] can be used to obtain the length of a list. Rremember a list contains elements of the same type. Use :type var to get the type of a list. A list can have a non-finite length, and Haskell deals well with inifinite length lists (lazy evaluation.). [t] means a list when asking :type for types.
Tuples: Surrounded by parentheses, multiple types allowed, seperated by a comman. A type of size 1 is not allowed. Arity of a tuple is the number of its components. There are no restrictions on the different types of a tuple, for example tuple of tuples, tuples of bool and a list, etc. Tuples have a finite arity.
Classes: Types area collection of related values. Classses area a collection of Types, that have certain overloaded operations called methods. Some built-in classes, which we inherit from, and their methods, which can be overloaded:
Eq: include == (equal) and /= (not equal) (notice the sign is forward slash not backward). These two operators are overloaded for basic types, for example you can use them on Bool, String, and any other type. False == True -> False. "Amir" /= "Amir" -> False. All basic types are instances of the class Eq, as are list and tuples.
Ord: (<), (<=), (>), (>=), min, max. All basic types are instances of the class Ord, as are are lists and tuples. Lists and tuples are ordered lexicographically, in the same way words are ordered in a dictionary (compare first, if equal, move to second, if not, stop, etc).
Show: Converts any basic type into a string. show [1,2,3] -> "[1,2,3]".
Read: Opposite of show, takes a string and converts it to a basic type. Syntax is read "String" :: TypeToConvertTo. Sometimes the Type can be inferred by GHCi so no type information is necessary.
In other words, the basic types have / are "multiple inheritance" from multiple "classes" to gain access to their generic functions. Think of Clojure traits being inherited by every type in Haskell.
Examples:
read "[1,2,3]" :: [Int]
read "5.0" :: Double
read "4" :: Int
Prelude> x = read "[1,2,3]" :: [Int]
Prelude> :type x
x :: [Int]
Num: All typse whose value is numeric. (+) (-) (*) (negate) (abs) (signum). All arguments to a function that are negative must be surrounded by parentheses. Division is a special case handled two ways one for Integers one for Floats and Doubles.
Integral: Types that are part of the Num class but also support integer addititon and integer remainder functions.
x `div` y
x `mod` y
Fractional: Types that part of the Num class but also are non-integral meaning they support fractional division and fractional reciprocation:
8.0 / 4.0 -> 2.0
recip 2 = 1/2 -> 0.5
recip 4 = 1/4 -> 0.25
Conditional: - All If statements must have an Else part.
- Haskelll provides a better way than If functions, namely guarded equations.
abs n | n >0 = n
| otherwise = -n
- "otherwise" is not necessary. It's definded as True by Prelude. Basically Haskell evaluates statements from first to last, if the nth expression is true, it stops. This is a bit similar to mathematical definitions of functions
- "|" is read as "such as"
Basically nameOfFunction Parameter | Condition1 is True = Result
Wildcard pattern: It's a bit difficult to explain but sometimes you wanna simplify multiple expressions into one. For instance the && (logical AND) is only true if a and b are true
a && a = True
_ && _ = False. -- Assuming a is a Boolean value.
Signum: Returns 1 if the value is positive, -1 if value is negative, 0 if value is zero.
Lists and List Comprehension
Recursive Functions
Modules:
Haskell libraries are distributed in modules. To import a module simply add import module.submodule to the top of the file.
If only specific function needed:
import module.submodule (function_name).
If everything EXCEPT that function
import module.submodule hiding (function_name).
If we wish to import things to reference them by the module name, try
import module.submodulbe as M
M.funct arg1
Programs:
Simply add a main function and save under .hs.
double x = x *2
main = print(double 2)
Lambda expressions:
\x -> x * 2
-- The \ means lambda in Greek letters
-- How to use them?
(\x -> x * 2) 2
map (\x -> x * 2) [1,2,3,4]
Operators:
Functions written between their arguments are called operators. A normal function can be converted to an operator by enclosing it in single backquotes. The reverse is true: take an operator (+), and put it infix in front of its arguments (+) 4 5. add x y = x + y; 5 `add` 5 = 10.
List comprehension
[x | x <- [1..10 ]]
Let
Guards
Guards are indicated by pipes that follow a function's name and its parameters. Usually, they're indented a bit to the right and lined up. A guard is basically a boolean expression. If it evaluates to True, then the corresponding function body is used. If it evaluates to False, checking drops through to the next guard and so on.
bmiTell :: (RealFloat a) => a -> String
bmiTell bmi
| 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!"
Another version:
bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| bmi <= skinny = "You're underweight, you emo, you!"
| bmi <= normal = "You're supposedly normal. Pffft, I bet you're ugly!"
| bmi <= fat = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
where bmi = weight / height ^ 2
skinny = 18.5
normal = 25.0
fat = 30.0
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.
Pattern matching
When defining a function you could define it for certain arguments abd pattern match its argument list.
Example
lucky :: (Integral a) => a -> String
lucky 7 = "LUCKY NUMBER SEVEN!"
lucky x = "Sorry, you're out of luck, pal!"
You pattern match the argument values. Yes you could do it with if statements but this is easier for the eye. Basically they are evaluated from the top to the bottom, whichever statement clicks then it stops evaluation. If it reaches x then none clicked and the x statement is triggered. When making patterns, we should always include a catch-all pattern so that our program doesn't crash if we get some unexpected input.w
Another Example:
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"
Patterns and Guards are a bit similar. Guards allow you to run tests and more flexibility. Patterns are less flexible and depend on the call value of the function.
Classes
Conditionals
Curried Functions:
Basically all functions in Haskell take one parameter which returns a value and a function call. In other words (+ 6) 5
Partial Application:
Lambda functions
anonymousFunction = (/x -> x + 1)
= (/x)
Higher-Order Functions:
Haskell functions can take functions as parameters and return functions as return values. A function that does either of those is called a higher order function. Functions that take other functions as parameters. Examples include map, filter, foldl.
Filter
Filter takes a function of type a->True, and a list, and returns only the variables for which that function evaluates as true. For example:
filter even [1..10] returns all even numbers in the sequence 1 to 10 inclusive.
Map
Map takes a function and a list and returns a new list, which is basically that function, applied to every element of the first list.
map (\x -> x*2) [1..20] -- notice the arrow direction
Fold (foldl, foldr)
Takes an operator or a function and applies between the members of a list either from the left or the right. So foldl (+) 0 lst is
(0 + 1 + foldl lst)
(1 + 2 foldl lst)
(3 + foldl lst)
Takes two arguments, a function and a number. If L, it adds that number or element to the left, if right it adds it to the right, then it inserts the function between all the members of the list, then starts processing from right (foldr) or left (foldl). The difference between foldr and foldl is that some functions do not care if applied from left to right, or right to left (the plus sign for example), but other functions might care about whether it is applied from a certain direction (the minus sign)
fold (+) 0 [1,2,3] is equal to:
1. Imagine the list and write it down [1][2][3]
2. Leave a space between each member [1] [2] [3]
3. Add the element to the left or right side depending on the version [0] [1] [2] [3]
4. Replace the space with functions and apply the function from left to right or right to left depending on the direction [0]+[1]+[2]+[3] = 6
foldr (-) 0 [1,2,3] = 1,2,3,0, 1 2 3 0, 0 - 3 - 2 - 1 = -6
foldl (-) 0 [1,2,3] = 0,1,2,3, 0 1 2 3, 0 - 1 - 2 - 3 = -6
foldr (+) 0 [1,2,3] = 1,2,3,0, 1 2 3 0, 0 + 3 + 2 + 1 = 6
foldl (+) 0 [1,2,3] = 0,1,2,3, 0+1+2+3 = 6
{-
Todo:
- Classes
- Interfaces
- File I/O
- Monads
- Real World Haskell
- Lambda Calculus
- Fix Sublime Text REPL / Haskell / Haskell IDE
-}
0 notes