Skip to content

Instantly share code, notes, and snippets.

@gusalbukrk
Last active January 8, 2021 18:11
Show Gist options
  • Save gusalbukrk/5407d7d07ec96a3515c3741e2987fec6 to your computer and use it in GitHub Desktop.
Save gusalbukrk/5407d7d07ec96a3515c3741e2987fec6 to your computer and use it in GitHub Desktop.
#haskell #functional

Haskell

  • purely functional programming language
  • isn't an imperative language (sequence of tasks, variable values can change, have control structure)
  • elegant and concise, its programs usually are short than their imperative equivalents
  • functions are pure:
    • functions has no side-effects, the only thing it can do is to calculate something and return a result
    • functions have referential transparency:
      • calling a function twice with the same arguments it's guaranteed to return the same result
      • replace a function with its corresponding value doesn't change the program's behavior (because a pure function doesn't have side-effects, only return a value)
  • it's lazy:
    • unless specifically told otherwise, Haskell won't execute functions and calculate things until it's really force to show you the result
    • this allow things such a infinite data structure
  • it's statically typed:
    • compiler knows which piece of code is a number, string and so on...
    • it includes type inference = the type system intelligently figure out the type of variables

Installation

  • full installation Haskell Plataform
    • sudo apt install haskell-plataform
    • sudo apt install haskell-stack && stack upgrade if weren't installed with command above

Run

  • GHC takes a Haskell script (usually '.hs' extension) and compile

GHCi - Interactive mode

  • GHCi is the GHC interactive mode
    • workflow: load a script, call a function from it and get results immediately
    • this is much easier and faster than compiling every time

commands

  • ghci = invoke the interactive mode
    • :l <filename>.hs = load a specific script
    • :r = to reload the script
    • :t, :type = get expression type (Char, Bool...) or function type/signature
      • e.g. :t sqrt, :t (+)
    • :info String

compile

# remember: 'main =' is the entry point and is required
ghc --make myfilename
./myfilename

runhaskell

  • runhaskell filename.hs = run on the fly

Packages

  • either cabal-install or stack, both overlap a lot but have different workflows
  • they're build tools and package managers
  • both use the Cabal library underneath, think of them like an frontend
  • recommendation: use any, doesn't matter unless you're creating a library which is required to work specifically with cabal-install and/or stack

cabal-install

  • files: '.cabal'
  • dependency problems (version conflicts in global installations) happened very often
  • cabal-install evolved in recent years:
    • introduced sandboxes (project specific installations)
    • dependency conflict avoidance was added
-- global installation
cabal update
cabal install <package-name>

Stack

  • replaces cabal-install
  • was created to solve cabal-install's very common dependency problems
  • files: '.cabal' plus a additional '.stack.yaml'
  • it uses the Cabal library but with a curated version of the Hackage called Stackage where dependencies are curated to build together
stack upgrade -- update

-- setup a new project
stack new my-project
cd my-project
stack setup
stack build
stack exec my-project-exe

stack install <package-name>

Modules

Export

-- module name (always capitalized) and filename must be the same
module MyModule (double) where

double x = x * 2

Import

  • import Data.List = at the beginning of the file

Types and Typeclasses

types

-- :t "abc" -- [Char]
-- :t True -- Bool
-- :t ("a", 3, True) -- tuple types vary by length and components' types

declare the type

-- optional
-- use a colon to define a value type

a = 12 -- defaults to type double

-- declaration on top
b :: Int
b = 12

-- inline declaration
c = 3 :: Float

Maybe & Either

-- Maybe type is an instance of Functor and Monad
-- data Maybe a = Nothing | Just a
-- represents the potential absence of value; similar to null in JS

mdouble :: [Int] -> Maybe [Int]
mdouble [] = Nothing
mdouble x = Just (map (* 2) x)

a = mdouble []
b = mdouble [3, 5]

-- represents a pice of data that can be one of two things
-- Left value is used when something went wrong, Right when everything is ok

safeDiv :: Int -> Int -> Either String Int
safeDiv _ 0 = Left "You cannot divide by zero!"
safeDiv x y = Right (x `div` y)

c = safeDiv 3 0

typeclasses

type class declaration

-- types have typeclasses; they're an instance of a typeclass
-- everything before => is called class constraint
-- :t (==) -- Eq a => a -> a -> Bool

-- `(Num a)` = 'a' must be of numeric type (Int, Double...)
getLength :: (Num a) => [a] -> Int
getLength [] = 0
getLength (_:xs) = 1 + (getLength xs)

Show & Read

-- Show and Read are typeclasses
-- an instance of Show os anything that can be represented as a string

-- show function takes value (member of Show) and presents it as a string
show 3 -- "3"

-- read function takes a string and returns a type (member of Read)
read (show "abc") :: String -- "abc"
read "5" :: Integer -- 5

Creating types & classes

enumerated types

-- enumerated type consist of a fixed set of constants
data Weekend = Friday | Saturday | Sunday deriving (Show, Eq)

day1 :: Weekend
day1 = Friday 

-- print is only possible because it's derived from Show

-- compare is only possible because is derived from Eq
compareMe = day1 == Friday

custom types

data Customer = Customer String String Int deriving Show

c1 :: Customer
c1 = Customer "John" "Smith" 32

getAge :: Customer -> Int
getAge (Customer _ _ age) = age

-- getAge c1

instance

-- instance gives more control than deriving
-- an instance of a class is an individual type which belongs to that class

data TrafficLight = Red | Yellow | Green

instance Eq TrafficLight where
  Red == Red = True
  Yellow == Yellow = True
  Green == Green = True
  _ == _ = False

instance Show TrafficLight where
  show Red = "Red light"
  show Yellow = "Yellow light"
  show Green = "Green light"

x :: TrafficLight
x = Red

y :: TrafficLight
y = Green

class

class YesNo a where
  truth :: a -> Bool

-- make a type be a instance of the created class
instance YesNo [a] where
  truth [] = False
  truth _ = True

a = truth [3, 5]
b = truth []

Functions

Create functions

  • addMe x y = x + y

type declarations

  • optional, but good practice
add :: Int -> Int -> Int -- arguments: 2 Int, return: Int
add x y = x + y

listMax :: [Int] -> Int -- argument: list of Int, return: Int
listMax x = maximum x

-- test with max or min
fiveOrTen :: (Int -> Int -> Int) -> Int -- argument: function (gets two Int, returns one)
fiveOrTen func = func 5 10

Infix vs prefix

  • 2 * 3 = infix function
  • mod 10 3 = prefix function
  • convert:
    • (+) 2 3 = from infix to prefix
    • 10 `mod` 3 = from prefix to infix

pattern matching

-- separate function bodies for different patterns
-- only performs equality (=) comparisons

num :: Int -> String
num 1 = "one"
num 2 = "two"
num _ = "neither one nor two"

Parentheses

  • used to do function application, just like the space (however, space has higher precedence)
  • arguments aren't surrounded by parentheses
  • are only required for function application; e.g. a (b 10)

dollar sign

-- function application, but with lower precedence than space
-- it's a convenience function so that we don't have to write so many parentheses. 
-- when a $ is encountered, the expression on its right is applied as the parameter to the function on its left

-- ($) :: (a -> b) -> a -> b
x = mod 9 $ round 2.0

Recursion

  • fact x = if x == 1 then x else x * fact (x - 1)

higher order functions

-- map :: (a -> b) -> [a] -> [b]
-- map takes a function
x = map ((+) 3) [7, 12, 15]

-- partially apply any function returns a function
double = (*) 2
y = double 7

curry

-- every function in Haskell officially only takes one parameter
-- function that accept several arguments are curried functions
-- putting a space between two things is simply function application
-- application with a space is left-associative ; f a b c = ((f a) b) c))

