-- Time-stamp: <Tue Stardate: Stardate: [-27]1874.01 hwloidl>
--
-- Simple examples from the Haskell intro of F21DP
-- See slides at: http://www.macs.hw.ac.uk/~hwloidl/Courses/F21DP/l15_handout.pdf
--
-- -----------------------------------------------------------------------------
-- Using the command-line interpreter (ghci):
-- Make sure that you have ghc (Glasgow Haskell Compiler) installed on your system
-- Run these examples like this: ghci simples.hs
-- Now you should see a prompt like this
-- #>
-- type in Haskell expressions to evaluate them, following the instructions in this file
-- In all examples below, #> represents the prompt, so you should just type in the text after the prompt.
-- -----------------------------------------------------------------------------
-- Using the web-based IDE:
-- If you want to use the web-based IDE at the School of Haskell Web page (https://www.fpcomplete.com/school), 
-- follow this link: https://www.fpcomplete.com/clone-active-code/VyMbsozGDE?env=ghc-7.8-stable-14.09
-- and replace the Haskell code with the one from these examples.
-- You'll find more links to the IDE embedded in the courses on this web site.
-----------------------------------------------------------------------------
--
-- Installing ghc: 
-- On Ubuntu-based systems the following should be sufficient
-- sudo apt-get install ghc
-- Depending on your setup you may have to enable the 'multiverse' repository first
-- To do so, just uncomment this line at the end of /etc/apt/sources.list, by removing the leading '#'
-- # deb http://archive.ubuntu.com/ubuntu/ trusty-security multiverse
--
-- You are encouraged to run this from inside a docker container, to start with a clean setup and
-- an isolate setup changes in that container (see http://www.macs.hw.ac.uk/~hwloidl/Courses/F21DP/docker_notes.txt
-- for details)
--
-----------------------------------------------------------------------------

-- at the beginning you import code from other modules; 
-- these are only needed for the monadic examples at the very end

import System.Environment(getArgs)  -- needed for reading from command line
-- import System.Random                -- needed for mkRandomList

-- NB: for these simple examples you need to avoid name clashes with Prelude (library)
--     functions that are automatically imported
--     for example, we use 'len' below, to avoid a clash with the existing function 'length'
len   :: [a] -> Int 
len [] = 0 
len (x:xs) = 1 + len xs 
-- Observation: the x, bound in the pattern matching is never used on the right-hand-side
-- Exercise: how do you change that code to make explicit that the head of the list is not used?

-- Example of using len
xs = [2,3,5,7,11,13]
n = len xs
-- NB: if you work in the interpreter, and you want to define a value like n above, type:
-- #> let n' = len xs
-- Exercise: try that; check that the value of n and of n' are the same

-- You can of course use a built-in function that computes the list length; it is called 'length'
-- type in the interpreter:
-- #> let n'' = length xs
-- Exercises: try that; check that the value of n'' is the same as that for n and n'

-- More examples of list functions are at the end of the "Haskell Basics" section of the Haskell Intro:
-- https://www.fpcomplete.com/school/starting-with-haskell/introduction-to-haskell/1-haskell-basics
-----------------------------------------------------------------------------
-- Example of a data-type
data MyList a = Nil 
              | Cons a (MyList a)

-- Exercise: define mylen, doing the same as 'len' above, but on the type MyList
-- mylen :: MyList a -> Int
-- mylen ...

-- Example of a type synonym
type MyIntList = MyList Int

-- Example of using this data-type (note that xs' represents the same list as xs)
-- The ' in xs' is part of the name; no magic there
xs' :: MyIntList
xs' = Cons 2 (Cons 3 (Cons 5 (Cons 7 (Cons 11 (Cons 13 Nil))))) 
-- Exercise: enable the code below, with your definition of mylen (if you do this in the interpreter, use a let)
-- n' = mylen xs'

-- Example of a tuple,  used to encode the first and last-name of a person
type Name = String
type Person = (String, String)

person1 :: Person
person1 = ("John", "Smith")
-- use either fst and snd to get the components like this
fname = fst person1
sname = snd person1
-- or use pattern matching like this
get_fname (x,y) = x
-- Exercise: define a function for getting the surname
-- get_sname ...

-- a simple increment fct; note that Integer means arbitrary precision integer numbers (Int is fixed prec)
inc   :: Integer -> Integer
inc n = n+1

-- use it
five = inc (inc 3) 
-- Exercise: what is the expected value and the expected type?

-- our initial example
fac :: Integer -> Integer
fac 1 = 1
fac n = n*fac (n-1)

-- another version of factorial, over an interval that is halved in each step of the recursion
fact :: Integer -> Integer
fact n = fact' 1 n

fact' :: Integer -> Integer -> Integer
fact' m n
 | m == n    = m
 | otherwise = (left * right)
	       where mid = (m + n) `div` 2
	             left = fact' m mid
	             right = fact' (mid+1) n

-- another data type (NB: you need the 'deriving' part in order to be able to print and display a value
data Day = Mon | Tue | Wed | Thu | Fri | Sat | Sun
     deriving (Show)

-- the next day function
next      ::  Day -> Day
next Mon  =  Tue
next Tue  =  Wed
next Wed  =  Thu
next Thu  =  Fri
next Fri  =  Sat
next Sat  =  Sun
next Sun  =  Mon 

-- You can find a lot more examples in the "Algebraic Datatypes" section of the Haskell Intro:
-- https://www.fpcomplete.com/school/starting-with-haskell/introduction-to-haskell/2-algebraic-data-types
-----------------------------------------------------------------------------
-- tree examples
-- see tree.hs for a definition of Tree a and fringe
-- follow the instructions in this file to do the exercises
-----------------------------------------------------------------------------
-- more list examples

-- string examples
hi = "hello" ++ (' ' : ['w','o', 'r', 'l', 'd'])
-- NB: a string is just a list of characters; the above concatenates 2 strings

-- take the first n elems; again, beware of a name clash with the prelude function
mytake  0  _       =   []
mytake  _  []      =   []
mytake  n  (x:xs)  =   x : mytake (n-1) xs
-- Exercise: use this definition to extract just the "hello" part from the string hi above

-- guarded patterns: you can define conditions that need to be true for one branch to match
sign x  |  x >  0   =   1
        |  x == 0   =   0
        |  x <  0   =  -1

-----------------------------------------------------------------------------
-- for different variants of the sum-of-squares running example, see sqs.hs
-----------------------------------------------------------------------------

-- a sorting function; simples!
quicksort   []     =  []
quicksort   (x:xs) =
   quicksort [y | y <- xs, y<x ] ++
   [x] ++ 
   quicksort [y | y <- xs, y>=x]

-- Exercise: test it on a list of values from 9 down to 1
-- #> quicksort [9,8..1]

-- Exercise: use the mkRandList function below to generate a list of random values and then sort it

-- higher-order fcts
add x y =  x + y

-- alternative definition of inc (see above)
-- inc :: Integer -> Integer
-- inc =  add 1 

ys = map inc [1..10] 
-- Exercise: what is the result of evaluating ys?

-- infinite data structures
numsFrom n   =   n : numsFrom (n+1)
squares      =   map (^2) (numsFrom 0)

zs = take 5 squares
-- Exercise: what is the result of evaluating zs

-- this is a more verbose version of squares, working on a list
verbose_squares :: [Integer] -> [Integer]
verbose_squares xs' = 
  if (null xs')
    then []
    else let 
           x = head xs'
           xs = tail xs'
         in 
           x^2 : verbose_squares xs  
-- try this: 
-- #> verbose_squares [1..10]

-- see sqs.hs for more examples of the sum-of-squares function
sqs_even xs = [ x^2 | x <- xs, even x]

-- Example: Sieve of Erathostenes  -------------------------------------------------------
-- iteratively remove all multiples of identified prime numbers
sieve :: [Integer] -> [Integer]
sieve (p:xs) = p : sieve (removeMults p xs)

-- remove all multiples of a given number
removeMults :: Integer -> [Integer] -> [Integer]
removeMults p xs = [ x | x <- xs, not (x `rem` p == 0) ]

-- define an infinite list of prime numbers
-- beware, this is an infinite list
primes :: [Integer]
primes = sieve [2..]
-- sieve (p:xs) = p : sieve (removeMults p xs)
-- removeMults p xs = [ x | x <- xs, not (x `rem` p == 0) ]

-- Exercise: use the definition of primes to print all prime numbers less than 100

-----------------------------------------------------------------------------
-- monadic code to read arguments from the command-line
-- needs this import at the beginning of the file
-- import System.Environment(getArgs)
-- then, define a main function like this (beware of indentation!)
{-
-- use this as the main function in a separate file, to read 2 values from the command line and process them
main = 
 do 
   args <- getArgs        -- read from commandline
   let x = read (args!!0) -- first argument
   let y = read (args!!1) -- second argument
   putStrLn (show (sqs_even [x..y]))  -- process the input
-}

{-
-- monadic code, and I/O operations
myAction :: String -> IO Int  -- define an IO-action, that takes a string as input
myAction fn = 
 do                     -- this starts a block of monadic actions
   str <- readFile fn   -- perform an action, reading from file
   let lns = lines str  -- split the file contents by lines
   let nums = map read lns  -- turn each line into an integer value
   let res = sum nums  	-- compute the sum over all integer values
   print res            -- print the sum
   return res           -- return the sum

writeSample :: String -> Int -> Int -> Int -> IO ()
writeSample fn low high len =
 do 
   xs <- mkRandList low high	                   -- generate a list of random numbers
   writeFile fn (unlines (map show (take len xs))) -- write the list to file, line by line

seed = fromIntegral (primes!!99) -- my favourite prime number

-- generate a list of random numbers   
-- needs: import System.Random
mkRandList :: Int -> Int -> IO [Int]
mkRandList low high = 
        do
          let g = mkStdGen seed
          setStdGen g
	  let xs = randomRs (low,high) g  -- generate an infinite list of random numbers 
	  return xs

-- to test this code, type the following in ghci
-- #> writeSample "numbers.txt" 1 65535 99
-- -- this generates a file "numbers.txt" with 99 lines, each containing a value btw 1 and 65535
-- #> myAction "numbers.txt"
-- -- this compute the sum over all numbers and print the result
-- Exercise: try the above!

-- you can create your own file, called "numbers.txt", to test different input
-- then call myAction from inside ghci like this
-- #> myAction "numbers.txt"
-- Exercise: what should be printed?

-----------------------------------------------------------------------------
-- Example: Caesar Cipher
-- for the caesar cipher example, see http://www.macs.hw.ac.uk/~hwloidl/Courses/F21CN/Labs/CryptoI/caesar.hs
-----------------------------------------------------------------------------
-}
-- end of file