Learn you a Haskell for Great Good!

IHaskell-Notebook version 0.21.9 alt text

jupyter-notebooks are an interactive 'thing'

I have heard them called:

  1. 'reproducible-research-thing'
  2. 'literate-programming-thing'
  3. 'an-application-thing-that-can-teach-haskell-coding-thing'

I'm partial to the last one ... yeah lets use that one.

To evaluate a code 'cell' denoted with:

In [ ]:

select it by clicking on it like this ...'click' and then press the Shift Ctrl/ and Enter keys at the same time

well... hold the 'shift' 'control' key down while hitting the 'enter' key 'shift' return works but also adds a new cell if it can.

This will trigger the ghci-interpreter to parse the input and then cook it down untill it is ready to be a 'printed out responce' using the ghcjs 'thing'.

That is a really high level view of the software. we will dig deeper into this stuff later/ much later ...but for now just remember

'CapitalizeCtrl/Control+Enter'.

how does a jupyter notebook really work? I have no idea! but is uses python code.

to learn more about this 'jupyter' stuff goto jupyter.org alt text

What is Haskell ?

  1. Haskell is pure
  2. Haskell is lazy alt text
  3. Haskell is statically typed ...
    • but has type inference
  4. Haskell is functional ...alt text
    • with higher-order-functions
  5. Haskell uses immutable data structures
  6. Haskell is named after 'Haskell Curry' alt text
  7. Haskell was designed by mostly really smart 'bearded-dudes'
  8. Haskell is said to be difficult to learn
    • because most programmers learned how to program a dumb way
  9. Haskell has alot of stuff

doesn't seem that hard yet... does it?

In [38]:
--programs in haskell are a series of transformations to data.
xs = [1,2,3,4,5,6]      -- this is a list of Numbers named xs.
doubleThis x = x * 2    -- this is a function that doubles stuff
map doubleThis xs       -- this transfoms our list --sort of
[2,4,6,8,10,12]
In [39]:
print xs                 -- not really remember 'immutable'
[1,2,3,4,5,6]
In [40]:
--some really basic arithmetic.
2 + 5
42 + 100
50 + (-8)
84 / 2       --below are the results of this math in order
7
142
42
42.0

alt text So here is your first assignment: write a program that multiplies two numbers and then subracts a number from that product and then add back another number to equal 42

x * y - z + q = 42

--lower case letters are just place holders.

In [41]:
-- OK! I'll show you!
50 * 100 - 4999 + 41
42
In [42]:
-- your turn!   oh yeah '--' lines are code comments.




-- Shift+Enter to run your program

how did you do?

Ok how about some Boolean algebra?
What!

&& represents logical and

|| represents logical or

not represents logical negates

In [43]:
True && False  -- should return False
Evaluate
Found:
True && False
Why Not:
False
False
In [44]:
True && True   -- should return True
Evaluate
Found:
True && True
Why Not:
True
True
In [45]:
False || True  -- True
Evaluate
Found:
False || True
Why Not:
True
True
In [46]:
not False      -- catching on?
Evaluate
Found:
not False
Why Not:
True
True
In [47]:
-- testing for equality you use '==' double equal
-- testing for un-equal you use '/=' slash equal
5 == 5 
5 /= 5
True
False


Now it's your turn -did you think i was going to do all the programing!

write an expression that uses 'Bools' using if/then/else see if you can get "nice" to print

In [48]:
--as in:
if 11 == 10 then "nice" else "oops"
"oops"
In [49]:
-- go ahead don't be shy! 
-- type some code on next line 



--and hit Shift+Enter to evaluate your code

Well how did you do? I can't tell cuz your are in the future.......

Up next: An introduction to haskell Lists

lists are like shopping lists ...sort of

they are homogenous meaning all the items need to be the same type like this:

In [50]:
lostNumbers = [4,8,16,23,42]  --lostNumbers is a List of values
lostNumbers    --prints it out the value
[4,8,16,23,42]
In [51]:
-- a common task with lists is make one list out of two lists
lostNumbers ++ [100,99,98]        -- (++) is concatination
[4,8,16,23,42,100,99,98]
In [52]:
--or did I?
lostNumbers  --eval cell to see a secret message...
[4,8,16,23,42]