-- max :: Ord a => a -> a -> a
-- e.g. max takes an 'a', returns (->) a function that takes an 'a' and returns an 'a'
x = max 7 3

partial application

-- because functions in Haskell are curried,
-- when you call one with too few arguments,
-- we get back a partially applied function

add3 = (+) 3
x = add3 7 -- 10

Lambda

x = map (\x -> x * 2) [3, 5, 8] -- backslash denotes an lambda

composition, dot operator

-- (f . g) x = f(g(x))
-- (f . g . z) x = f (g (z x))

pow2 x = x * x
half x = x / 2
add2 x = x + 2

a = (pow2 . half . add2) 12 -- 49.0
b = (add2 . half . pow2) 12 -- 74.0

-- lambda
l = map (\x -> negate (abs x)) [-4, 2, -1, 3] -- without composition
ll = map (negate .abs) [-4, 2, -1, 3] -- with composition

Operators

  • operators are just functions with special names

Control structures

if

  • x = if True then "t" else "f" = if is an expression
  • else part is mandatory
    • because every expression and function must return something

case

-- case only performs equality (=) comparisons
-- (not >, <, ...)
num :: Int -> String
num n =
  case n of
    1 -> "one"
    2 -> "two"
    _ -> "neither one nor two"

-- syntactic sugar
-- num :: Int -> String
-- num 1 = "one"
-- num 2 = "two"
-- num _ = "neither one nor two"

