-- ---
-- jupyter:
--   jupytext:
--     text_representation:
--       extension: .hs
--       format_name: light
--       format_version: '1.5'
--       jupytext_version: 1.14.4
--   kernelspec:
--     display_name: Haskell
--     language: haskell
--     name: haskell
-- ---

-- ## Haskell Basics for Functional Programming

-- *Some snippets in this notebook are taken from BLG458E Functional Programming course slides which can be accessed through the following link: https://www.slideshare.net/uyar/tag/blg458e*

-- ### Definitions
--
-- Definitions associate an identifier(name) with a value of a specific type  
-- **Format:**  
--
-- ```haskell
-- name :: type
-- name = expression
-- ```
--
-- *Frequently used types: Int, Float, String, Char, Bool, Double, Integer*

-- +
age :: Int      -- age is an integer
age = 20        -- age is equal to 20

name :: String  -- name is a string
name = "Bob"    -- name is equal to Bob

gpa :: Float    -- gpa is a float
gpa = 3.78      -- gpa is equal to 3.78
-- -

age

name

gpa

-- **Example**  
-- Calculate circumference of a circle whose radius is 7.2

-- +
radius :: Float
radius = 7.2

circumference :: Float
circumference = 2 * 3.14159 * radius

circumference
-- -

-- ### Local Definitions
--
-- We can also define identifiers locally *which can only used inside a block*  
-- **Format:**  
--
--
--
-- ```haskell
-- name = expression  
--   where
--     name1 :: type1
--     name1 = expression1
--   
--     name2 :: type2
--     name2 = expression2   
--     ...
-- ```
--      
-- Note: Haskell can infer the types of identifiers, so name::type parts can be left out.

-- **Example**  
-- Calculate BMI of a person whose weight is 78 kg and height is 1.82 m

-- +
bmi :: Float
bmi = weight / (height * height)    -- we'll define weight and height inside where
  where
    weight = 78    -- Haskell infers the type of weight and height
    height = 1.82   

bmi
-- -

weight  -- locally-defined identifiers aren't accessible outside of their scope

-- ### Functions
--
-- **Definition:**  
--
-- ```haskell
-- functionName :: param1Type -> param2Type -> ... -> paramkType -> resultType
-- functionName param1 param2 ... paramk = result
-- ```
--
-- For example, you can call functions like this:  
-- `functionName p1 p2 p3` where p1, p2, and p3 represent parameters of the function
--

square :: Int -> Int      -- parameter is of type Int and result is also of Type Int
square x = x * x          -- parameter is x, and result is x^2

square 7

-- ### Local Function Definitions
--
-- You can define functions locally as well using *where* keyword

-- **Example**  
-- Let's write a function to calculate a<sup>3</sup> - b<sup>3</sup>

differenceOfCubes :: Int -> Int -> Int
differenceOfCubes a b = cube a - cube b
  where
    cube :: Int -> Int
    cube a = a * a * a

differenceOfCubes 5 3

-- ### Prefix vs Infix 
--
-- Functions can be called in infix format using backticks.  
--
-- For example you can call mod function using **mod 10 3** or **10 \`mod\` 3** 

mod 10 3

10 `mod` 3

mod 10 3 == 10 `mod` 3

-- You can also use operators in prefix format.  
-- All you need to do is put the operator inside paranthesis as follows:  *(/) 10 4* 

(/) 10 4

-- ### Conditional Statements
--
-- In order to write conditional statements, we can use *Guards*
--
-- A guard is a boolean expression to check some condition
--
-- **Format:**  
--
-- ```haskell
-- functionName :: p1Type -> p2Type -> ... -> pkType -> resultType
-- functionName p1 p2 .. pk
--   | guard1    = expression1
--   | guard2    = expression2
--   ...
--   | otherwise = expression
-- ```
--
-- Return value is the expression of the first guard whose value is True.  
-- If none of the guards is true, last expression (otherwise case) is returned.
--

maxOfTwo :: Int -> Int -> Int
maxOfTwo a b
  | a >= b     = a
  | otherwise  = b

maxOfTwo 3 7

-- We can also use if-then-else statements in order to write conditional statements.  
-- **Format**  
-- `if condition then x else y`

if 12 > 5 then "12 is larger than 5" else "12 is not larger than 5"

-- ### Error Messages
--
-- We can use *error* keyword to define error messages.

multiInverse :: Float -> Float
multiInverse a
  | a == 0    = error "Division by Zero!"
  | otherwise = 1 / a

multiInverse 5

multiInverse 0

StarcoderdataJupyterScriptsDedupFiltered