All data in Haskell is Immutable which is a fancy word for you can never change a value once it is created... This seems silly at first but has the number 42 every changed during your life time? Nope! and it never will. you have to give the two lists a name to reuse it anywhere. So lets call our new list say 'newList'

In [53]:
newList = lostNumbers ++ [100,99,98]
newList   -- will print it out below
[4,8,16,23,42,100,99,98]

Names are important in Haskell. you need to use really good names cuz they are around 'forever' ... well at least while your program is running Let look at another way to 'grow' a list: segway... the colon is what we use and it's name is 'cons' which is short for 'construct'.

In [54]:
-- Oh yeah a String in haskell is a list of 'char' or characters.

-- 'colon t' is a command that lets you  see the 'type' of something
-- types are like HUGE in haskell.
:t "Some string"
-- and its not 'cons' either ...
"Some string" :: [Char]
In [55]:
-- That double colon in the output above is called 'has type' and has
--nothing to do with 'cons' either
--Here is 'cons'
'A' : " small cat"
--or
42 : [1,2,3]
--how about we stick the cat string onto the 42 list. will that work?
42 : [1,2,3] ++ 'A' : " small cat"
-- go head and try it
"A small cat"
[42,1,2,3]
No instance for (Num Char) arising from the literal `42'
In the first argument of `(:)', namely `42'
In the expression: 42 : [1, 2, 3] ++ 'A' : " small cat"
In an equation for `it': it = 42 : [1, 2, 3] ++ 'A' : " small cat"

Oh thats alot of RED stuff! Yeah you cant stick lists of strings onto lists of numbers in haskell. Each list can only have a single 'type'. remember that homo-stuff word... don't say it!

In [56]:
-- lets look at some stuff that is == 'equal' remember Bool?
[1,2,3] == 1:2:3:[]
['a','b','c','d'] == 'a':'b':'c':'d':[]
"abcd" == 'a':'b':'c':'d':[]
Use list literal
Found:
1 : 2 : 3 : []
Why Not:
[1, 2, 3]
Use string literal
Found:
['a', 'b', 'c', 'd']
Why Not:
"abcd"
Use list literal
Found:
'a' : 'b' : 'c' : 'd' : []
Why Not:
['a', 'b', 'c', 'd']
Use list literal
Found:
'a' : 'b' : 'c' : 'd' : []
Why Not:
['a', 'b', 'c', 'd']
True
True
True
In [57]:
{-
Thats alot of stuff printed out but in the end you see 'True'
 why... cuz they are.
 the right side is equal to the left side. So it evaluated to 'True'
 the right side is 'sugar'd or 'frosted' I like to say!
 do you remember 'lazy' yea everyone is!
 its just alot less keys to press ... oh my fingers are tired!
 OK thats alot of gibber/jabber lets get back to writing haskell
-}

"Steve Buscemi" !! 6

-- what the hell is that 'bang' bang' == !!
-- Shift+Enter to find out....

-- {- blah,blah,blah -}  is how you do a block-comment
'B'
In [58]:
-- !! is how you pull out the 'n'th item in a list. computer people 
-- start counting at zero,one,two... lazy and fussy are we!
-- lists can contain other lists like this:
[[1,2,3],[10,9,8],[42]]
-- programmers like the number 42 and the word 'foo' I'm not sure why?
[[1,2,3],[10,9,8],[42]]
In [59]:
--see if you make a list hold another list or two
--you have to put the comas and braces just SOOOOOO!

myNestedList = undefined

-- replace 'undefined' with your nested list.
-- then Shift+Enter to run it.... I'm not going to tell you that
-- anymore... the shifting thing that is.
In [60]:
--Lists can also be compared to each other 
--using > greater-than, < less-than and >= or <= or == or /=
--Lists are compared in lexicographical ... from the left one at a time.
[3,2,1] > [2,1,0]
[3,2,1] > [2,20,100]
[1,3,42] > [2,4,43]
[2,3,4] /= [2,42,43]
True
True
False
True
In [61]:
--see if you can write two lists that are close but not quite equal
--using 'chars' witch are just 'a' or 'G' or 'c' or 'q'
--chars are compared alphabetically so 'a' < 'b' or is it?

'a' > 'b'
'z' > 'a'
-- ['a','c','e'] ? ['a','c', 'f']
-- remove the -- just above and fill in the ? with <,>,==, or @
False
True

alt text

In [62]:
--another way to pick apart lists is to use 'head' and 'tail'
head [1,2,3,4]
tail [1,2,3,4]
head []  --what is the head of nothing? oh no more 'red-ink'
1
[2,3,4]
Prelude.head: empty list
In [63]:
--haskell also has a 'last' function for lists
last [1,2,3,4]
--and 'init'
init [1,2,3,4]
4
[1,2,3]
In [64]:
--'cycle' takes a list and cycles it into an infine list --ooohh!
--'take' trims it off so your computer won't catch on fire!
take 42 (cycle ['a'..'z'])
"abcdefghijklmnopqrstuvwxyzabcdefghijklmnop"

I'm gonna skip over type-classes cuz they are over my head as such! we will revisit them shortly.

Next up is: Function Syntax and specifically 'pattern matching'

In [65]:
--you specify a pattern to which some data should conform and if
--it does you 'deconstruct' the data according to those patterns

--trivial example--
lucky :: (Integral a) => a -> String
lucky 7 = "LUCKY NUMBER SEVEN"
lucky x = "Sorry, you're out of luck, pal!"

lucky 42
lucky 7
"Sorry, you're out of luck, pal!"
"LUCKY NUMBER SEVEN"
In [66]:
--here is how you could use pattern matching to return a number/String
--from guessing from one..five

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"

sayMe 4
sayMe 2
sayMe 42
"Four!"
"Two!"
"Not between 1 and 5"

factorial :: (Integral a) => a -> a
factorial 0 = 1  -- when factorial matches 0 it returns 1
factorial n = n * factorial (n - 1)

calling factorial 3 ...

factorial is not =0 so it tries 3 * factorial 2 ..recurses again

factorial is not =0 so it tries 3 (2 factorial 1) ..recures again

factorial is not =0 so it tries 3 (2 (1 factorial 0)) ...

here since we defined factorial 0 = 1 our function 'expands/resolves'

into:

3 (2 (1 * 0)) --substitute 0 for 1

3 (2 (1 * 1)) --reduces to

3 (2 1) --reduces to

3 * 2 --reduces to

6

In [67]:
factorial :: (Integral a) => a -> a
factorial 0 = 1  -- when factorial 0 hits is just returns 1 and finishes
factorial n = n * factorial (n - 1)
--more on recursion comming up...

factorial 3
6
In [68]:
--patterns must be 'exhaustive'...cover all inputs...

--here is some pattern matching on adding 2 tuples...

addVectors :: (Num a) => (a,a) -> (a,a) -> (a,a) --two pairs for params 
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
--and call it with:
addVectors (3,5)(6,2)
(9,7)
In [69]:
--here is how you DIY --fst/snd for triple-tuples
-- the underscores mean we don't care about that pattern/position
first :: (a, b, c) -> a
first (x, _, _) = x

second :: (a, b, c) -> b
second (_, y, _) = y

third :: (a, b, c) -> c
third (_, _, z) = z

first ('a', 'b', 'c') 
second ('a', 'b', 'c')
third ('a', 'b', 'c')
'a'
'b'
'c'
In [70]:
--here we pattern match on a list-comprehension
xs = [(1,3), (4,3), (2,4), (5,3), (5,6), (3,1)] 
[a + b | (a, b) <- xs]  -- and xs == the list of tuples above
[4,7,6,8,11,4]
In [71]:
--here is DIY head function --remember that cute inch-worm from before
head' :: [a] -> a
head' [] = error "Can't call head on an empty list, dummy!"
head' (x:_) = x

head' [4, 5, 6]
head' ["ghci", "build", "stack"]
head' []
4
"ghci"
Can't call head on an empty list, dummy!
In [72]:
--your turn... didn't think you had to do any more work ... Not!
--write a function that shows the first and second items in a list
--dont forget to make it 'exhaustive'
--i'll write this much for you:


tell :: (Show a) => [a] -> String
tell [] = "The list is empty"
tell (x:[]) = "The list has one element: " ++ show x
-- show first and second items here
-- show how to handle rest of 5 item list

tell []
tell ['a']
tell ['a', 'b']
tell ['a', 'b', 'c', 'd', 'e']
Use string literal
Found:
['a']
Why Not:
"a"
Use string literal
Found:
['a', 'b']
Why Not:
"ab"
Use string literal
Found:
['a', 'b', 'c', 'd', 'e']
Why Not:
"abcde"
"The list is empty"
"The list has one element: 'a'"
:(2,1)-(3,52): Non-exhaustive patterns in function tell
In [73]:
--lets try some more stuff with recursion

length' :: (Num b) => [a] -> b
length' [] = 0                --the length of an empty list is 0
length' (_:xs) = 1 + length' xs 
--here we chopped of the head and call that 1
--(+) the rest of the list
--basically we keep chopping and adding 1 till we hit the [].
--then add it all up

length' "Ham Bone"
length' "Ham"
length' "Bone"
8
3
4
In [74]:
--lets do our own version of sum
sum' :: (Num a) => [a] -> a
sum' [] = 0                 --sometimes called base-case
sum' (x:xs) = x + sum' xs
--could it be that simple? indeed
sum' [1,2,3,4]
sum' [22,1,1]
10
24
In [75]:
--patterns are kinda like an alias 
-- xs@(x:y:ys) --so you can say 'xs' instead of '(x:y:ys)'
--yet another form of lazy :)
capital :: String -> String
capital "" = "Empty String, whoops!"
capital all@(x:xs) = 
  "The first letter of " ++ all ++ " is " ++ [x]

capital "Dracula"
capital "TacoCat"
"The first letter of Dracula is D"
"The first letter of TacoCat is T"

alt text

Guards, guards!

Instead of explaining 'guards' syntax, lets write some in code

In [76]:
--guards provide several bools --first True is eval'd.
bmiTell :: (RealFloat a) => a -> String
bmiTell bmi   --no '=' after function name/params 
  | bmi <= 18.5 = "You're underweight"
  | bmi <= 25.0 = "You're supposedly normal"
  | bmi <= 30.0 = "You so big you scare my wife!"
  | otherwise   = "You are a whale congrats!"
--otherwise = True --to make 'exhaustive' 
  
bmiTell 17.5
bmiTell 29.40
bmiTell 42.42
"You're underweight"
"You so big you scare my wife!"
"You are a whale congrats!"
In [77]:
--lets make our own max function. 
--two args must be comparible
max' :: (Ord a) => a -> a -> a
max' a b   -- no 'equals-sign'
  | a > b = a
  | otherwise = b
  
  
max' 7 9
max' 10 1
max' 'a' 'z'
9
10
'z'
In [78]:
--lets write our own version of compare

:t compare  --ghci version

myCompare :: (Ord a) => a -> a -> Ordering

a `myCompare` b   --defined as infix `sometimes` is better
  | a > b = GT
  | a == b = EQ
  | otherwise = LT
 
:t myCompare 
compare 3 2
3 `myCompare` 4
myCompare 42 42
compare :: forall a. Ord a => a -> a -> Ordering
myCompare :: forall a. Ord a => a -> a -> Ordering
GT
LT
EQ
In [79]:
--OK now!  -thats how you begin again after finishing and 
--aren't sure what's next
--Oh yeah! Where? ...were we...
--'where' can be used with guards to keep things DRY.
--here is our lastest bmi function:

{- 
bmiTell :: (RealFloat a) => a -> a -> String  
bmiTell weight height  
    | weight / height ^ 2 <= 18.5 = "You're underweight, you emo, you!"  
    | weight / height ^ 2 <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"  
    | weight / height ^ 2 <= 30.0 = "You're fat! Lose some weight, fatty!"  
    | otherwise                   = "You're a whale, congratulations!"  
-}    

-- and here it is using 'where'    

bmiTell':: (RealFloat a) => a -> a -> String
bmiTell' weight height
  | bmi <= 18.5 = "Your underweight,..."
  | bmi <= 25.0 = "Your normal, more or less"
  | bmi <= 30.0 = "You so fat!"
  | otherwise =  "Your a whale, conrats"
  where bmi = weight / height ^ 2

  
bmiTell' 85 1.90
"Your normal, more or less"
In [80]:
--here is a bmiCalc that returns a list of bmi's
calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi w h | (w, h) <- xs]
  where bmi weight height = weight / height ^ 2
  
calcBmis [(85, 1.90),(81, 1.90), (78, 1.90)]
[23.545706371191137,22.437673130193907,21.606648199445985]

alt text

In [81]:
--'Let it be' was sung by Paul not George!
--'let'<bindings>'in' <expression> haskell is sung by Alanzo C.!
4 * (let a = 9 in a + 1) +2   --see what i mean about 42's
--they can also be used as functions in local scope*
[let square x = x * x in (square 5, square 3, square 2)]
--will this work?
(let a = 100; b = 200; c = 300 
   in a*b*c,
 let foo = "Hey "; bar = "there!" 
   in foo ++ bar)
42
[(25,9,4)]
(6000000,"Hey there!")
In [82]:
--looks like it did! dammn!
--'lets' look at some more examples
let zoot x y z = x * y + z
zoot 3 9 2
let boot x y z = x * y + z in boot 3 4 2
--is 'boot' still in scope?  no- not the mouthwash silly
boot --outside of let expression it is out of scope. don't!
29
14
Not in scope: `boot'
Perhaps you meant `zoot' (line 1)

Case expressions in Haskell

alt text

In [83]:
--both of these are 'interchangeable'
--one is just a sugar frosted version of the other
--guess which ?
head' :: [a] -> a
head' [] = error "Empty lists have no head"
head' (x:_) = x
--or
head_ :: [a] -> a
head_ xs =
  case xs of
    [] -> error "Empty lists tell no lies"
    (x:_) -> x
    

head' [1,2,3,4]

head_ [2,3,4,5]
 
head_ []
1
2
Empty lists tell no lies
In [84]:
--one more case for case's
describeList :: [a] -> String
describeList xs = 
  "The list is " ++
    case xs of
      [] -> "empty"
      [x] -> "a singleton list"
      xs -> "a longer list"
      
describeList []
describeList [2]
describeList [2,3,4]
"The list is empty"
"The list is a singleton list"
"The list is a longer list"

next up

Recursion

alt text

In [85]:
--In haskell recursion is important ... declare what something 'is'
                                    -- not 'how' you get it.
--it is mandatory that we look at the 
--fibonacci secquence yet again:

fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

fib 20
6765
In [86]:
--Maximum awesome-ness
--maximum' :: (Ord a) => [a] -> a

maximum_ [] = error "maximum of empty list is just dum!"
maximum_ [x] = x
maximum_ (x:xs) = max x (maximum_ xs)
--maximum of a lsit is the max of head and maximum of tail

:t max
:t maximum_  

maximum_ [22,42,33,9]
maximum_ [2,5,7]
max 2 (maximum_ [5,7])
maximum_ [] --look out you dummie!
max :: forall a. Ord a => a -> a -> a
maximum_ :: forall t. Ord t => [t] -> t
42
7
7
maximum of empty list is just dum!
In [87]:
--Haskell-prelude version
:t replicate
replicate 3 5

--our DIY version


replicate_ n x
  | n <= 0 = []
  | otherwise = x:replicate_ (n-1) x
:t replicate_
replicate_ 5 3
replicate :: forall a. Int -> a -> [a]
[5,5,5]
replicate_ :: forall a t. (Num a, Ord a) => a -> t -> [t]
[3,3,3,3,3]

alt text

In [88]:
:t take -- prelude version for comparison sake'

take' :: (Num i, Ord i) => i -> [a] -> [a]
take' n _
  | n <= 0 = []
take' _ [] = []
take' n (x:xs) = x : take' (n-1) xs

take' 3 [4,3,2,1]
take :: forall a. Int -> [a] -> [a]
[4,3,2]
In [89]:
--reverse simply reverses a list. no kidding wow-wee
:t reverse -- again for comparisonism

reverse' :: [a]-> [a]
reverse' [] = [] -- look a nothing in the rearview mirror/ same-nothing
reverse' (x:xs) = reverse' xs ++ [x] --rev' tail and stick head back on it

reverse' [] 
reverse' [5,4,3,2,1]
reverse' ['c','b','a']
Use string literal
Found:
['c', 'b', 'a']
Why Not:
"cba"
reverse :: forall a. [a] -> [a]
[]
[1,2,3,4,5]
"abc"
In [90]:
--ok now lets look at repeat/repeat/repeat/repeat...
:t repeat -- you know why? don't play dum with me!

repeat' :: a -> [a]
repeat' x = x:repeat' x --could that possibly work? yeah FOREVER!
--thats why you need to 'take' just so much


take 5 (repeat' 3)  --now that's better

take 5 repeat' 3   --error take does not take 3 args but two....
                    --don't even mention repeat is totally 'foo-barred'
repeat :: forall a. a -> [a]
[3,3,3,3,3]
Couldn't match expected type `Integer -> t' with actual type `[a0]'
Relevant bindings include it :: t (bound at :1:1)
The function `take' is applied to three arguments,
but its type `Int -> [a0] -> [a0]' has only two
In the expression: take 5 repeat' 3
In an equation for `it': it = take 5 repeat' 3

Couldn't match expected type `[a0]' with actual type `a1 -> [a1]'
Probable cause: repeat' is applied to too few arguments
In the second argument of `take', namely repeat'
In the expression: take 5 repeat' 3
In [91]:
--now comes 'zip' which take two lists and 'zips' them together...
:t zip -- again for compareishness , i like it...

zip' :: [a] -> [b] -> [(a,b)]
zip' _ [] = []
zip' [] _ = []
zip' (x:xs) (y:ys) = (x,y):zip' xs ys  --why i never!

zip' [1,2,3] ['a','b','c', 'd']
Use string literal
Found:
['a', 'b', 'c', 'd']
Why Not:
"abcd"
zip :: forall a b. [a] -> [b] -> [(a, b)]
[(1,'a'),(2,'b'),(3,'c')]

alt text

Quick, sort!

In [92]:
-- I've been looking forward to using him!!!

--and now a classic algorIthmism 'Quick-sort' 

quicksort :: (Ord a) => [a] -> [a]
quicksort [] = []
quicksort (x:xs) =
  let smallerSorted = quicksort [a | a <- xs, a <= x]
      biggerSorted = quicksort [a | a <- xs, a > x]
   -- |need to be flush @ smaller... and bigger...
  in smallerSorted ++ [x] ++ biggerSorted
  
--tesing 1,2,3
quicksort [10,2,5,3,1,6,7,4,2,3,4,8,9] 
quicksort "the quick brown fox jumps over the lazy dog"
-- did not see that comming!

-- for diaramma below
quicksort [5,1,9,4,6,7,3]
[1,2,2,3,3,4,4,5,6,7,8,9,10]
"        abcdeeefghhijklmnoooopqrrsttuuvwxyz"
[1,3,4,5,6,7,9]
  • this is a direct cut/paste from origin-master l.y.a.h.f.g.g (CL)

So if we have, say [5,1,9,4,6,7,3] and we want to sort it, this algorithm will first take the head, which is 5 and then put it in the middle of two lists that are smaller and bigger than it. So at one point, you'll have [1,4,3] ++ [5] ++ [9,6,7]. We know that once the list is sorted completely, the number 5 will stay in the fourth place since there are 3 numbers lower than it and 3 numbers higher than it. Now, if we sort [1,4,3] and [9,6,7], we have a sorted list! We sort the two lists using the same function. Eventually, we'll break it up so much that we reach empty lists and an empty list is already sorted in a way, by virtue of being empty. Here's an illustration:

atl text

An element that is in place and won't move anymore is represented in orange. If you read them from left to right, you'll see the sorted list. Although we chose to compare all the elements to the heads, we could have used any element to compare against. In quicksort, an element that you compare against is called a pivot. They're in green here. We chose the head because it's easy to get by pattern matching. The elements that are smaller than the pivot are light green and elements larger than the pivot are dark green. The yellowish gradient thing represents an application of quicksort.

Thinking recursively-ish

alt text

a pattern is emerge'n here with this recursion thing

  1. define your base case
  2. define your function that does something to an element
  3. have that same function applied to the rest of the stuff

Higher Order Functions

Functions that can take other functions as parameters and can return a function as a result-value is concidered a "HOA" /see above

all functions in haskell are 'curried' which basically means they take one parameter at a time and return a function that either takes the next parameter or if it's the last param returns the final value.

In [93]:
--lets look at some sample code:

max 4 5 == (max 4) 5  --they should be equal
Redundant bracket
Found:
(max 4) 5
Why Not:
max 4 5
True
In [94]:
--partial application or partially applied functions
-- offensively simple version:
multThree :: (Num a) => a -> a -> a -> a
multThree x y z = x * y * z
multTwoWithNine = multThree 9
--call function 
multTwoWithNine 2 3
--name a partially applied multTwoWithNine function
multWithEighteen = multTwoWithNine 2
--call new function with one more arg.
multWithEighteen 10
54
180
In [95]:
--lets create a function that takes a number and 
--compairs it to 100 and return GT LT or EQ
compareWithHundred :: (Num a, Ord a) => a -> Ordering
compareWithHundred x = compare 100 x
--call with different params
compareWithHundred 99
compareWithHundred 101
compareWithHundred 100
GT
LT
EQ
In [96]:
--the same function can be written this way also:
compareWithHundred' = compare 100
--call function with same args as above
compareWithHundred' 99
compareWithHundred' 101
compareWithHundred' 100
GT
LT
EQ
In [97]:
--Infix functions can also be parially applied using 'sections'
--to section apply parens and only add arg to one side only
--like this:
divideByTen :: (Floating a) => a -> a
divideByTen = (/ 10)
-- call function with other side argument
divideByTen 200
-- same as
200 / 10
-- or
(/10) 200
20.0
20.0
20.0
In [98]:
--what about just calling multThree 3 4 ... lets see
multThree 3 4
Unshowable:a0 -> a0)) (maybe you haven't applied enough arguments to a functionNo instance for (Show (a0 -> a0))
(maybe you haven't applied enough arguments to a function?)
arising from a use of `print'
In the first argument of `print', namely `it'
In a stmt of an interactive GHCi command: print it
In [99]:
--that message is saying there is no 'Show' typeclass for the 
--function (a0 -> a0)
--just name it 'some' 'thing' and give it the last arg.
--as in:
someThing = multThree 3 4
someThing 1
12