guards

education :: Int -> String
education age
  | age <= 6 = "kindergarten"
  | age <= 17 = "school"
  | otherwise = "college or other"

where

-- where is used to avoid repetition
--- names defined at where are only visible to that function
avgScore :: Int -> Int -> String
avgScore n1 n2
  | avg < 60 = "low"
  | avg >= 60 = "high"
  where avg = (n1 + n2) `div` 2

where & case

isList :: [a] -> String
isList l = "The list is " ++ what l
  where what [] = "empty."
        what [x] = "a singleton."
        what xs = "a longer list."

let

-- similar to where
-- let vs where = let is an expression and go in many more places than where

-- let expression
a = let x = 7 in x + 3

-- inside list comprehension
b = [(x, y) | x <- [3, 5, 8], let y = 2 * x]

-- inside do-notation
c = do
  let x = 7
  x + 3

Data structures

character

  • enclosed in single quotes

Lists

  • homogeneous = it stores several elements of the same type
  • list of characters are displayed as a string (double quotes)

concatenation

  • [1, 2] ++ [3, 4, 5]

cons operator

  • 0:[1,2,3] = cons operator = prepend a single element to list
  • cons takes a single number/char and a list, whereas concatenation takes two lists

get element (!!)

  • [1, 2, 3] !! 1 = 2

comparison

  • e.g. [2, 3] /= [2, 3, 4]
  • first the heads are compared. If they are equal then the second elements are compared, etc

ranges

  • [1..20]
    • product [1..8] = factorial of 8
  • [0, 2..10] = specific step; first two is the first to in the sequence and the last is the upper limit
  • [20, 19..0] = list with decreasing values, set a step is necessary

infinite lists & laziness

  • [0, 2..] = doesn't specify upper limit, infinite list
    • take 10 [0, 2..] = take is to make sure that the program don't run indefinitely

list comprehension

  • returns a list from an existent list(s)
  • [x * 2 | x <- [1..10], mod x 2 == 0] = [2,4,6,8,10,12,14,16,18,20]
    • x <- [1..10] = generator; can have multiple generators
    • x * 2 = output function
    • mod x 2 == 0 = predicate or condition; only numbers that pass this condition, will be passed to the output function

Strings

  • enclosed in double quotes
  • are just lists of characters
  • "hello" is just syntactic sugar for ['h','e','l','l','o']

Tuples

  • [("a", True), ("b", False), ("c", True)] = a list containing 3 tuples size 2
  • fixed-length of values
  • its type depends on how many components it has and the types of the components
    • a tuple size 2 has a different type of a tuple size 3
    • ("a", True) and ("b", 3) have different types
  • access in tuples with 2 items: fst first el, snd second el

