The program in this text file implements “Hello world” with infix
notation:
Haskell/HelloInfix.hs
#!/usr/bin/runghc
helloThing :: String -> String
helloThing x = "Hello " ++ x
main :: IO ()
main = putStrLn (helloThing "World!")
This program illustrates prefix notation:
Haskell/HelloPrefix.hs
#!/usr/bin/runghc
helloThing :: String -> String
helloThing x = (++) "Hello " x
main :: IO ()
main = putStrLn (helloThing "World!")
This program uses infix notation,
and hints at partial application capabaliities:
Haskell/HelloInfixPartial.hs
#!/usr/bin/runghc
helloThing :: String -> String
helloThing = ("Hello " ++)
main :: IO ()
main = putStrLn (helloThing "World!")
This program uses prefix notation,
and hints at partial application capabaliities:
Haskell/HelloPrefixPartial.hs
#!/usr/bin/runghc
helloThing :: String -> String
helloThing = (++) "Hello "
main :: IO ()
main = putStrLn (helloThing "World!")
The above files are all equivalent programs.
++++++++++++++++++++
Discussion question:
Which of the above do you find most intuitive?
Which seems most consistent with the rules of function application
broadly?
Haskell programs can reference code from other files.
The two files below illustrate HelloModule exporting a function,
and main importing that function for use:
This is a pure function:
Haskell/HelloModule.hs
Compiling it will produce a binary object,
but not an excecutable .out
file.
Every time it is run with the same input,
the output must be the same.
It modifies no external data structures.
It is thus pure, like a mathematical function.
https://en.wikipedia.org/wiki/Pure_function
It has referential transparency:
https://en.wikipedia.org/wiki/Referential_transparency
Thus, you can use algebraic reseasoning with it.
This is a main driver with Input/Output (IO) side effects:
Haskell/main.hs
Compiling this will produce an executable out file
(main.out
),
which is linked to the above function.
Non-pure code may read from disk, keyboard,
/dev/urandom
, network card,
or write to the screen, disk, network card, etc.
Thus running a non-pure function with the same arguments may produce
different results each time.
In Haskell, almost all of your code should be written in pure
functions.
Thus, almost all data structures are immutable,
with functions operating on memory-optimized copies of data objects.
The minimal non-pure IO code should be relegated to the small shell of a main driver.
This practice of minimizing and isolating impure code has many benefits!
+++++++++++++++++++++
Cahoot_Haskell-purity
It is pre-installed in the class container (see the general syllabus)!
If you are on Linux, use the system repositories:
sudo dnf install ghc*
sudo apt insstall ghc*
Otherwise, if you want to install on your host machine:
https://www.haskell.org/downloads/
At your shell command line, to compile and run:
You can implicitly compile, and run directly:
As in the above Hello World example code,
if you have a shebang in your main driver:
+++++++++++++++++++++++
Cahoot_Haskell-compiler
To run interactively:
$ ghci
λ> :load HelloInfix.hs
λ> helloThing "Haskell"
λ> main
:load can be abbreviated :l
$
indicates a shell command.
prelude>
, ghci>
, *main>
,
λ>
or similar, indicate ghci
interaction.
Haskell is statically typed, with a convenient type-inference.
It checks the type consistency of your program at compile time.
Statically typed languages automatically eliminate an entire class of
errors.
Haskell can infer types in many cases, so you don’t always have to
write them.
However, it is a good idea to do so on all function definitions.
++++++++++++++++++++
Discussion question:
What are benefits of dynamically typed languages, like Python, Clojure,
etc.?
How are they easier that statically typed languages you have studied,
like C++?
How might we acheive the same benefits in a statically typed
language?
#!/usr/bin/runghc
-- This is a shebang: https://en.wikipedia.org/wiki/Shebang_(Unix)
-- If you make a file executable: `chmod +x main.hs`
-- and at the shell, run it: `./main.hs`,
-- then the OS will search for the program at the top on the path,
-- and run the code with it.
-- It is not required if you compile using ghc explicitly,
-- or for any non-main files (pure functions only).
-- This is a module export:
module Summary where
-- Single line comments start with two dashes.
{- Multiline comments can be enclosed
in a block like this.
-}
-- This file is not expected to compile.
-- It is expected that you type these commands in `ghci`.
----------------------------------------------------
-- 1. Primitive Datatypes and Operators
----------------------------------------------------
-- You have numbers
3 -- 3
-- They come in several types:
3 :: Int
3 :: Integer
-- Integers can handle arbitrary size.
-- Floats and Doubles
3.7 :: Float
3.7 :: Double
-- Doubles are higher precision
-- Math is what you would expect
1 + 1 -- 2
8 - 1 -- 7
10 * 2 -- 20
35 / 5 -- 7.0
-- Division is not integer division by default
35 / 4 -- 8.75
-- integer division, infix
35 `div` 4 -- 8
-- integer division, prefix
div 35 4 -- 8
-- Remainder
mod 35 4 -- 3
-- And real fractional rational numbers
import Data.Ratio
8 % 4 -- 2 % 1
-- Boolean values are primitives
True
False
-- Boolean operations
not True -- False
not False -- True
True && False -- False
True || False -- True
1 == 1 -- True
1 /= 1 -- False
1 < 10 -- True
-- In the above examples, `not` is a function that takes one value.
-- Haskell doesn't need parentheses for function calls...all the arguments
-- are just listed after the function. So the general pattern is:
-- func arg1 arg2 arg3...
-- See the section on functions for information on how to write your own.
-- Strings and characters
"This is a string."
'a' -- character
'b' :: Char -- character
'You cant use single quotes for strings.' -- error!
-- Strings can be concatenated
"Hello " ++ "world!" -- "Hello world!"
"Hello " <> "world!" -- "Hello world!"
-- A string is a list of characters
['H', 'e', 'l', 'l', 'o'] -- "Hello"
-- Lists can be indexed with the `!!` operator followed by an index
-- but this is an O(n) operation because lists are linked lists
"This is a string" !! 0 -- 'T'
----------------------------------------------------
-- 2. Lists and Tuples
----------------------------------------------------
-- Every element in a list must have the same type.
-- These two lists are equal:
[1, 2, 3, 4, 5]
[1..5]
-- Ranges are versatile.
['A'..'F'] -- "ABCDEF"
-- You can create a step in a range.
[0,2..10] -- [0, 2, 4, 6, 8, 10]
[5..1] -- [] (Haskell defaults to incrementing)
[5,4..1] -- [5, 4, 3, 2, 1]
-- indexing into a list
[1..10] !! 3 -- 4 (zero-based indexing)
-- You can also have infinite lists in Haskell!
[1..] -- a list of all the natural numbers
-- Infinite lists work because Haskell has "lazy evaluation". This means
-- that Haskell only evaluates things when it needs to. So you can ask for
-- the 1000th element of your list and Haskell will give it to you:
[1..] !! 999 -- 1000
-- And now Haskell has evaluated elements 1 - 1000 of this list...but the
-- rest of the elements of this "infinite" list don't exist yet! Haskell won't
-- actually evaluate them until it needs to.
-- joining two lists
[1..5] ++ [6..10]
-- adding to the head of a list
0:[1..5] -- [0, 1, 2, 3, 4, 5]
-- more list operations
head [1..5] -- 1
tail [1..5] -- [2, 3, 4, 5]
init [1..5] -- [1, 2, 3, 4]
last [1..5] -- 5
-- https://en.wikipedia.org/wiki/Set-builder_notation#In_programming_languages
-- https://en.wikipedia.org/wiki/List_comprehension
-- list comprehensions
[x*2 | x <- [1..5]] -- [2, 4, 6, 8, 10]
-- with a conditional
[x*2 | x <- [1..5], x*2 > 4] -- [6, 8, 10]
-- Every element in a tuple can be a different type, but a tuple has a
-- fixed length.
-- A tuple:
("haskell", 1)
-- accessing elements of a pair (i.e. a tuple of length 2)
fst ("haskell", 1) -- "haskell"
snd ("haskell", 1) -- 1
-- pair element accessing does not work on n-tuples (i.e. triple, quadruple, etc)
snd ("snd", "can't touch this", "da na na na") -- error! see function below
----------------------------------------------------
-- 3. Functions
----------------------------------------------------
-- A simple function that takes two variables
add a b = a + b
-- Note that if you are using ghci (the Haskell interpreter)
-- You'll need to use `let`, i.e.
-- let add a b = a + b
-- Using the function
add 1 2 -- 3
-- You can also put the function name between the two arguments
-- with backticks:
1 `add` 2 -- 3
-- You can also define functions that have no letters! This lets
-- you define your own operators! Here's an operator that does
-- integer division
(//) 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
sndOfTriple (_, y, _) = y -- use a wild card (_) to bypass naming unused value
-- 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
----------------------------------------------------
-- 4. More functions
----------------------------------------------------
-- partial application: if you don't pass in all the arguments to a function,
-- it gets "partially applied". That means it returns a function that takes the
-- rest of the arguments.
add a b = a + b
foo = add 10 -- foo is now a function that takes a number and adds 10 to it
foo 5 -- 15
-- Another way to write the same thing
foo = (10+)
foo 5 -- 15
-- function composition
-- the operator `.` chains functions together.
-- For example, here 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 a parameter to the function on its left.
-- before
even (fib 7) -- false
-- equivalently
even $ fib 7 -- false
-- composing functions
even . fib $ 7 -- false
----------------------------------------------------
-- 5. Type signatures
----------------------------------------------------
-- Haskell has a very strong type system, and 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's a function that takes two arguments:
-- add :: Integer -> Integer -> Integer
-- When you define a value, it's good practice to write its type above it:
double :: Integer -> Integer
double x = x * 2
----------------------------------------------------
-- 6. Control Flow and If Expressions
----------------------------------------------------
-- if-expressions
haskell = if 1 == 1 then "awesome" else "awful" -- haskell = "awesome"
-- if-expressions can be on multiple lines too, indentation is important
haskell =
if 1 == 1
then "awesome"
else "awful"
-- case expressions: Here's how you could parse command line arguments
case args of
"help" -> printHelp
"start" -> startProgram
_ -> putStrLn "bad args"
-- Haskell doesn't 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]
-- you can make a for function using map
for list func = map func list
-- and then use it
for [0..5] $ \i -> show i
-- we could've written that like this too:
for [0..5] show
-- filter keeps only the elements in a list that satisfy a condition
filter even [1..10] -- [2, 4, 8, 10]
-- You can use foldl or foldr to reduce a list
-- foldl <fn> <initial value> <list>
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)))
----------------------------------------------------
-- 7. Data Types
----------------------------------------------------
-- A data type is declared with a 'type constructor' on the left
-- and one or more 'data constructors' on the right, separated by
-- the pipe | symbol. This is a sum/union type. Each data constructor
-- is a (possibly nullary) function that creates an object of the type
-- named by the type constructor.
-- This is essentially an enum
data Color = Red | Blue | Green
-- Now you can use it in a function:
say :: Color -> String
say Red = "You are Red!"
say Blue = "You are Blue!"
say Green = "You are Green!"
-- Note that the type constructor is used in the type signature
-- and the data constructors are used in the body of the function
-- Data constructors are primarily pattern-matched against
-- This next one is a traditional container type holding two fields
-- In a type declaration, data constructors take types as parameters
-- Data constructors can have the same name as type constructors
-- This is common where the type only has a single data constructor
data Point = Point Float Float
-- This can be used in a function like:
distance :: Point -> Point -> Float
distance (Point x y) (Point x' y') = sqrt $ dx + dy
where
dx = (x - x') ** 2
dy = (y - y') ** 2
-- You may find let-in to be more intuitive than where:
distance2 :: Point -> Point -> Float
distance2 (Point x y) (Point x' y') =
let dx = (x - x') ** 2
dy = (y - y') ** 2
in sqrt $ dx + dy
-- Types can have multiple data constructors with arguments, too
data Name
= Mononym String
| FirstLastName String String
| FullName String String String
-- To make things clearer we can use record syntax
data Point2D
= CartesianPoint2D {x :: Float, y :: Float}
| PolarPoint2D {r :: Float, theta :: Float}
myPoint = CartesianPoint2D { x = 7.0, y = 10.0 }
-- Using record syntax automatically creates accessor functions
-- (the name of the field)
xOfMyPoint = x myPoint
-- xOfMyPoint is equal to 7.0
-- Record syntax also allows a simple form of update
myPoint' = myPoint { x = 9.0 }
-- myPoint' is CartesianPoint2D { x = 9.0, y = 10.0 }
-- Even if a type is defined with record syntax, it can be declared like
-- a simple data constructor. This is fine:
myPoint'2 = CartesianPoint2D 3.3 4.0
-- It's also useful to pattern match data constructors in `case` expressions
distanceFromOrigin x =
case x of
(CartesianPoint2D x y) -> sqrt $ x ** 2 + y ** 2
(PolarPoint2D r _) -> r
-- Your data types can have type parameters too:
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`
-- For convenience we can also create type synonyms with the 'type' keyword
type String = [Char]
-- Unlike `data` types, type synonyms need no constructor, and can be used
-- anywhere a synonymous data type could be used. Say we have the
-- following type synonyms and items with the following type signatures
type Weight = Float
type Height = Float
type Point = (Float, Float)
getMyHeightAndWeight :: Person -> (Height, Weight)
findCenter :: Circle -> Point
somePerson :: Person
someCircle :: Circle
distance :: Point -> Point -> Float
-- The following would compile and run without issue,
-- even though it does not make sense semantically,
-- because the type synonyms reduce to the same base types
distance (getMyHeightAndWeight somePerson) (findCenter someCircle)
----------------------------------------------------
-- 8. Typeclasses
----------------------------------------------------
-- Typeclasses are one way Haskell does polymorphism
-- They are similar to interfaces in other languages
-- A typeclass defines a set of functions that must
-- work on any type that is in that typeclass.
-- The Eq typeclass is for types whose instances can
-- be tested for equality with one another.
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
x == y = not (x /= y)
x /= y = not (x == y)
-- This defines a typeclass that requires two functions, (==) and (/=)
-- It also declares that one function can be declared in terms of another
-- So it is enough that *either* the (==) function or the (/=) is defined
-- And the other will be 'filled in' based on the typeclass definition
-- To make a type a member of a type class, the instance keyword is used
instance Eq TrafficLight where
Red == Red = True
Green == Green = True
Yellow == Yellow = True
_ == _ = False
-- Now we can use (==) and (/=) with TrafficLight objects
canProceedThrough :: TrafficLight -> Bool
canProceedThrough t = t /= Red
-- You can NOT create an instance definition for a type synonym
-- Functions can be written to take typeclasses with type parameters,
-- rather than types, assuming that the function only relies on
-- features of the typeclass
isEqual :: (Eq a) => a -> a -> Bool
isEqual x y = x == y
-- Note that x and y MUST be the same type, as they are both defined
-- as being of type parameter 'a'.
-- A typeclass does not state that different types in the typeclass can
-- be mixed together.
-- So `isEqual Red 2` is invalid, even though 2 is an Int which is an
-- instance of Eq, and Red is a TrafficLight which is also an instance of Eq
-- Other common typeclasses are:
-- Ord for types that can be ordered, allowing you to use >, <=, etc.
-- Read for types that can be created from a string representation
-- Show for types that can be converted to a string for display
-- Num, Real, Integral, Fractional for types that can do math
-- Enum for types that can be stepped through
-- Bounded for types with a maximum and minimum
-- Haskell can automatically make types part of Eq, Ord, Read, Show, Enum,
-- and Bounded with the `deriving` keyword at the end of the type declaration
data Point = Point Float Float deriving (Eq, Read, Show)
-- In this case it is NOT necessary to create an 'instance' definition
----------------------------------------------------
-- 9. 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
-- You 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.
----------------------------------------------------
-- 10. 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
-- You can see the type of any value or expression with `:t`:
> :t foo
foo :: Integer
-- Operators, such as `+`, `:` and `$`, are functions.
-- Their type can be inspected by putting the operator in parentheses:
> :t (:)
(:) :: a -> [a] -> [a]
-- You can get additional information on any `name` using `: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
There are a number of ways to accomplish the same thing:
Haskell/Conditions.hs
-- Below, are four different ways to implement this infinite corecursive sequence:
-- https://en.wikipedia.org/wiki/Fibonacci_sequence
-- | if then else
-- >>> fibIf 10
-- 89
fibIf :: Int -> Int
fibIf x =
if x == 1
then 1
else
if x == 2
then 2
else fibIf (x - 1) + fibIf (x - 2)
-- | Guards
-- >>> fibGuard 10
-- 89
fibGuard :: Int -> Int
fibGuard x
| x == 1 = 1
| x == 2 = 2
| otherwise = fibGuard (x - 1) + fibGuard (x - 2)
-- | Actual case
-- >>> fibCase 10
-- 89
fibCase :: Int -> Int
fibCase x = case x of
1 -> 1
2 -> 2
x -> fibCase (x - 1) + fibCase (x - 2)
-- | Syntantic sugar for case
-- >>> fibOverload 10
-- 89
fibOverload :: Int -> Int
fibOverload 1 = 1
fibOverload 2 = 2
fibOverload x = fibOverload (x - 1) + fibOverload (x - 2)
+++++++++++++++++++++++++
Cahoot_Haskell-repetition
Pattern matching is known as “destructuring” in other languages, like
Clojure;
this is perhaps a more narrowly descriptive term than pattern
matching.
Haskell/PatternMatching.hs
-- https://en.wikibooks.org/wiki/Haskell/Pattern_matching
-- https://haskyll.github.io/decide_pattern/
-- https://www.haskell.org/tutorial/patterns.html
x = [1]
y = [1, 2]
z = [1, 2, 3]
-- |
-- >>> f x -- fails
-- *** Exception: pattternMatching.hs:23:1-17: Non-exhaustive patterns in function f
-- |
-- >>> f y
-- []
-- |
-- >>> f z
-- [3]
f :: [a] -> [a]
f (x : y : z) = z
-- This is the same.
fcase :: [a] -> [a]
fcase xs = case xs of (x : y : z) -> z
-- |
-- >>> f2 x
-- []
-- |
-- >>> f2 y
-- [2]
-- |
-- >>> f2 z
-- [2,3]
f2 :: [a] -> [a]
f2 (x : yz) = yz
-- This is the same
f2case :: [a] -> [a]
f2case xs = case xs of (x : yz) -> yz
-- |
-- >>> f3 x
-- 1
-- |
-- >>> f3 y
-- 1
-- |
-- >>> f3 z
-- 1
f3 :: [a] -> a
f3 (x : yz) = x
-- This is the same
f3case :: [a] -> a
f3case xs = case xs of (x : yz) -> x
-- | With tuples
-- >>> myFst myTuple
-- 2
myTuple :: (Int, Int)
myTuple = (2, 3)
myFst :: (Int, Int) -> Int
myFst (x, _) = x
-- | With specific elements of lists
-- >>> lstFst myList
-- 2
myList :: [Int]
myList = [2, 3]
lstFst :: [Int] -> Int
lstFst [x, _] = x
-- | Pattern matching on custom data: Dat Year, Month, Day
-- >>> showDate dateItem
-- "2025-1-22"
data Date = Date Int Int Int
dateItem = Date 2025 01 22
showDate :: Date -> String
showDate (Date y m d) = show y ++ "-" ++ show m ++ "-" ++ show d
-- | In let expressions
-- >>> ylet
-- 7
ylet =
let (x : _) = map (* 2) [1, 2, 3]
in x + 5
-- | With where
-- >>> ywhere
-- 7
ywhere = x + 5
where
(x : _) = map (* 2) [1, 2, 3]
-- Do clauses
putFirstChar = do
(c : _) <- getLine
putStrLn [c]
-- | list comprehensions
-- >>> listComp
-- [4,7,6,8,11,4]
xs = [(1, 3), (4, 3), (2, 4), (5, 3), (5, 6), (3, 1)]
listComp = [a + b | (a, b) <- xs]
-- This is an algebraic data type:
data Pair = Pint Int Int | Pfloat Float Float
deriving Show
-- |
-- >>> x
-- Pint 6 7
x = Pint 6 7
-- |
-- >>> y
-- Pfloat 6.0 7.0
y = Pfloat 6.0 7.0
-- |
-- >>> firstOfPair x
-- 6
firstOfPair :: Pair -> Int
firstOfPair (Pint x y) = x
-- |
-- >>> secondOfPair y
-- 7.0
secondOfPair :: Pair -> Float
secondOfPair p = case p of
(Pfloat x y) -> y
-- This is how the actual True and False are implemented:
-- data Bool = True | False
data MyBool = MyTrue | MyFalse
deriving Show
-- This is how the actual Maybe and nothing is implemented:
-- data Maybe a = Nothing | Just a
data MyMaybe a = MyNothing | MyJust a
deriving Show
-- Destructure (unwrap Just), built in: fromJust
myFromJust :: MyMaybe a -> a
myFromJust (MyJust a) = a
-- |
-- >>> myFromJust justThing
-- 5
justThing = MyJust 5
-- This is how the (:) list operator works:
data MyList a = Empty | MyCons a (MyList a)
deriving (Show)
emptyList = Empty
oneBigList = MyCons 5 Empty
threeBigList = MyCons 3 (MyCons 4 (MyCons 5 Empty))
-- |
-- >>> listSugar == listNoSugar
-- True
listEmpty = []
listSugar = [1,2,3]
listNoSugar = 1 : (2 : (3 : []))
If you want to learn about built-in functions,
or search for third-party packages:
https://hoogle.haskell.org
To see type information about a binding,
where <thing>
is any binding:
$ ghci
λ> :type <thing> -- general form
λ> :type reverse -- a prefix function
λ> :type (==) -- for infix bindings
:type can be abbreviated :t
To see type information about a binding,
where <thing>
is any binding:
$ ghci
λ> :info <thing> -- general form
λ> :info reverse -- a prefix function
λ> :info (==) -- for infix bindings
:info can be abbreviated :i
Some versions of ghc
let you look up documentation for
some functions,
where <thing>
is any binding:
$ ghci
λ> :doc <thing> -- general form
λ> :doc reverse -- a prefix function
λ> :doc (==) -- for infix bindings
To auto-format your code for syle:
$ ormolu -i *.hs
Let’s learn to step on bugs… in code.
ghci
:stepStepping and time-travel debuggers are great for languages with
mutable state.
They are helpful because they releive your working memory from keeping
track of state,
and from needing to keep track of the sequential path of execution
through your code.
For purely functional languages don’t mutate state, and thus have less
such need.
That being said, ghci
has a stepping debugger.
To start, create a config file for nicer debug and prompt settings:
~/.ghci
$ echo ':set stop :list' >~/.ghci
$ echo ':set prompt "\ESC[1;34m%s \ESC[0;35mλ>\ESC[m "' >>~/.ghci
You only need to do that part once.
Here is some code to debug:
Haskell/DeBug.hs
#!/usr/bin/runghc
module DeBug where
deBug = [" \\ \\ \\ ", " \\ \\ \\ ", " / / / ", " / / / ", " DDDDDDDDDDDDD ", " D::::::::::::DDD o ", " D:::::::::::::::DD / ", " DDD:::::DDDDD:::::D / ", " D:::::D D:::::D / ", " D:::::D D:::::D ", " D:::::D D:::::D--> ", " D:::::D D:::::D ", " D:::::D D:::::D--> ", " D:::::D D:::::D ", " D:::::D D:::::D ", " D:::::D D:::::D \\ ", " DDD:::::DDDDD:::::D \\ ", " D:::::::::::::::DD \\ ", " D::::::::::::DDD o ", " DDDDDDDDDDDDD ", " \\ \\ \\ ", " \\ \\ \\ ", " / / / ", " / / / "]
-- The below two structurally recursive functions are the same:
deBugFunc :: [String] -> String
deBugFunc xs = case xs of
[] -> ""
(x : xs) -> x ++ "\n" ++ deBugFunc xs
deBugFunc2 :: [String] -> String
deBugFunc2 [] = ""
deBugFunc2 (x : xs) = x ++ "\n" ++ deBugFunc2 xs
main :: IO ()
main = do
putStrLn (deBugFunc deBug)
putStrLn (deBugFunc2 deBug)
You can use the debugger as follows:
$ ghci
λ> :load DeBug
λ> :step deBugFunc deBug
λ> :step -- repeat this command
λ> :reload DeBug
λ> :step deBugFunc2 deBug
λ> :step -- repeat this command
:reload can be abbreviated :r
+++++++++++++++++
Cahoot_Haskell-interactive
You can also include print-like debugging statements:
Haskell/DeBug.hs
#!/usr/bin/runghc
module DeBug where
import Debug.Trace (trace)
deBug = [" \\ \\ \\ ", " \\ \\ \\ ", " / / / ", " / / / ", " DDDDDDDDDDDDD ", " D::::::::::::DDD o ", " D:::::::::::::::DD / ", " DDD:::::DDDDD:::::D / ", " D:::::D D:::::D / ", " D:::::D D:::::D ", " D:::::D D:::::D--> ", " D:::::D D:::::D ", " D:::::D D:::::D--> ", " D:::::D D:::::D ", " D:::::D D:::::D ", " D:::::D D:::::D \\ ", " DDD:::::DDDDD:::::D \\ ", " D:::::::::::::::DD \\ ", " D::::::::::::DDD o ", " DDDDDDDDDDDDD ", " \\ \\ \\ ", " \\ \\ \\ ", " / / / ", " / / / "]
-- The below two structurally recursive functions are the same:
deBugFunc :: [String] -> String
deBugFunc xs = case xs of
[] -> ""
(x : xs) -> trace ("\nx is:" ++ show x ++ "\nxs is:" ++ show xs) $ x ++ "\n" ++ deBugFunc xs
deBugFunc2 :: [String] -> String
deBugFunc2 [] = ""
deBugFunc2 (x : xs) = trace ("\nx is:" ++ show x ++ "\nxs is:" ++ show xs) $ x ++ "\n" ++ deBugFunc2 xs
main :: IO ()
main = do
putStrLn (deBugFunc deBug)
putStrLn (deBugFunc2 deBug)
With a functional language, trace-printing is even more useful than in imperative languages.
https://en.wikipedia.org/wiki/Lint_(software)
https://en.wikipedia.org/wiki/Static_program_analysis
If you can automate something you do manually, you probably should!
Ideally, I would read everyone’s homework code,
and give thoughtful stylistic feedback on it.
Given large numbers, that is not feasible,
so automating stylistic analysis is a good option.
In many ways, it is actually more reliable than a human code
review,
though doing both is clearly ideal, albeit impractical.
To get the basic parts of the assignments in this class done,
and to get the the bulk of your points,
I do not require you to address linting hints.
But, I do make about 5% of your grade addressing those linting hints and
warnings,
by requiring them as smaller part of numerous tests.
You can get an A without addressing linting, but in order to get a
100,
you will need to address subtler, finer, points of excellence.
I turn on all warnings during compilation of your main tests,
without stopping compilation:
$ ghc -Wall *.hs
Unlike gcc
/ g++
, ghc
actually
has helpful error messages.
Read them, and think about them!!
You can turn on the same warnings,
by putting this “pragma” near the top of your source code files:
{-# OPTIONS_GHC -Wall #-}
This is an instruction to the compiler, in the source code
itself,
rather than passing command line arguments at compile time.
For unit-testing purposes, you can actually halt compilation with
warnings,
by adding the -Werror
flag also.
$ ghc -Wall -Werror *.hs
The same would work in the pragma above.
HLint is another good linter I use to check style:
https://github.com/ndmitchell/hlint
$ hlint *.hs
Stan can suggest more subtle and nuanced aspects of style and
efficiency:
https://github.com/kowainik/stan
$ ghc -fwrite-ide-info *.hs
$ stan --hiedir ./
This is an example of a Doctest.
Doctests are executable comments.
Haskell/HelloDoctest.hs
module HelloModule where
-- | This is a Doctest comment.
-- >>> helloThing "Doctest."
-- "Hello Doctest."
-- | This test fails. It had an extra space.
-- >>> helloThing "Doctest."
-- "Hello Doctest."
helloThing :: String -> String
helloThing = (++) "Hello "
Doctests serve multiple purposes:
concise documentation, insuring comments are correct, testing code
itself.
Discrete Mathematics using a Comptuter (O’Donnell, Hall, Page) Chapter 1
The Haskell Road to Logic, Math, and Programming (Doets, van Eijck)
Chapter 1
Code from chapter:
Code-HRLMP/HL01GS.hs
module HL01GS where
-- | 2 divides into 4
-- >>> divides 2 4
-- True
-- | 3 does not divide into 4
-- >>> divides 3 4
-- False
divides :: Integer -> Integer -> Bool
divides d n = rem n d == 0
-- | The least divisor of 12 is 2
-- >>> ld 12
-- 2
-- | The least divisor of 15 is 3
-- >>> ld 15
-- 3
ld :: Integer -> Integer
ld n = ldf 2 n
-- | The least divisor, from 3 up, of 64 is 4
-- >>> ldf 3 64
-- 4
ldf :: Integer -> Integer -> Integer
ldf k n
| divides k n = k
| n < k ^ 2 = n
| otherwise = ldf (k + 1) n
-- | 7 is prime
-- >>> prime0 7
-- True
-- | 6 is not prime
-- >>> prime0 6
-- False
prime0 :: Integer -> Bool
prime0 n
| n < 1 = error "not a positive integer"
| n == 1 = False
| otherwise = ld n == n
-- | The minimum of [5, 3, 1, 6] is 1
-- >>> mnmInt [5, 3, 1, 6]
-- 1
mnmInt :: [Int] -> Int
mnmInt [] = error "empty list"
mnmInt [x] = x
mnmInt (x : xs) = min x (mnmInt xs)
-- min is provided in Prelude, min' is home-made.
-- | The minimum of 7 and 5 is 5
-- >>> min 7 5
-- 5
min' :: Int -> Int -> Int
min' x y
| x <= y = x
| otherwise = y
-- | The average of [5, 3, 1, 6] is 1
-- >>> average [5, 3, 1, 6]
-- 15 % 4
average :: [Int] -> Rational
average [] = error "empty list"
average xs = toRational (sum xs) / toRational (length xs)
-- | The sum of [5, 3, 1, 6] is 15
-- >>> sum' [5, 3, 1, 6]
-- 15
sum' :: [Int] -> Int
sum' [] = 0
sum' (x : xs) = x + sum' xs
-- | The length of [5, 3, 1, 6] is 4
-- >>> length [5, 3, 1, 6]
-- 4
length' :: [a] -> Int
length' [] = 0
length' (x : xs) = 1 + length' xs
-- | Pre is a prefix of Prefix
-- >>> prefix "Pre" "Prefix"
-- True
-- | fix is not a prefix of Prefix
-- >>> prefix "fix" "Prefix"
-- False
prefix :: String -> String -> Bool
prefix [] ys = True
prefix (x : xs) [] = False
prefix (x : xs) (y : ys) = (x == y) && prefix xs ys
-- | The prime factors of 66 are [2,3,11]
-- >>> factors 66
-- [2,3,11]
factors :: Integer -> [Integer]
factors n
| n < 1 = error "argument not positive"
| n == 1 = []
| otherwise = p : factors (div n p)
where
p = ld n
-- | The prime factors of 66 are [2,3,11]
-- >>> factors' 66
-- [2,3,11]
-- This is just a version using let instead of where.
factors' :: Integer -> [Integer]
factors' n
| n < 1 = error "argument not positive"
| n == 1 = []
| otherwise =
let p = ld n
in p : factors' (div n p)
-- | This is an infinite generator of primes, so only take 5
-- >>> take 5 primes0
-- [2,3,5,7,11]
primes0 :: [Integer]
primes0 = filter prime0 [2 ..]
-- | This is an improved least divisor function, only checking primes.
-- >>> ldp 15
-- 3
ldp :: Integer -> Integer
ldp n = ldpf primes1 n
-- | This is similar to ldf above, but faster.
-- >>> ldpf [2,3,5,7] 64
-- 2
ldpf :: [Integer] -> Integer -> Integer
ldpf (p : ps) n
| rem n p == 0 = p
| n < p ^ 2 = n
| otherwise = ldpf ps n
-- | This is a faster version of primes0.
-- >>> take 5 primes0
-- [2,3,5,7,11]
primes1 :: [Integer]
primes1 = 2 : filter prime [3 ..]
-- | This is a faster version of prime0
-- >>> prime 7
-- True
prime :: Integer -> Bool
prime n
| n < 1 = error "not a positive integer"
| n == 1 = False
| otherwise = ldp n == n
a = 3
b = 4
-- | f is a function that takes the sum of squares of x and y
-- >>> f 4 5
-- 41
f :: Integer -> Integer -> Integer
f x y = x ^ 2 + y ^ 2
-- | g of 0 is 1
-- >>> g 0
-- 1
-- | g of anything else is a function of x
-- >>> g 5
-- 32
g :: Integer -> Integer
g 0 = 1
g x = 2 * g (x - 1)
-- | h1 of 0 is 0
-- >>> h1 0
-- 0
-- h1 of anything els has a problem. Ask class what it is.
h1 :: Integer -> Integer
h1 0 = 0
h1 x = 2 * h1 x
-- | h2 of 0 is 0
-- >>> h2 0
-- 0
-- h2 of anything else has a problem too.
h2 :: Integer -> Integer
h2 0 = 0
h2 x = h2 (x + 1)
Solution code from chapter questions:
Code-HRLMP/Sol01.hs
module Sol01 where
import HL01GS
-- | 1.9 Define a function that gives the maximum of a list of integers. Use the predefined function max.
-- >>> mxmInt [1, 44, 56, 2, 3]
-- 56
mxmInt :: [Int] -> Int
mxmInt [] = error "empty list"
mxmInt [x] = x
mxmInt (x : xs) = max x (mxmInt xs)
-- | 1.10 Define a function removeFst that removes the first occurrence of an integer m from a list of integers. If m does not occur in the list, the list remains unchanged.
-- >>> removeFst 5 [1, 5, 6, 5]
-- [1,6,5]
removeFst :: (Eq a) => a -> [a] -> [a]
removeFst x [] = []
removeFst x (y : ys)
| x == y = ys
| otherwise = y : (removeFst x ys)
-- | 1.13 Write a function count for counting the number of occurrences of a character in a string. In Haskell, a character is an object of type Char, and a string an object of type String, so the type declaration should run: count :: Char -> String -> Int.
-- >>> count 'e' "Discrete Math, eek"
-- 4
count :: Char -> String -> Int
count c [] = 0
count c (x : xs)
| c == x = 1 + (count c xs)
| otherwise = (count c xs)
-- 1.14 A function for transforming strings into strings is of type String -> String. Write a function blowup that converts a string a1 a2 a3 · · · to a1 a2 a2 a3 a3 a3 · · · . blowup "bang!" should yield "baannngggg!!!!!". (Hint: use ++ for string concatenation.)
-- | A helper for a helper.
-- >>> copy 5 'a'
-- "aaaaa"
copy :: Int -> Char -> String
copy 0 c = []
copy n c = c : (copy (n - 1) c)
-- | A helper for blowup, which includes start count.
-- >>> blowup' "hi" 5
-- "hhhhhiiiiii"
blowup' :: String -> Int -> String
blowup' [] n = []
blowup' (x : xs) n = (copy n x) ++ (blowup' xs (n + 1))
-- | The actual blowup function, which starts at 1.
-- >>> blowup "Discrete"
-- "Diisssccccrrrrreeeeeettttttteeeeeeee"
blowup :: String -> String
blowup xs = blowup' xs 1
-- Haskell hackers may appreciate the following alternative.
-- To understand the details, look up the code for zip, take and repeat in Prelude.
-- | Like blowup, spread the word!
-- >>> spread "Math"
-- "Maattthhhh"
spread :: [a] -> [a]
spread xs = [x | (n, y) <- zip [1 ..] xs, x <- take n (repeat y)]
-- 1.15 Write a function srtString :: [String] -> [String] that sorts a list of strings in alphabetical order.
-- The best way to approach this is to generalize mnmInt and srtInt,
-- and use these to implement a general sorting algorithm based on insertion.
-- In Haskell, types for which we can do size comparison,
-- are put in a so-called type class, the type class Ord.
-- In Haskell, we can make this type class requirement part of the type declaration.
-- f :: Ord a => a means that f is a type in class Ord.
-- f :: Ord a => [a] -> a means that f is a function from lists over a to a objects,
-- where a is a type in class Ord.
-- In other words, f picks an object from a list of things,
-- where the list contains objects that can be compared for size.
-- That is the type we need for the generalized minimum function.
-- | A helper function. String ord, like string sort. Think dictonary sorting.
-- >>> mnm ["bad", "good"]
-- "bad"
mnm :: (Ord a) => [a] -> a
mnm [] = error "empty list"
mnm [x] = x
mnm (x : xs) = min x (mnm xs)
-- | a sorting function:
-- >>> srt ["good", "bad", "awesome", "terrible"]
-- ["awesome","bad","good","terrible"]
srt :: (Ord a) => [a] -> [a]
srt [] = []
srt xs = m : (srt (removeFst m xs)) where m = mnm xs
-- 1.17 Write a function: substring :: String -> String -> Bool
-- that checks whether str1 is a substring of str2.
-- The substrings of an arbitrary string ys are given by:
-- 1. if xs is a prefix of ys, xs is a substring of ys,
-- 2. if ys equals y:ys’ and xs is a substring of ys’, xs is a substring of ys,
-- 3. nothing else is a substring of ys.
-- | Detects if first string is a subset of the second string. This should help you be less confused...
-- >>> substring "bob" "combobulate"
-- True
substring :: String -> String -> Bool
substring [] ys = True
substring (x : xs) [] = False
substring (x : xs) (y : ys) =
((x == y) && (prefix xs ys))
|| (substring (x : xs) ys)
-- | 1.20 Use map to write a function lengths that takes a list of lists and returns a list of the corresponding list lengths.
-- >>> lengths [[1,2,3],[1,2,3,4]]
-- [3,4]
-- | Strings are lists of Char in Haskell. The function is polymorphic.
-- >>> lengths ["Discrete", "Math"]
-- [8,4]
lengths :: [[a]] -> [Int]
lengths = map length
-- | 1.21 Use map to write a function sumLengths, that takes a list of lists and returns the sum of their lengths.
-- >>> sumLengths [[1,2,3],[1,2,3,4]]
-- 7
sumLengths :: [[a]] -> Int
sumLengths lists = sum (map length lists)
-- | Here is another way to express this, using (.) for function composition:
-- >>> sumLengths' ["Discrete", "Math"]
-- 12
sumLengths' :: [[a]] -> Int
sumLengths' = sum . lengths
This is a quick tutorial you should read in-full:
https://www.happylearnhaskelltutorial.com/contents.html
Here is another:
https://haskyll.github.io
If you are feeling ambitious, this is a longer book:
https://learnyouahaskell.github.io/chapters.html
These are some videos to binge on 2x speed:
https://youtube.com/playlist?list=PLOJjn67NeYg9cWA4hyIWcxfaeX64pwo1c
https://youtube.com/playlist?list=PLe7Ei6viL6jGp1Rfu0dil1JH1SHk9bgDV
https://youtube.com/playlist?list=PLD8gywOEY4HauPWPfH0pJPIYUWqi0Gg10
https://youtube.com/playlist?list=PLF1Z-APd9zK7usPMx3LGMZEHrECUGodd3
https://youtube.com/playlist?list=PLF1Z-APd9zK5uFc8FKr_di9bfsYv8-lbc
To explore the community discussions more:
https://haskell.pl-a.net/