alt text

In [100]:
-- i love the drunk octopi! not sure why
-- this function takes a function and then applies it twice to 
-- 'something' ... not the previous 'some' 'thing'

applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)

-- how would you call this function?

applyTwice (+3) 3
applyTwice (/10) 100
applyTwice (multThree 2 2) 1
applyTwice (3:) [42]
9
1.0
16
[3,3,42]
In [101]:
:t zipWith  -- from the mother ship!
zipWith :: forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
In [102]:
--here is our version
zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith' _ [] _ = []
zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ys

-- here is what it can do:

zipWith' (+) [4,2,5,6] [2,6,2,3]
zipWith' max [4,2,5,6] [2,6,2,3]
zipWith' (zipWith' (*)) [[4,2,5,6],[2,6,2,3],[4,2,5,6]]
                        [[2,6,2,3],[4,2,5,6],[2,6,2,3]]
[6,8,7,9]
[4,6,5,6]
[[8,12,10,18],[8,12,10,18],[8,12,10,18]]
In [103]:
flip' :: (a -> b -> c) -> (b -> a -> c)
flip' f = g
  where g x y = f y x
-- this is equivalent
flip_ f y x = f x y

zip function straight up:

In [104]:
zip [1,2,3,4,5] "hello"
[(1,'h'),(2,'e'),(3,'l'),(4,'l'),(5,'o')]