Pattern matching

as pattern

-- break something up and keep a: reference to the whole
baz :: String -> String
baz all@(x:xs) = "The first letter of '" ++ all ++ "' is " ++ [x]

Lists (xs)

double :: [Int] -> [Int]
double [] = []
double (x:xs) = x * 2 : double xs -- x is the first element, xs is the rest

I/O

  • Haskell separates the part of our program that is pure and the part that is impure
  • I/O actions will only be performed when they are
    • given a name of main
    • and/or inside a bigger I/O action that we composed with a do block
  • '<-' gets the result from a I/O action (e.g. IO String) and stores it as a normal value
main = do -- 'do' is to perform multiple I/O actions
  putStr ("What's your name? ")
  name <- getLine -- note '<-' instead of '='
  putStrLn ("Hello, " ++ name ++ "!")

let

import Data.Char

main = do
  putStrLn "What's your name?"
  name <- getLine
  let upperName = map toUpper name -- let is needed to declare name inside main
  putStrLn ("Hello, " ++ upperName ++ "!")

return

  • return is different in Haskell, it doesn't ends the execution of a function
  • it's the opposite of '<-', it makes an I/O action out of a pure value
  • it's usually used to create an I/O action that doesn't do anything or to reset the previous I/O action value
main = do  
    a <- return "test" -- creates a I/O value (like getLine) and doesn't end execution
    putStrLn $ a  

main recursion

main = do
  line <- getLine

  if null line
    then return () -- does nothing
    else do
      putStrLn line
      main -- recursively call itself

print text

main = do  
    putStr "without line break; "
    putStrLn "with line break"
    putStrLn (show 25) -- doesn't work without show because it's not a string
    print 25

files

hGetContents

import System.IO

main = do  
    handle <- openFile "test.txt" ReadMode -- WriteMode | ReadWriteMode | AppendMode
    contents <- hGetContents handle
    putStrLn contents
    hClose handle -- best practice, make sure to free resources and flush data

utils

  • concise functions to open, read and edit files

readFile, writeFile & appendFile

import System.IO

main = do
  contents <- readFile "test.txt"
  putStrLn contents

  writeFile "test.txt" "content here..." -- rewrite
  appendFile "test.txt" "one more line\n"

Functors & monads

functors

  • in Haskell a functor is a type (with typeclass of Functor) that can be mapped over (e.g. lists, maps, trees)
    • functors = things that can be mapped over
    • described by the typeclass Functor which has only one method (fmap)
  • by mapping multi-parameter functions over functors, we get functors that contain functions inside them

fmap

  • infix version: '<$>'; =
  • fmap is a generalized representation of map (map is specific to lists)
  • fmap :: (a -> b) -> f a -> f b; reads:
    • give me a function that takes an 'a' and returns a 'b' and a box with an 'a' (or several of them) inside it
    • and I'll return you a box with a 'b' (or several of them) inside it.
    • 'f' is a parameterized data type (e.g. List) that takes a type parameter (e.g. Int)
-- <$> = infix version of fmap
a = fmap (\x -> x * 2) [3, 5, 8]
b = (\x -> x * 2) <$> [3, 5, 8]

applicative functors

-- structure intermediate between a functor and a monad
-- found in the Applicative typeclass; contains 'pure' & '<*>' function
-- <*> has the same type declaration than fmap and it's a more robust version of fmap
  -- fmap takes a function and a functor and applies the function inside the functor
  -- <*> takes a functor that has a function in it and another functor, extracts the function from the first functor and then maps it over the second one
-- by mapping "multi-parameter" functions over functors, we get functors that contain functions inside them

a = fmap (+) [3, 5, 8]

-- not an example of applicative functors
-- because it maps a normal function over the functor, instead of directly map with the functor's function
b = fmap (\f -> f 2) a

-- applicative functors
c = a <*> [2]


