Haskell
Introduction
This is my first dip into Haskell, just felt like trying it. Haskell is a purely functional programming language. rather than a declarative language. I.E. it is a declarative language rather than imperative. With declarative, you tell the language what the thing is rather than how to do the thing. Most of the resource for this page was from [https://github.com/input-output-hk/haskell-course] which is an excellent course even I could understand
Features
Lazy
Haskell is a programming lazy language and only evaluates on usage. E.g. only evaluates the first 10
let infiniteList = [1..]
take 10 infiniteList
Statically Typed
With Haskell types are inferred
Hello World
Here we start with main and strange format but no curly brackets which is good.
main :: IO()
main = putStrLn "Hello World"
We build like gcc using
ghc hello.hs -o hello
VS Code setup
Well for me I used extensions Haskell, Haskell Syntax Highlighting and Haskell GHCI Debug Photityne. I had a nasty time getting the ghci-dap work because there is stack and cabal which do the same thing. What worked for me was
- Remove stack stuff from ~/.local/bin
- Follow the instructions in the extension for dap
Having a debugger for me just makes the whole thing a lot easier.
Types
- Int, Integer e.g. 1
- Float, Double 21.3
- Chars e.g. 'S'
- Boolean e.g. True, False
- List e.g. [1,2,3], [True, False, True], elements must be of same type
- Tuple e.g. (1,2,3, "string, True)
Function Signature
With Haskell there are no variables as things are immutable. Therefore what look list variable declarations are actually more like consts and call definitions e.g.
name:: String
name = "Bob"
For functions with one argument this is straight forward
square :: Int -> Int
square v =v * v
But more arguments not a straight forward and seem to be declared like currying functions. Remember the last type is the result of the function
product :: Int -> Int -> Int
product x y = x * y
We can have a signature with no specific type. Haskell handles these polymorphic types
first :: (a, b) -> a
first (x, y) = x
The function must return value of type of a.
Notation
Not complicated just how the format operators
Prefix
We use the operator before the arguments. Note brackets around symbol operator
(+) 3 4 prod 3 4
Infix
We use the operator between the arguments. Note for non symbol, need back ticks
3 + 4 3 `prod` 5
Lists
We can assess lists using the double bang operator. e.g.
"abc" !! 1 -- b
[12,13, 16,18] !! 3 -- 18
-- Make a list
[3..5] -- [3,4,5]
-- Make a list with step
[3,5..13] -- [5,8,12]
-- Make an infinite list
[5..]
-- Take
take 3 [1,2,3,4] -- [1,2,3]
-- Concat with ++
[1,2] ++ [3,4] -- [1,2,3,4]
-- Make a list with letters
['a', 'c'...'f'] -- ["acdef"]
-- Null Length, sum and `elem`
Here are some more examples
let x = [1..10]
head x -- 1
tail x -- [2,3,4,5,6,7,8,9,10]
last x -- 10
init x -- [1,2,3,4,5,6,7,8,9]
reverse x -- [10,9,8,7,6,5,4,3,2,1]
length x -- 10
take 5 (x) -- [1,2,3,4,5]
drop 6 (x) -- [7,8,9,10]
sum x -- 55
product x -- 3628800
elem 8(x) -- True Searches list
null x --- False
We also have words, unwords, lines and unlines to join and break text
Conditionals
If else
You must use the else as every function must return a value
main = do
let var = 23
if var `rem` 2 == 0
then putStrLn "Number is Even"
else putStrLn "Number is Odd"
Guards
Simple and easy to read
bookCategory :: Int -> String
bookCategory age
| age < 10 = "Children's book"
| age < 18 = "Teen book"
| otherwise = "Adult book"
main = do
print(bookCategory 5)
print(bookCategory 15)
print(bookCategory 25)
Pattern Matching
Again easy pezzy and I love pattern matching
-- Pattern Matching
coffeeType :: String -> String
coffeeType "Epresso" = "Strong and bold"
coffeeType "Cappuccino" = "Frothy and delicious"
coffeeType "Filter" = "Watery and weak"
coffeeType _ = "I have no idea what that is"
main = do
print(coffeeType "Epresso")
print(coffeeType "Cappuccino")
print(coffeeType "Filter")
print(coffeeType "Latte")
Case Expressions
Again easy pezzy
checkForZeroes :: (Int, Int,Int) -> String
checkForZeroes (0,_,_) = "First is zero"
checkForZeroes (_,0,_) = "Second is zero"
checkForZeroes (_,_,0) = "Third is zero"
checkForZeroes _ = "No zeros"
checkForZeroes(32,0,256) -- "Second is zero"
Let expressions
The word bind haughts me, it is like trying to remember Samuel L Jackson's name. Here is the tutors opening gambit
With in
func arg = let <BIND_1> <BIND_2> in <EXPR that uses BIND_1 and/or BIND_2>
With where
func arg = <EXPR that uses BIND_1 and/or BIND_2> where <BIND_1> <BIND_2>
I have to say they are now speaking my language as this was the example which does look pretty pretty awful
hotterInKelvin :: Double -> Double -> Double
hotterInKelvin c f = if c > (f- 32) * 5/9 then c + 273.16 else (f - 32) * 5/9 + 273.16
hotterInKelvin 40 100 -- 273.16
Now we have this and I understand the BIND_1 or BIND_2 are just declared expressions for use with the in control
hotterInKelvin2 :: Double -> Double -> Double
hotterInKelvin2 c f =
let fToCelius t = (t - 32) * 5 / 9
cToKelvin t = t + 273.16
fToKelvin t = cToKelvin (fToCelius t)
in if c > fToCelius f then cToKelvin c else fToKelvin f
And using where approach
hotterInKelvin3 :: Double -> Double -> Double
hotterInKelvin3 c f =
if c > fToCelius f then cToKelvin c else fToKelvin f
where
fToCelius t = (t - 32) * 5 / 9
cToKelvin t = t + 273.16
fToKelvin t = cToKelvin (fToCelius t)
I prefer the where one because I can stop reading quicker but they said I would take time to workout and sometimes preference
More on Functions
High Order functions
Now I know more about signature and how to read them
applyTwice :: (a->a) -> a -> a
applyTwice f x = f (f x)
Make must more sense, as the signature shows
- the first argument is a function take a type a and returning a type a,
- the second a value of type a and
- the result is of type a
Built-in Map function
let increment x = x + 1
map increment [1,2,3] -- [2,3,4]
Built-in Filter function
And filter, loving the mod operation
let isEven n = n `mod` 2 == 0
filter isEven [2,3,4] -- [2,4]
Built-in Foldl function
Finally something new, foldl, i.e. apply operation start from position using array from left to right (lispy)
foldl (+) 0 [1,2,3,4,5] -- 15
Sort function and others
And now first use of libraries
import Data.List (sort)
sort [10, 2, 5, 3, 6, 7, 4, 8, 9, 1]
Other functions were zipWith, takeWhile, dropWhile
-- All
all even [1,2,4] -- False
any even [1,2,4] -- True
Lambda Expressions
This is a shortcut to writing unnamed function. We do this by
- specifying a back slash
- specifying arguments
- an arrow
- specifying body
print((\c -> c * 9/5 + 32) 25) -- 77.0
Precedence and Associativity
In Haskell the operators have a precedence, like looking at type with :t you can see this with :i (+). The higher the number the higher the precedence. If two have the same then then the associativity decides. So for (+) the :i shows infixl (i.e. left)
Curried Functions
All functions take on argument in Haskell. Hopefully this explains it as all functions are broken down and applied
Function application $ Operator
Gosh tired and messed with my brain but I think it means a $ will usually put parentheses around the arguments following
(2 *) 3 + 4 -- ((2 *) 3) + 4 = 10
(2 *) $ 3 + 4 -- (2 *) (3 + 4) = 14
max 5 4 + 2 -- ((max 5) 4) + 2 = 7
max 5 $ 4 + 2 -- (max 5) (4 + 2) = 6
The biggest use of $ is to replace a set of parentheses to make code more readable
show ((2**) (max 3 (2 + 2))) -- 16
show $ (2**) (max 3 (2 + 2)) -- 16
show $ (2**) $ max 3 (2 + 2) -- 16
show $ (2**) $ max 3 $ 2 + 2 -- 16
Function Composition
Here we can combine functions in one call. This just looks like chaining to me but here is the example like in Maths when you have f{g(x)} First lets look a the signature of the dot operator with my new found signature skills. The result of the second function the same type as the input to the first function and the result is the same type as the output of the first function
(.) :: (b->c) -> (a -> b) -> a -> c
f . g = \x -> f (g x)
And an example
-- IsEven function
isEven :: Int -> Bool
-- Is Odd function
isNotEven :: Bool -> String
isEven x = if x `rem` 2 == 0
then True
else False
isNotEven x = if x == True
then "This is an Even Number"
else "This is an Odd Number"
main = do
print((isNotEven.isEven) 5)
Point Free Style
Did not like the sound of this. Surely easier to decide and go with it. But here we are for reference
Recursion
Sum Example
Lets start with sum, I did not know at the beginning but (x:xs) takes a list and splits it into first element + rest
sum' :: [Int] -> Int
sum' [] = 0 -- No elements no value
sum' (x:xs) = x + sum xs
This works out as
sum'[1,2,3,4,5] = 1 + sum' [2,3,4,5] = 1 + 2 + sum'[3,4,5] = 1 + 2 + 3 + sum'[4,5] = 1 + 2 + 3 + 4 + sum'[5] = 1 + 2 + 3 + 4 + 5 + sum'[] = 1 + 2 + 3 + 4 + 5 + 0 = 15
Where would we be without Factorial
Stone a crow
The one thing which wasn't immediately clear we that the function we do a pattern matching. I.E. if you comment of the sum' to only match on 0 then you will get the error
Non-exhaustive patterns in function sum'
Note a single quote on a function is pronounced prime. e.g. sum' = sum prime
Product Example
This points out the fact you need to think about the base case. Does not work for 0
product :: [Int] -> Int
product [] = 1 -- No elements then 1
product (x:xs) = x * sum xs
This works out as
sum[1,2,3,4,5] = 1 * product [2,3,4,5] = 1 * 2 * product[3,4,5] = 1 * 2 * 3 * product[4,5] = 1 * 2 * 3 * 4 * product[5] = 1 * 2 * 3 * 4 * 5 * product[] = 1 * 2 * 3 * 4 * 5 * 1 = 120
Factorial Example
Where would we be without Factorial
-- Factorial
factorial :: Int -> Int
-- Base case
factorial 0 = 1
-- Recursive case
factorial n = n * factorial (n - 1)
factorial 5 = 5 * factorial[4] = 5 * 4 * factorial[3] = 5 * 4 * 3 * factorial[2] = 5 * 4 * 3 * 2 * factorial[1] = 5 * 4 * 3 * 2 * 1 * factorial[] = 5 * 4 * 3 * 2 * 1 * 1 = 120
Steps to Create Recursion
Here is recommended steps
- Write down Type: This will help define the function
- Enumerate the possible cases based on inputs
- Between the above cases identify simplest and define
- Think about what you have available
- Define the rest
- Reflect
Drop Example
For this we drop n elements from a list. This highlight that we should consider the following cases
- We are using numbers so 0 or other
- We are using a list so empty or non-empty
A reminder of more parameters more arrows and that the last is the return value
drop' :: Int -> [a] -> [a]
-- Cases to consider
drop' : 0 [] = -- 0 and Empty list
drop' : 0 (x:xs) = 0 and Non-Empty list
drop' : n [] = non-zero and Empty list
drop' : n (x:xs) = non-zero 0 and Empty list
So we can answer 3 of the 4 cases
drop' :: Int -> [a] -> [a]
drop' : 0 [] = [] -- 0 and Empty list
drop' : 0 (x:xs) = x:xs -- what we sent is what we get back
drop' : n [] = []
drop' : n (x:xs) = drop' (n-1) xs
Improving on this and knowing we are really just pattern match we get
drop` :: Int -> [a] -> [a]
drop` : _ [] = [] -- 0 and non-zero with Empty List can be matched with _
drop` : 0 xs = xs -- confused me as xs is the rest but there are no brackets so xs is all
--Not Required drop' : n [] = []
drop` : n (_:xs) = drop' (n-1) xs -- x not used
Finally what if the pass a negative number of elements, lets assume none
drop' :: Int -> [a] -> [a]
drop' _ [] = []
drop' n xs | n <= 0 = xs -- check for negative
drop' n (_:xs) = drop' (n-1) xs
-- So now
(-3) [1,2,3] -- [1,2,3]
foldr and the Primitive Recursive Pattern
Well looking at sum and product examples above we see that there is a pattern and it is the same as the built in function foldr. This pattern is known as the ```Primitive Recursive Pattern
sum` :: [Int] -> Int
sum` foldr (+) 0
product` :: [Int] -> Int
product` foldr (*) 1
Non Parameterized Types
Type Synonyms
We can use Type Synonyms to make a type have a more readable but the same functionality. e.g
type Address = String
type Value = Int
type Id = String
-- Makes the function name more readable
generateTxId :: Address -> Address -> Value -> Id
generateTxId from to value = from ++ to ++ show value
New Types
We can make a new type
data Colour = Red | Green | Blue
Value Parameters
We have value parameters associated with our types
data Shape = Circle Float | Rectangle Float Float
Record Syntax
Record Syntax allows us to define our type constructors much like record in C#.
data Employee = Employee { name :: String, experienceInYears :: Float } deriving show
matt = Employee "Matt" 5
-- Update to another Matt
newMatt = matt { experienceInYears = 6.0 }
Parameterized and Recursive Types
Type Synonyms
We can also use parameterized Synonyms where we identify commonality
...
type Company = (CompanyId, Address)
type Provider = (ProviderId, Address)
-- Instead we could have
type Entity a = (a, Address)
--Examples
company = (123, ("Fred St", 999)) :: Entity CompanyId
provider = ("String", ("Fred St", 999)) :: Entity ProviderId
Parameterized Type
So we can create types with an unspecified type. We constrain it with the fat arrow (=>) for the type we are using. I guess this is like generics
--data Box a = Empty | Has a
box = Has(1:: Int)
addN:: Num a => a-> Box a -> Box a
addN _ Empty = Empty
addN n (Has a) = Has (a+n)
addN 3 Box -- Has 4
So an example
data Tweet = Tweet
{ content :: String,
likes :: Int,
comments :: [Tweet]
}
-- Example
tweet:: Tweet
tweet = Tweet "I am angry about something" 5
[ Tweet "Me too" 0 [],
Tweet "It make me angry you are angry" 2
[ Tweet "I have no ideas" 3 [] ]
]
-- Get total likes for above
engagement :: Tweet -> Int
engagement Tweet { likes = l, comments = c} = l + length c + sum(map engagement c)
engagement tweet --- 13
Binary Tree
Searching a binary tree seems pretty simple.
The important thing I learned was you can constrain with the fat arrow what a can be. The function has an a and a Tree of type a.
data Tree a = EmptyT | Node a (Tree a) (Tree a)
elemTree :: (Ord a) => a -> Tree a -> Bool
elemTree v EmptyT = False
elemTree v (Node x left right)
| v == x = True
| v > x = elemTree v right
| v < x = elemTree v left
Some Notes
- The type of a value constructor gives you the quantity and type of its values it takes
- The kind' of a type constructor gives you the quantity and kind of types it takes
Reading of kinds
- '*' means concrete type
- '* -> *' means type constructor takes a single concrete type and returns another concrete type
- '* -> (* -> *) -> *' means type constructor that takes one concrete type, and one single parameter type constructor and returns a concrete type
newType Keyword
Think they wanted to make it hard. This seems pretty pointless but I will wait and see. Types create with newType need to have exactly one constructor with exactly one parameter/field
newType Color a = Color a
newType Product a = Product { getProduct a}
And the answer was, it improves performance in the compiler - Sigh!!!
TypeClasses
Introduction
This is an overview of what typeclasses are which I got from [here] and made sense to me which is the main struggle for documentation for me.
What is a typeclass
First of all a typeclass is uses the keyword class. Here is the Num typeclass
class Num a where
(+) :: a -> a -> a
(-) :: a -> a -> a
(*) :: a -> a -> a
negate :: a -> a
abs :: a -> a
signum :: a -> a
fromInteger :: Integer -> a
It provides several a set of functions which you either need to implement or derive
Deriving a typeclass implementation
You can do this by using the deriving keyword when defining you data type
data Pokemon = Pokemon
{ pokedexId :: Int
, name :: String
, pokemonType :: [String]
, abilities :: [String]
} deriving (Eq)
This is only possible because the compiler can infer Eq on the data types used in the Pokemon data type
Defining a typeclass implementation with instance
To define our own typeclass implementation we can use the instance keyword. You must not now have deriving
data Pokemon = Pokemon
{ pokedexId :: Int
, name :: String
, pokemonType :: [String]
, abilities :: [String]
}
instance Eq Pokemon where
pokemon1 == pokemon2 = pokedexId pokemon1 == pokedexId pokemon2
Knowing What to implement
We can find what we need to implement by using the info command
*Main> :info Eq ... {-# MINIMAL (==) | (/=) #-}
Examples of Type Classes
Here are some examples of common typeclasses to force me to keep the idea in my head.
- Num
- Integral
- Fractional
- Show -- Debugging
- Read -- Reverse of Show convert string to type e.g. read :: [Int] "[1,2,2]" we get [1,2,3]
Creating a Type Class
To create a type class you need to
- Create a type class stating some behaviors
- Make a Type instance of that Type Class with implementation of those behaviors
Overloading
- In Haskell Overloading is multiple implementations of function or value with the same name
Example 1 Defining Type Class
The Eq is already defined but here is how it could work. It looks like we are just defining overloaded operators like C++
-- Define class
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
-- Here is our data type
data PaymentMethod = Card | Cash | CC
-- Create instance of our PaymentMethod
Instance Eq PaymentMethod where
-- ==
Card == Card = True
Cash == Cash = True
CC == CC = True
_== _ = False
-- /=
Card /= Card = False
Cash /= Cash = False
CC /= CC = False
_== _ = True
Now this was easy but can be done better because we have repeated ourselves with the Not Equal bit. We use some called Mutual Recursion, with is when we define the negative in terms of the positive and vice-versa. Probably wont remember but the name will allow me to google it but here is the improved version
class Eq a where
(==), (/=) :: a -> a -> Bool
x /= y = not (x ==y)
x == y = not (x /=y)
-- Now we only need to define the positive as the compiler will infer the negative
Instance Eq PaymentMethod where
Card == Card = True
Cash == Cash = True
CC == CC = True
_== _ = False
-- Info for Eq class
:i Eq
type Eq :: * -> Constraint
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
{-# MINIMAL (==) | (/=) #-}
...
Because both function have the same type we can specify them in oneline. The not function which takes a boolean and returns the opposite. We can see running the info command for the Eq class we see the MINIMAL implementation is == or /=
Define Eq and Ord for Parameterized Types
Here hope we can define instances for parameterized types (Box in this case). We simply handle the non parameterized part and pass the Eq and Ord functions down to the concrete type.
-- Implement Eq for Box a
instance (Eq a) => Eq (Box a) where
Empty == Empty = True
Empty == _ = False
_ == Empty = False
(Has x) == (Has y) = x == y
-- Implement Ord for Box a
instance (Ord a) => Ord (Box a) where
compare Empty Empty = EQ
compare Empty _ = LT
compare _ Empty = GT
compare (Has x) (Has y) = compare x y
Example 2 Type Class
So here is another example. I guess I am hoping these examples will help as I have compiled them and they work. You seem to create a class much like other languages or operate on the types you create
data Box a = Empty | Has a deriving Show
data Present t a = EmptyPresent t | PresentFor t a deriving Show
class Container c where
isEmpty :: c a -> Bool
contains :: Eq a => c a -> a -> Bool
replace :: c a -> b -> c b
instance Container Box where
isEmpty Empty = True
isEmpty _ = False
contains (Has x) y = x == y
contains Empty _ = False
replace _ = Has
instance Container (Present t) where
isEmpty (EmptyPresent _) = True
isEmpty _ = False
contains (PresentFor _ x) y = x == y
contains(EmptyPresent _) _ = False
replace (PresentFor tag _) x = PresentFor tag x
replace (EmptyPresent tag) x = EmptyPresent tag
main = do
print $ Has False `contains` False
print $ isEmpty (Has 'a')
print $ PresentFor "Tommy" 5 `contains` 5
print $ PresentFor "Tommy" 5 `replace` "Arduino"
Deriving
We can use the deriving keyword to implement some on the type classes e.g.
data Choice = No | Idk | Yes deriving (Eq, Ord,. Show, Bounded, Enum)
-- So ordering of variables matters and it will using this not the label alphabetically
data PaymentMethod = Cash |Card|CC deriving (Eq,Ord)
But be careful
data Length = M Double | Km Double deriving (Eq)
-- This won't work if you want to mix and match you meters and kilometers so we need to implement
instance Eq Length where
(M x) == (M y) = x == y
(Km x) == (Km y) = x == y
(M x) /= (Km y) = x == 1000 * y
(Km x) /= (M y) = 1000 * x == y
Basic IO
IO Actions
- IO action can interact with and change the world outside of our program
- A Side effect is any observable effect other than the effect of returning a value
Examples of these are
API
Screen
Keyboard
The have two IO actions
>> - then operator - IO a -> IO b -> IO b
We can see it takes two parameters, with IO it performs the actions and discards the result. Though you can see a and b, it is the action produces this, not a returned value, only c is returned. Bit like cout
abc :: IO ()
abc = putStrLn "a" >> putStrLn "b" >> putStrLn "c"
abc -- prints abc
>>= - bind operator - IO a -> (a -> IO b) -> IO b With this operator the values is returned and the getLine IO action result is fed into the yellit function
yellit :: String -> IO ()
yellit str = pustStrn (str ++ "!!!!!")
yellitBack :: String -> IO ()
yellitBack = getline >>= yellit
-- Yellit ouputs
yellit "Hey" -- Outputs Hey!!!!!
-- YellitBack ouputs
YellitBack ouputs
-- After typing How's it going
-- How's it going!!!!!
Chatty Bot
We can use the do notation and the left arrow binding. This looks a lot better
finalChattyBot :: IO ()
finalChattyBot =
putStrLn "Hey! What's your name?"
>> getLine
>>= ( \name ->
putStrLn ("Nice to meet you, " ++ name ++ "!")
>> putStrLn (lettersInName name)
>> putStrLn ("So, " ++ name ++ ", what do you do for fun?")
>> getLine
>>= ( \hobby ->
putStrLn ("Are you kidding, " ++ name ++ "! I love " ++ hobby ++ "!")
)
)
>> putStrLn "OK, bye!"
finalChattyBotDo :: IO ()
finalChattyBotDo = do
putStrLn "Hey! What's your name?"
name <- getLine
putStrLn ("Nice to meet you, " ++ name ++ "!")
putStrLn (lettersInName name)
putStrLn ("So, " ++ name ++ ", what do you do for fun?")
hobby <- getLine
putStrLn ("Are you kidding, " ++ name ++ "! I love " ++ hobby ++ "!")
putStrLn "OK, bye!"
File IO
Reading
Very easy
main = do
contents <- readFile "movies.txt"
putStr contents
Writing
main = do
let movies = ["The Godfather", "The Shawshank Redemption", "The Dark Knight", "Pulp Fiction", "The Lord of the Rings: The Return of the King"]
writeFile "movies.txt" (unlines movies)
putStrLn "Movies written to movies.txt"
Modules
Importing
We can import using the import command. Going to [https://hackage.haskell.org] provides help on packages and the search is great
Example Importing
import System.Directory
import Data.List
find' :: String -> IO [FilePath]
find' str = do
entry <- listDirectory "."
-- the isInfixOf compares string in entry
let found = filter (str `isInfixOf`) entry
return found
main :: IO ()
main = do
putStrLn "Provide Search "
str <- getLine
found <- find' str
putStrLn ("These Entries Match" ++ show found)
Importing one or loads or conflicting
To import a part you specify the part you want in brackets e.g.
import Data.List (isInfixOf, sort)
To import all less some things
import Data.List hiding(isInfixOf, sort)
To import via namespace you add qualified to force it to be used
import qualified Data.Map
To import via namespace and rename namespace
import qualified Data.Map as Map
Making a modules
Modules are directory based. I.E. You make a directory and name the module to that name. You do not need a module in each subdirectory so a module for User/Actions/Battle can be
module User.Actions.Battle where
data Golem = Golem { gAttack :: Int, gHp :: Int } deriving Show
data Player = Player { pAttack :: Int, pHp :: Int } deriving (Show)
data Battle = Fight | RunAway deriving (Show, Read)
battle :: IO Bool
battle = undefined
An example of this can be found [here]
Prelude
Gosh rust, this is module loaded by default when creating Haskell
Cabal
This is the packaging system, by default it uses hackage.
- cabal update
- cabal init (start new package)
- cabel build
- cabel exec ForestGame
- cabel run (build and run)
- cabel repl (run the REPL)
Very like npm. Here is an example. The build-depends must be ok for your install version
cabal-version: 3.0
name: ForestGame
version: 0.1.0.0
license: MIT
license-file: LICENSE
author: Robertino Martinez
maintainer: robertino.martinez@iohk.io
category: Game
build-type: Simple
extra-doc-files: CHANGELOG.md
executable ForestModule
import: warnings
main-is: Main.hs
other-modules: Forest.Level1
, User.Actions.Move
build-depends: base ^>=4.16.4.0
, random
hs-source-dirs: app
default-language: Haskell2010
They usually have an app with Main.hs and src with all of the library functions.
Language Extension
You can configure Haskell to use language extensions. The one shown was the NumericUnderscores e.g. 1_000 instead of 1000. To do this you add a #pragma to you code
{-# LANGUAGE NumericUnderscores #-}
A list of these can be found [here]
It did show the extension TypeApplications but I did not understand it. It showed a function declaring the type returned e.g.
startingStamine <- randomIO (10_000, 20_000) :: IO Int
We can put
startingStamine <- randomIO @Int (10_000, 20_000)
I think it is because the return value is polymorphic so you need to specify. This just makes it more readable
Exception Handling
Example 1
Here we can define our exception and make it and instance of Exception. On from that we can define a handler for it
import Control.Exception
data TrafficLightException = TrafficLightIsOff | WrongColor String
deriving (Show)
instance Exception TrafficLightException
nextMove :: String -> IO String
nextMove color = case color of
"Red" -> return "Stop!"
"Yellow" -> return "Wait!"
"Green" -> return "Go!"
"Black" -> throwIO TrafficLightIsOff
_ -> throwIO . WrongColor $ color ++ " is not a valid color!"
main :: IO ()
main = do
putStrLn "What color is the traffic light?"
color <- getLine
response <- nextMove color `catch` handler
putStrLn $ "I'll " ++ response
main
where
handler :: TrafficLightException -> IO String
handler e = do
-- do whatever you want...
putStrLn $ "WARNING: " ++ show e
case e of
TrafficLightIsOff -> return "Proceed with caution."
WrongColor _ -> return "Stop the car and shut down!"
Maybe type
They stressed using of Maybe to hand errors. It mentioned readMaybe, aside from tools for developers, rarely have I written something which uses user input.
Example 2
Here is an example of catching a division by zero in Haskell
import Control.Exception
main = do
result <- try (evaluate (div 5 0)) :: IO (Either SomeException Int)
case result of
Left ex -> putStrLn $ "Caught exception: " ++ show ex
Right val -> putStrLn $ "The result was: " ++ show val
Tips
- using cabal repl you can import you library into the repl
- hackage show details of package
- hoogle search for package using function e.g. String -> IO, or If you know the function but not the package
- undefined Force type check to run, or stop the program at a given point like throw
- type holes use underscore to get information about expected value.
- --- >>> foo ["A","B"] Putting this is the source allow inline evaluation
Functors
Introduction
The definition I got from C++ is function object is a function with state.
Simple C++ Example
struct Value {
int m_result {0};
int operator()(int newResult) {
m_result = newResult;
return m_result;
}
}
int main() {
Value v;
v(42);
std::cout << v.m_result << std::endl;
}
Better C++ Example
They went on to provide a second C++ example where the Comparator was used as a good use of a functor. Calling the function Comparator with the state of the goblins.
struct Goblin {
int health;
int strength;
Goblin(int health, int strength) : health(health), strength(strength) {};
bool operator<(const Goblin& other) const {
return health < other.health;
}
};
struct GoblinComparator {
bool operator()(const Goblin& a, const Goblin& b) const {
return a.strength < b.strength;
}
};
int main() {
std::vector<Goblin> goblins {
{5, 25},
{3, 25},
{100, 1}
};
std::sort(goblins.begin(), goblins.end(), GoblinComparator());
for(const auto& goblin : goblins) {
std::cout << "Health: " << goblin.health << " Strength: " << goblin.strength << std::endl;
}
return 0;
}
// Health: 100 Strength: 1
// Health: 5 Strength: 25
// Health: 3 Strength: 25
Much Better Example
Googling to make sure I understood the idea of a functor, found a much better example. which was a logger and all makes sense now.
class Logger {
public:
Logger(const std::string& fileName) {
m_output.exceptions(m_output.badbit | m_output.failbit);
try
{
m_output.open(fileName, std::ofstream::out);
m_output << "Logger started" << std::endl;
}
catch (const std::ofstream::failure& e)
{
std::cerr << "Failure: " << e.what() << std::endl;
std::string error = "Could not open file: " + fileName;
std::cout << error << std::endl;
throw std::runtime_error(error);
}
}
void operator() (const std::string& message) {
m_output << message << std::endl;
}
public:
~Logger() {
m_output.flush();
m_output.close();
}
private:
std::ofstream m_output;
};
int main() {
Logger logger("log.txt");
logger("Hello, world!");
return 0;
}
Back to Haskell
Arggghhhhh
To I am still struggling but this is what the definition of a functor is and therefore some kind of type definition, i.e. anything which looks like this is a functor
class Functor f where
fmap :: (a->b) -> f a -> f b
The fmap definition is I guess
(a->b) -> f a -> f b
This was explained as
fmap takes a function which is of type a and results in a type b
It applies f of a and gives you a f of b
Map and FMap
So some progress, the tutor showed a couple of things which start to make more sense in the interactive session. They the types which I knew but here to keep context
:t False -- Bool
:t "hello" -- String
:t 'T' -- Char
:t 12 -- Num a=>a (an instance of typeclass Num not type)
-- Now lets look at our Maybe (like Option but instead on Some and None, Just and Nothing)
:t Nothing -- Maybe a
:t Just 5 -- Num a => Maybe a
It is the last one where thing are starting to make sense, the output suggests you put a in and you get a out. So we can now use the kind or k to look a the type constructors
:k Bool -- :: *
:k String -- :: *
:k Char -- :: *
:k Int -- :: *
-- Now lets look at our Maybe
:k Maybe -- * -> * Which means it is a function converting from type to type
So looking a map
-- Example
map (+2) [1,2,3] -- [3,4,5]
-- Looking at type
:t map -- map :: (a -> b) -> [a] -> [b]
This looks a lot like the type of fmap. fmap came first but because lists are so useful they made a specific implementation of fmap called map for lists.
You can use fmap for any shape of data but you have to tell it how to traverse the data you are using. In the example you talked about trees.
Third Video
So opening line was great, a functor is a TYPECLASS. A typeclass, you can think of as an interface you need to implement.
class Functor f where
fmap :: (a->b) -> f a -> f b
As Oliver Hardy might say, now we are getting somewhere. The video went on to distinguish between types where are wrapped and unwrapped. They eluded to the Maybe type which can be a Just or Nothing. They demonstrated unwrapped values can be evaluated
(+3) 9 -- 12 Where 9 is unwrapped
-- but
(+3) (Just 9) -- Will not work
Now the Functor for some types are already defined including, Maybe, Either and List so to demonstrate we can make our on type, a copy of Maybe using this
data Maybe2 a = Just2 a | Nothing2 deriving Show
Now we can make our own Functor for Maybe2
instance Functor Maybe2 where
fmap func (Just2 a) = Just2 (func a)
fmap func Nothing2 = Nothing2
Putting it together we have
import Data.Functor
data Maybe2 a = Just2 a | Nothing2 deriving Show
instance Functor Maybe2 where
fmap f (Just2 x) = Just2 (f x)
fmap f Nothing2 = Nothing2
main = do
print $ fmap (+1) (Just2 10) -- Just2 11
print $ fmap (+2) (Nothing2) -- Nothing2
Trees
Now we come on to the tree, the most common example I have seen. For fmap and a tree we want to apply a function at the tip of the branch to get everything. This will involved recursion as a branch can have many branches
So here is a C# Recursive approach before we start
// Function to perform inorder traversal
static void inorderTraversal(Node node) {
// Base case
if (node == null)
return;
// Recur on the left subtree
inorderTraversal(node.left);
// Visit the current node
Console.Write(node.data + " ");
// Recur on the right subtree
inorderTraversal(node.right);
}
But with Haskell this does seem somewhat more readable
import Data.Functor
data Tree a = Tip a | Branch (Tree a) (Tree a) deriving Show
instance Functor Tree where
fmap f (Tip x) = Tip (f x)
fmap f (Branch left right) = Branch (fmap f left) (fmap f right)
x = Branch (Tip 4) (Branch (Tip 5) (Tip 6))
main = do
print $ fmap (*2) x
Finally
Whilst I understood functor for both C++ and Haskell, I did not understand how a C++ functor related to a Haskell one too much. Time means I march on to Monads
Applicative
Introduction
When they say applicative, they are referring to the <*> symbol. Now we use applicatives they always have a defined Functor, this has the following TypeClass definition
class Functor => Applicative f where
pure :: a-> f a
(<*> :: f (a ->b) -> f a -> f b
You will notice that the second clause is exactly the same as Functor aside from the f
Example 1
Here is an example of using the applicative
import Data.Functor
data Maybe2 a = Just2 a | Nothing2 deriving Show
instance Functor Maybe2 where
fmap f (Just2 x) = Just2 (f x)
fmap f Nothing2 = Nothing2
instance Applicative Maybe2 where
pure = Just2
Just2 f <*> Just2 x = Just2 (f x)
-- _ <*> _ = Nothing2
main = do
print $ fmap (+1) (Just2 10)
print $ fmap (+2) (Nothing2)
print $ Just2 (+3) <*> Just2 10 -- Just2 13
Example 2
Its time for the tree again
import Data.Functor
data Tree a = Tip a | Branch (Tree a) (Tree a) deriving Show
instance Functor Tree where
fmap f (Tip x) = Tip (f x)
fmap f (Branch left right) = Branch (fmap f left) (fmap f right)
instance Applicative Tree where
pure x = Tip x
Tip f <*> x = fmap f x
Branch left right <*> x = Branch (left <*> x) (right <*> x)
x = Branch (Tip 4) (Branch (Tip 5) (Tip 6))
main = do
print $ Tip (*4) <*> x -- Branch (Tip 16) (Branch (Tip 20) (Tip 24))
print $ Branch (Tip (+1)) (Tip (*2)) <*> x -- Branch (Branch (Tip 5) (Branch (Tip 6) (Tip 7))) (Branch (Tip 8) (Branch (Tip 10) (Tip 12)))
Monads
So this is the Monad bit which I what I started on to learn this. Like Functors, the explanation may have been written in greek. I have no idea what he meant
class Monad m where
return :: a -> m a
-- Bind
(>>=) :: m a -> (a -> m b) -> m b
-- Then
(>>) :: m a -> m b -> m b
x >> y = x >>= \_ -> y
-- Fail
fail :: String => m a
fail msg = error msg
He preceded to speak about 3 laws of Monads
--- Three Laws of Monads:
--- 1 Left Identity:
return a >> = f
--- the same as : f a
-- 2 Right identity:
x >>= return
-- the same as: x {Value not changed}
-- 3 Associativity:
(m >>= f) >>= g
m >>= (\x -> f x >>=g)
Moniods
The explanation was again useable. This is left here to fill in later