and then 'flip'd

In [105]:
flip' zip [1,2,3,4,5] "hello"
[('h',1),('e',2),('l',3),('l',4),('o',5)]
In [106]:
zipWith div[4,2,5,6] [2,6,2,3]
zipWith (flip' div) [4,2,5,6] [2,6,2,3]

-- not sure what this use case would be ...
[2,0,2,2]
[0,3,0,0]

Here comes the bread and butter

map

and

filter

In [107]:
-- map uses a function  of (a to b),
-- with an arg of list of [a's] 
-- and returns a list of [b's]

:t map -- from prejude
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs

-- lets use it and see what we get...

map (+3) [1,2,3,4,5]
map (^2) [1,2,3,4,5]
map (replicate 4) [3..7]
map fst [(1,4),(3,5),(6,3),(2,6)]
map :: forall a b. (a -> b) -> [a] -> [b]
[4,5,6,7,8]
[1,4,9,16,25]
[[3,3,3,3],[4,4,4,4],[5,5,5,5],[6,6,6,6],[7,7,7,7]]
[1,3,6,2]
In [108]:
-- filter uses a predicate function of (True or False),
-- with an arg of list of [a's]
-- and returns  list of [a's] that meet the predicate

filter :: (a -> Bool) -> [a] -> [a]
filter _ [] = []
filter p (x:xs)
    | p x = x : filter p xs
    | otherwise = filter p xs
    
-- now lets call it some

filter (> 3) [1,2,3,4,5,6,7]

let notNull x = not (null x) 
  in filter notNull [[1,2,3],[],[3,4,5],[2,2],[],[],[]]  

filter (`elem` ['A'..'Z']) "i lauGh At You BecAuse u r aLL the Same"
[4,5,6,7]
[[1,2,3],[3,4,5],[2,2]]
"GAYBALLS"
In [109]:
:t "GAYBALLS"

-- yeah so "GAYBALLS" is a list of Char so it returning a list.
"GAYBALLS" :: [Char]
In [110]:
--Here is our previous 'Quick-sort' using filter

quicksort_ :: (Ord a) => [a] -> [a]
quicksort_ [] = []
quicksort_ (x:xs) =
    let smallerSorted = quicksort_ (filter (<=x) xs)
        biggerSorted = quicksort_ (filter (>x) xs)
    in smallerSorted ++ [x] ++ biggerSorted
    
quicksort_ [3,6,5,2,8,9]
[2,3,5,6,8,9]

next in line is

takeWhile

In [111]:
--find the sum of all odd squares that are smaller than 10,000

sum (takeWhile (<10000) (filter odd (map (^2) [1..])))

--looks kinda lisp'e with all the closing parens......
166650
In [119]:
nums = map (^2) [1..] 
oddnums = filter odd nums
lastTime = takeWhile (< 10000) oddnums
sum lastTime
166650
In [120]:
let nums = map (^2)[1..]
    filter odd nums = oddnums
    lastChunk = takeWhile (< 10000) oddnums
    in sum lastChunk
166650

Collatz sequencs:

In [114]:
chain :: (Integral a) => a -> [a]
chain 1 = [1] --base/edge case
chain n 
    | even n = n:chain (n `div` 2)  --if even divide it by 2
    | odd n = n:chain (n * 3 + 1)   --if odd do this
    
-- lets see what it does
chain 5
chain 10
chain 1
chain 30
[5,16,8,4,2,1]
[10,5,16,8,4,2,1]
[1]
[30,15,46,23,70,35,106,53,160,80,40,20,10,5,16,8,4,2,1]

Now what we want to know is this: for all starting numbers between 1 and 100, how many chains have a length greater than 15? First off, we'll write a function that produces a chain:

In [115]:
numLongChains :: Int
numLongChains = 
  length (filter isLong (map chain [1..100]))
    where isLong xs = length xs > 15
In [116]:
-- first we 'map chain' to a 'range' to get list of chains
map chain [1..5] --used smaller range for smaller output

-- we need to define isLong function
isLong_ xs = length xs > 15

-- next we 'length filter f map chain' gives num of list you have > 15
length (filter isLong_ (map chain [1..100]))
[[1],[2,1],[3,10,5,16,8,4,2,1],[4,2,1],[5,16,8,4,2,1]]
66

Lambda == anonymous function == (\xs -> length xs > 15)

alt text

In [117]:
--since 'isLong' is only used once we could use a 'lambda'
-- a 'lamba is an unnamed function you only use once.
numLongChains_ :: Int
numLongChains_ = 
  length (filter (\xs -> length xs > 15)(map chain [1..100]))
-- this is a lambda: (\xs -> length xs > 15)
numLongChains_
66
In [15]:
listOfFunctions = map (*) [0..] -- [0.. is an infinite list..]
-- (listOfFunctions !! 4) returns partial function == (4)  
(listOfFunctions !! 4) 5  -- and multiplies it by (4)5 or 4*5
20
In [12]:
zipWith (\a b -> (a * 30 + 3) / b)[5,4,3][1,2,3]
[153.0,61.5,31.0]
In [11]:
-- this is long-hand-version of above...
((5 * 30) + 3) / 1
((4 * 30) + 3) / 2
((3 * 30) + 3) / 3
153.0
61.5
31.0
In [31]:
addThree x y z = x+y+z 
addThreeLambda = \x -> \y -> \z -> x+y+z
addThree 1 2 3
addThreeLambda 1 2 3
6
6

alt text