{--
-- ITI0212 Lecture week 6, 2021.03.01
--
-- Review of basic functional programming in Idris
--}
{--
-- Announcements:
--
-- * Configure your Git repo with your name,
-- it's hard to guess from the UID.
--
-- * Follow requested directory structure
-- and recall that names are case sensitive.
--
-- * Add a .gitignore for build artifacts
-- (*.ibc, *.ttc, *.ttm, _tmp*, etc.).
--
-- * Term Project description to be posted this week.
--}
module Lecture6
import Data.Fin
import Data.Vect
-- Inductive Data Types and their Constructors:
data Shape : Type where
Circle : (radius : Double) -> Shape
Rectangle : (width , height : Double) -> Shape
IsocTriangle : (base , height : Double) -> Shape
RegularPolygon : (sides : Nat) -> (length : Double) -> Shape
-- Recursive Functions:
area : Shape -> Double
area (Circle radius) = pi * radius * radius
area (Rectangle width height) = width * height
area (IsocTriangle base height) = (base * height) / 2
area (RegularPolygon sides length) =
let
θ = pi / cast sides
height = (1/2) * length / tan θ
in
cast sides * area (IsocTriangle length height)
{-
/---------\
/ \
/ \
| |
| ^ |
| /|\ |
\ / | \ /
\ / | \ /
\/___|___\/
-}
-- Type Constructors:
-- general form:
-- data Foo : -> Type where
-- : -> Foo
-- ...
-- Parameterized Types:
data Tree : Type -> Type where
Leaf : Tree a
Node :
(left : Tree a) -> (this : a) -> (right : Tree a) -> Tree a
-- Recursive Functions on Parameterized Types:
-- count the number of nodes in a tree:
size : Tree a -> Nat
size Leaf = 0
size (Node left this right) = S $ size left + size right
-- Parameters vs Indices:
-- A parameter is an argument to a type constructor
-- that we never analyse.
-- So we must treat it uniformly.
-- Parameters are usually elements of type Type.
-- Type constructors that take parameter types
-- are sometimes called "generic".
-- An index is an argument to a type constructor
-- that we may analyze.
-- Indices are usually elements of inductive types.
-- Type constructors that take index values
-- are sometimes called "dependent".
-- the set of natural numbers less than a given one:
-- (this is in the standard library as Data.Fin)
data BoundedNat : (index : Nat) -> Type where
Zero : BoundedNat (S n)
Succ : BoundedNat n -> BoundedNat (S n)
{-
Fin 0 Fin 1 Fin 2 Fin 3 Fin 4
SSSZ
SSZ SSZ
SZ SZ SZ
Z Z Z Z
0 1 2 3 4 ... : Nat
-}
-- Some type constructors take both parameters and indices:
-- (this is in the standard library as Data.Vect)
data SizedList : (size : Nat) -> (kind : Type) -> Type where
Nil : SizedList 0 a
Cons : a -> SizedList n a -> SizedList (S n) a
-- Type indices can give us more information
-- about what a function does.
-- This can help us to write correct programs.
concatVect : Vect m a -> Vect n a -> Vect (m + n) a
concatVect [] ys = ys
concatVect (x :: xs) ys = x :: concatVect xs ys
-- cf.
concatList : List a -> List a -> List a
concatList [] ys = ys
concatList (x :: xs) ys = x :: concatList xs ys
-- Higher-Order Functions
-- A higher-order function is a function
-- that trafficks in other functions.
-- It might take a function as an argument
-- or return a function as a result.
-- Higher-order fuctions let us turn "design patterns"
-- into actual programs.
-- Maybe mapping:
mapMaybe : (a -> b) -> Maybe a -> Maybe b
mapMaybe f Nothing = Nothing
mapMaybe f (Just x) = Just (f x)
-- List mapping:
mapList : (a -> b) -> List a -> List b
mapList f [] = []
mapList f (x :: xs) = f x :: mapList f xs
-- Maybe zipping:
zipMaybe : (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c
zipMaybe f Nothing y = Nothing
zipMaybe f (Just x) Nothing = Nothing
zipMaybe f (Just x) (Just y) = Just $ f x y
-- List zipping:
zipList : (a -> b -> c) -> List a -> List b -> List c
zipList f [] ys = []
zipList f xs [] = []
zipList f (x :: xs) (y :: ys) = f x y :: zipList f xs ys
-- Folds are a very general way to write functions
-- from an inductively defined type to any other type.
-- To write the type of the fold for an inductive type:
-- (1) Examine the types of its constructors:
-- [] : List a
-- (::) : a -> List a -> List a
-- (2) In each constructor replace the type itself
-- with a parameter type t:
-- n : t
-- c : a -> t -> t
-- (3) The fold is a function that takes one argument
-- for each such term replacing a constructor
-- and returns a function from the type being folded
-- to the parameter type:
foldList : (c : a -> t -> t) -> (n : t) -> List a -> t
-- To write the fold function itself:
-- (1) Case split on the argument of type being folded.
-- (2) For each constructor of the type
-- apply the corresponding constructor-replacing term,
-- and recurse on each subterm of the parameter type:
foldList c n [] = n
foldList c n (x :: xs) = c x (foldList c n xs)
-- Many function can be written as folds:
mapList' : (a -> b) -> List a -> List b
mapList' f = foldList (\ x , ys => f x :: ys) []
-- The IO type constructor is used to differentiate
-- * expressions, which get evaluated to a value from
-- * computations, which may perform actions
-- (i.e. interact with the world)
-- The run-time system defines some primitive computations
-- for performing IO actions.
greet : IO Unit
greet = putStrLn "hello, world!"
-- In order to run a computation and perform its actions
-- we must :exec or :compile it.
-- We build up compound IO computations using:
-- * a trivial computation (pure),
-- which performs no actions and returns a value,
-- * a computaion sequencing operator (>>=),
-- which performs the actions of its first computation
-- and passes the resulting value to its next computation.
meet_and_greet : IO Unit
meet_and_greet =
putStr "What's your name? " >>=
const getLine >>=
\ name => putStrLn ("Hello, " ++ name ++ "!")
-- There is syntactic sugar to write this
-- in an inperative-looking style:
meet_and_greet' : IO Unit
meet_and_greet' = do
putStr "What's your name? "
name <- getLine
putStrLn ("Hello, " ++ name ++ "!")