{- Quick Overview of Basic Haskell
CSci 450: Org. of Programming Languages
CSci 503: Fund. Concepts in Languages
Instructor: H. Conrad Cunningham, Fall 2018
1234567890123456789012345678901234567890123456789012345678901234567890
2017-08-28: Adapted from the website listed below
2018-08-31: Adapted from previous year
I adapted this document from the web page:
Learn X in Y Minutes, Where X = Haskell
https://learnxinyminutes.com/docs/haskell/
I used this modified document to work interctively using ghci to
discuss the various basic features of Haskell.
- The Monday, 28 Aug 2017, class demonstration used parts 1-3
(more or less).
-}
-- Single line comments start with two dashes.
{- Multiline comments can be enclosed
in a block like this is.
-}
----------------------------------------------------
-- 1. Primitive Datatypes and Operators
----------------------------------------------------
-- Haskell has numbers
18 --> 18
-- Math is what you would expect
1 + 1 --> 2
8 - 1 --> 7
10 * 2 --> 20
35 / 5 --> 7.0 - floating point
-- Division is not integer division by default
35 / 4 --> 8.75
-- integer division and remainder
35 `div` 4 --> 8 - note use of two back-ticks `
35 `rem` 4 --> 3
div 35 8 -- really just two-argument function calls
rem 35 8
-- Haskell function calls do not use parentheses or commas.
-- The general pattern is: func arg1 arg2 arg3 ...
-- Haskell has a variety of number types of different widths.
-- Int type - note capital -- default, fixed-width integer type
-- Default width for machine, but at least 30-bit, probably 32 or 64
-- On a 64-bit Mac below -- 64-bit two's complement
maxBound :: Int --> 9223372036854775807
(2^63-1) :: Int --> 9223372036854775807
(2^63) :: Int --> -9223372036854775808 (overflow!)
-(2^63) :: Int --> -9223372036854775808 (assymetric +/-)
-- Above:
-- ^ denotes exponentiation
-- :: Int declares of Int type
-- maxBound defined for only bounded types
-- Integer type - unbounded integer type
999999999999999999999 --> 999999999999999999999
-- type Bool - Boolean values with two primitives
True -- denotes truth
False -- denotes falsity
-- Boolean operations
not True --> False ("not" is really one-argument function)
not False --> True
1 == 1 --> True
1 /= 1 --> False
1 < 10 --> True
-- type Char -- Unicode characters
'R' -- character 'R' - single quote
"We are Rebels" -- String - note double quotes
'This is a syntactic error' -- error!
-- Strings can be concatenated (appended) using infix ++
"I am a " ++ "Rebel!" --> "I am a Rebel"
-- String is not really a primitive type, really just a list of
-- characters. A list literal uses square brackets around
-- comma-separated list
['R', 'e', 'b', 'e', 'l'] --> "Rebel"
-- We can index into string using !! - first element at 0
"I am a Rebel" !! 0 --> 'I'
----------------------------------------------------
-- 2. Lists
----------------------------------------------------
-- Every element in a list must have the same type.
-- These two lists are equal:
[1, 2, 3, 4, 5] -- list literal
[] -- empty list
[1..5] -- sequence generator (syntactic sugar)
[1,2,3,4,5] == [1..5] --> True
-- Ranges are versatile -- uses any type that is enumerable (Enum),
-- including both integers and characters.
['A'..'F'] --> "ABCDEF"
-- You can create a step in a range.
[0,2..10] --> [0, 2, 4, 6, 8, 10]
[5..1] --> [] (defaults to incrementing)
[5,4..1] --> [5, 4, 3, 2, 1] (can explicitly decrement)
-- indexing into a list
[1..10] !! 3 --> 4 (zero-based indexing)
-- Haskell has infinite lists
[1..] -- a list of all whole numbers (positive integers)
-- Infinite lists possible because Haskell uses "lazy evaluation".
-- Haskell only evaluates expresssions when it must yield a value.
[1..] !! 999 --> 1000
take 5 [1..] --> [1,2,3,4,5]
take 3 (drop 5 [1..]) --> [6,7,8]
-- In first above, Haskell evaluates elements 1-1000 of this list, but
-- the rest of the elements of this "infinite" list do not exist yet!
-- Concatenating (appending) two lists
[1..5] ++ [6..10] --> [1,2,3,4,5,6,7,8,9,10]
-- adding to the head of a list -- "cons" operator
0:[1..5] --> [0, 1, 2, 3, 4, 5]
0:1:2:3:4:5:[] -- brackets just syntactic sugar for many cons
-- cons binds from right
-- More list operations
head [1..5] --> 1
head [] -- error! not defined
tail [1..5] --> [2, 3, 4, 5]
tail [] -- error! not defined
init [1..5] --> [1, 2, 3, 4]
last [1..5] --> 5
----------------------------------------------------
-- 3. Functions A
----------------------------------------------------
-- We can define a simple function that takes two variables
add a b = a + b
-- Note when using ghci (the Haskell interpreter), we might
-- need to use "let" to define the function, as follows:
--
-- let add a b = a + b
-- We can apply (call) the function
add 2 3 --> 5
-- We can use the infix form using backticks:
2 `add` 3 --> 5
----------------------------------------------------
-- 4. Tuples
----------------------------------------------------
-- Tuples are fixed length groups, not necessarily same type.
-- Below is a pair whose first component is a string and second is
-- an integer.
("Archie", 18) :: (String, Int)
-- We can access components of a pair (i.e. a tuple of length 2)
fst ("Archie", 18) --> "Archie"
snd ("Archie", 18) --> 18
----------------------------------------------------
-- 5. If and Case Expressions
----------------------------------------------------
-- if-expressions
haskell = if 1 == 1 then "awesome!" else "awful!" --> "awesome!"
-- if-expressions can be on multiple lines. Indentation is
-- important to show nesting.
haskell = if 1 == 1 then
"awesome!"
else
"awful!"
-- case expressions -- pattern-matching expression
-- Suppose args is a list of strings
case args of
"help" -> giveHelp
"start" -> startProgram
arg -> error ("Bad argument: " ++ arg)
----------------------------------------------------
-- 6. Functions B
----------------------------------------------------
-- We can also define functions that have no letters!
-- E.g., define an integer division operator
(//) a b = a `div` b
35 // 4 --> 8
-- Guards: an easy way to do branching in functions
fib x
| x < 2 = 1
| otherwise = fib (x - 1) + fib (x - 2)
-- Pattern matching is similar. Here we have given three different
-- equations that define fib. Haskell will automatically use the first
-- equation whose left hand side pattern matches the value.
fib 1 = 1
fib 2 = 2
fib x = fib (x - 1) + fib (x - 2)
-- Pattern matching on tuples:
foo (x, y) = (x + 1, y + 2)
----------------------------------------------------
-- 7. Type signatures
----------------------------------------------------
-- Haskell is strictly and strongly typed. Every valid expression
-- has a type.
-- Some basic types:
5 :: Integer
"hello" :: String
True :: Bool
-- Functions have types too.
-- `not` takes a boolean and returns a boolean:
-- not :: Bool -> Bool
-- Here is a two-argument function.
-- add :: Integer -> Integer -> Integer
-- When we define a value, it is good practice to write its type
-- explicitly immediately above the defining equations.
double :: Integer -> Integer
double x = x * 2
NOTE: I HAVE NOT MODIFED FULLY FROM HERE
----------------------------------------------------
-- 8. List Comprehensions
----------------------------------------------------
-- list comprehension using a generator
[x*2 | x <- [1..5]] --> [2, 4, 6, 8, 10]
-- using a generator and a filter
[x*2 | x <- [1..5], x*2 > 4] --> [6, 8, 10]
----------------------------------------------------
-- 9. Functions C
----------------------------------------------------
-- Pattern matching on lists. Here `x` is the first element
-- in the list, and `xs` is the rest of the list. We can write
-- our own map function:
myMap func [] = []
myMap func (x:xs) = func x:(myMap func xs)
-- Anonymous functions are created with a backslash followed by
-- all the arguments.
myMap (\x -> x + 2) [1..5] -- [3, 4, 5, 6, 7]
-- using fold (called `inject` in some languages) with an anonymous
-- function. foldl1 means fold left, and use the first value in the
-- list as the initial value for the accumulator.
foldl1 (\acc x -> acc + x) [1..5] -- 15
----------------------------------------------------
-- 10. Functions D
----------------------------------------------------
-- Partial application: if we pass only part of the arguments to a
-- function, the function is "partially applied". It returns a
-- function that takes the rest of the arguments.
add a b = a + b
foo = add 10 -- foo now function that takes number and adds 10
foo 5 -- 15
-- Another way to write the same thing
foo = (10+) -- called an "operator section"
foo 5 -- 15
-- Function Composition.
-- Below foo is a function that takes a value. It adds 10 to it,
-- multiplies the result of that by 4, and then returns the final
-- value.
foo = (4*) . (10+)
-- 4*(10+5) = 60
foo 5 -- 60
-- Fixing precedence
-- Haskell has an operator called `$`. This operator applies a
-- function to a given parameter. In contrast to standard function
-- application, which has highest possible priority of 10 and is
-- left-associative, the `$` operator has priority of 0 and is
-- right-associative. Such a low priority means that the expression on
-- its right is applied as the parameter to the function on its left.
-- before
even (fib 7) -- false
-- equivalently
even $ fib 7 -- false
-- composing functions
even . fib $ 7 -- false
----------------------------------------------------
-- 11. Repetition
----------------------------------------------------
-- Haskell does not have loops; it uses recursion instead.
-- map applies a function over every element in a list
map (*2) [1..5] -- [2, 4, 6, 8, 10]
-- We can define a "for" function using "map"
for array func = map func array
-- and then use it
for [0..5] $ \i -> show i
-- We could have written that like this too:
for [0..5] show
-- We can use foldl or foldr to reduce a list
-- foldl
foldl (\x y -> 2*x + y) 4 [1,2,3] -- 43
-- This is the same as
(2 * (2 * (2 * 4 + 1) + 2) + 3)
-- foldl is left-handed, foldr is right-handed
foldr (\x y -> 2*x + y) 4 [1,2,3] -- 16
-- This is now the same as
(2 * 1 + (2 * 2 + (2 * 3 + 4)))
----------------------------------------------------
-- 12. Algebraic Data Types
----------------------------------------------------
-- Here's how we define our own data type in Haskell
data Color = Red | Blue | Green
-- Now we can use it in a function with pattern matching
say :: Color -> String
say Red = "You are Red!"
say Blue = "You are Blue!"
say Green = "You are Green!"
-- Our data types can also have parameters
data Maybe a = Nothing | Just a
-- These are all of type Maybe
Just "hello" -- of type `Maybe String`
Just 1 -- of type `Maybe Int`
Nothing -- of type `Maybe a` for any `a`
----------------------------------------------------
-- 13. Haskell IO
----------------------------------------------------
-- While IO can't be explained fully without explaining monads,
-- it is not hard to explain enough to get going.
-- When a Haskell program is executed, `main` is called. It must
-- return a value of type `IO a` for some type `a`. For example:
main :: IO ()
main = putStrLn $ "Hello, sky! " ++ (say Blue)
-- putStrLn has type String -> IO ()
-- It is easiest to do IO if you can implement your program as
-- a function from String to String. The function
-- interact :: (String -> String) -> IO ()
-- inputs some text, runs a function on it, and prints out the
-- output.
countLines :: String -> String
countLines = show . length . lines
main' = interact countLines
-- We can think of a value of type `IO ()` as representing a
-- sequence of actions for the computer to do, much like a
-- computer program written in an imperative language. We can use
-- the `do` notation to chain actions together. For example:
sayHello :: IO ()
sayHello = do
putStrLn "What is your name?"
name <- getLine -- this gets a line and gives it the name "name"
putStrLn $ "Hello, " ++ name
-- Exercise: write your own version of `interact` that only reads
-- one line of input.
-- The code in `sayHello` will never be executed, however. The only
-- action that ever gets executed is the value of `main`.
-- To run `sayHello` comment out the above definition of `main`
-- and replace it with:
-- main = sayHello
-- Let's understand better how the function `getLine` we just
-- used works. Its type is:
-- getLine :: IO String
-- You can think of a value of type `IO a` as representing a
-- computer program that will generate a value of type `a`
-- when executed (in addition to anything else it does). We can
-- name and reuse this value using `<-`. We can also
-- make our own action of type `IO String`:
action :: IO String
action = do
putStrLn "This is a line. Duh"
input1 <- getLine
input2 <- getLine
-- The type of the `do` statement is that of its last line.
-- `return` is not a keyword, but merely a function
return (input1 ++ "\n" ++ input2) -- return :: String -> IO String
-- We can use this just like we used `getLine`:
main'' = do
putStrLn "I will echo two lines!"
result <- action
putStrLn result
putStrLn "This was all, folks!"
-- The type `IO` is an example of a "monad". The way Haskell uses a monad to
-- do IO allows it to be a purely functional language. Any function that
-- interacts with the outside world (i.e. does IO) gets marked as `IO` in its
-- type signature. This lets us reason about which functions are "pure" (don't
-- interact with the outside world or modify state) and which functions aren't.
-- This is a powerful feature, because it's easy to run pure functions
-- concurrently; so, concurrency in Haskell is very easy.
----------------------------------------------------
-- 14. The Haskell REPL
----------------------------------------------------
-- Start the repl by typing `ghci`.
-- Now you can type in Haskell code. Any new values
-- need to be created with `let`:
let foo = 5
-- We can inspect the type of any value or expression with `:type`,
-- which we can abbreviate as `:t`
> :t foo
foo :: Integer
-- Operators, such as `+`, `:` and `$`, are functions.
-- We can inspect their types by putting the operator in parentheses:
> :t (:)
(:) :: a -> [a] -> [a]
-- We can get additional information on any `name` using `:info`,
-- which we can abbreviate as `:i`.
> :i (+)
class Num a where
(+) :: a -> a -> a
...
-- Defined in ‘GHC.Num’
infixl 6 +
-- You can also run any action of type `IO ()`
> sayHello
What is your name?
Friend!
Hello, Friend!
qsort [] = []
qsort (p:xs) = qsort lesser ++ [p] ++ qsort greater
where lesser = filter (< p) xs
greater = filter (>= p) xs