-- Applicative class preserves context (in this case, Just)
x = Just (* 3) <*> Just 5

monoids

-- monoid is when you have an associative binary function and a value which acts as an identity with respect to that function
-- acts as an identity with respect to a function, it means that when called with that function and some other value, the result is always equal to that other value
-- 1 is the identity with respect to * and [] is the identity with respect to ++. 

mul1 x = x * 1
concEmpty x = x ++ []

a = mul1 7
b = concEmpty [3, 5, 8]

typeclass Monoid

-- Monoid typeclass has a function named mappend
-- it's a binary function that takes two values of the same type and returns a value of that type
-- '<>' is the infix version of mappend

x = [1, 2] <> [3, 4]

monads

-- more robust applicative functor; Monad typeclass has '>>=' (called bind)
-- instances of Monad: lists, Maybe, IO
-- (>>=) :: (Monad m) => m a -> (a -> m b) -> m b  
  -- if you have a value with a context (m a) how do you apply to it a function that takes a normal 'a' and returns a value with a context
  -- monadic value = values with context

-- lists = >>= performs flat map
a = [3, 5] >>= (\x -> [x , x * 2])

-- Maybe = passing values from one calculation to the next
b = (Just 9) >>= (\x -> Just (x + 1)) >>= (\x -> Just (x * 2))

-- IO = >>= performs two actions sequentially

return

-- return is a function from Monad class
-- it simply takes a value and puts it in a context (wrapped in a monad) that still holds that value

-- in ghci: `:t (return 3)`

-- Maybe is an instance of monad
a :: Maybe Int
a = return 7 -- Just 7

b = (Just 9) >>= (\x -> return (x + 1)) -- return is used to preserve Maybe context

do

-- used to gluing together monadic values (e.g. IO) in sequence

-- nested '>>='
a = Just 3 >>= (\x -> Just "!" >>= (\y -> Just (show x ++ y))) -- Just "3!"

-- do chaining
b :: Maybe String
b = do
  x <- Just 3
  y <- Just "!"
  Just ((show x) ++ y)

in lists

-- >>= maps and flats a list

a = [3, 5, 8] >>= (\x -> [x, x * 2])

-- nested
b = [1, 2] >>= (\n -> ['a', 'b'] >>= \ch -> return (n, ch))
-- do
c = do
  n <- [1, 2]
  ch <- ['a', 'b']
  return (n, ch)

Examples

cashier program

import Text.Printf

allBills = [100, 50, 20, 10, 5, 2, 1, 0.5, 0.25, 0.1, 0.05, 0.01] :: [Double]

-- convert values is needed because calculations with doubles/floats can become inaccurate over time
dollar2Cent :: Double -> Int
dollar2Cent n = round (n * 100)
cent2Dollar :: Int -> Double
cent2Dollar n = ((fromIntegral n) / 100)

getChange :: [Double] -> Double -> [(String, Int)]
getChange _ 0 = []
getChange [] _ = []
getChange bills change = tuple ++ (getChange (tail bills) (changeRest))
  where
    billValue = head bills
    billsQuant = floor (change / billValue)
    billValueString = printf "%.2f" billValue -- show float instead of scientific notation
    tuple = if billsQuant > 0 then [(billValueString, billsQuant)] else []
    minus = ((fromIntegral (billsQuant)) * (dollar2Cent billValue))
    changeRest = cent2Dollar ((dollar2Cent change) - minus)

printChange :: (Double, [(String, Int)]) -> IO ()
printChange change = list >>= total -- monad operator is used to chain IO actions
  where
    list = mapM_ (\tuple -> putStrLn (fst tuple ++ " = " ++ (show (snd tuple)))) (snd change)
    total = (\_ -> (putStrLn ("Total = " ++ show (fst change))))

cashier :: Double -> IO ()
cashier change = printChange result
  where
    bills = filter (<= change) allBills
    result = (change, (getChange bills change))

main = do
  cashier 90.94
  cashier 70.96
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment