{--
-- TalTech ITI0212
--
-- Functional Programming
--
-- week 3, 2023-02-13
--
-- Parameterized Types and Generic Functions
--}
module Lecture3
{-- Parameterized Type Families --}
-- We can create an inductive type of lists
-- with a given element type:
data NatList : Type where
-- a list of Nats is either empty:
EmpNat : NatList
-- or it has a first Nat followed by a list of Nats:
ExtNat : (head : Nat) -> (tail : NatList) -> NatList
data BoolList : Type where
EmpBool : BoolList
ExtBool : (head : Bool) -> (tail : BoolList) -> BoolList
-- This gets tedious pretty quickly
-- because we need a new list type
-- for each possible element type.
-- We can try making a "generic list" type
-- but what would be the type of its elements?
data GenList : Type where
EmpGen : GenList
ExtGen : (head : ?gen) -> (tail : GenList) -> GenList
-- In a dependently typed language types are data.
-- So we can write expressions that involve types.
-- Instead of being a type, we can make lists
-- a function that takes a type and produces a type.
-- We call this a *type constructor* or
-- *parameterized type (family)*.
data List' : (a : Type) -> Type where
Nil' : List' a
Cons' : (x : a) -> (xs : List' a) -> List' a
-- The List type constructor is in the standard library.
-- The Cons constructor is written (::).
-- There is syntactic sugar to write lists as
-- comma separated sequences of elements between brackets [].
{-- Generic Functions --}
-- Let's write a length function for Lists.
length_ex : (a : Type) -> List a -> Nat
length_ex a Nil = 0
length_ex a (x :: xs) = S (length_ex a xs)
-- The length function doesn't care about
-- the element type of the list; i.e.
-- the algorithm doesn't depend on the argument `a`.
-- Such functions are called *generic*.
-- We only need the `a` in order to specify the function type.
-- Indeed, it is redundant because as soon as we give
-- the function an argument list it will know
-- the list element type and thus what `a` must be.
{-- Implicit Arguments --}
-- An *implicit argument* is one that we don't provide
-- but instead expect Idris to figure out for itself.
-- We mark implicit arguments by surrounding them with braces {}.
length_ie : {0 a : Type} -> List a -> Nat
length_ie [] = 0
length_ie (x :: xs) = S (length_ie xs)
-- We indicate that a function is *generic* in an implicit argument
-- by putting a *quantity* 0 in front of it.
-- This is like using an underscore `_` for an explicit argument.
-- There is syntactic sugar for generic implicit arguments:
-- Idris assumes that any lowercase unbound variable
-- is a generic implicit argument and implicitly binds it for you.
length_ii : List a -> Nat
length_ii [] = 0
length_ii (x :: xs) = S (length_ii xs)
-- Idris will show explicit bindings for implicit arguments
-- at the REPL with the command `:ti`.
-- A generic function to concatenate lists:
concat' : List a -> List a -> List a
concat' [] ys = ys
concat' (x :: xs) ys = x :: (concat' xs ys)
{-- Pair Types --}
-- A parameterized type family for *ordered pairs*:
data Pair' : (a : Type) -> (b : Type) -> Type where
-- an element is a pair of both
-- an element of `a` and an element of `b`:
MkPair' : a -> b -> Pair' a b
-- Warning: the syntactic sugar (x , y) : (a , b)
-- means MkPair x y : Pair a b
-- Because the *projection functions* are generic,
-- there is only one thing they can do:
fst' : Pair a b -> a
fst' (x , y) = x
snd' : Pair a b -> b
snd' (x , y) = y
{-- Maybe Types and Partial Functions --}
-- Sometimes we want to write a function of type `a -> b`,
-- but there is not a reasonable result for some arguments:
bad_index : Nat -> List a -> a
bad_index i [] = ?stuck
bad_index 0 (x :: xs) = x
bad_index (S i) (x :: xs) = bad_index i xs
-- A type constructor for possibly missing data:
data Maybe' : (a : Type) -> Type where
-- an element is either an element of `a`:
Just' : a -> Maybe' a
-- or else it's a new element not in `a`:
Nothing' : Maybe' a
-- We can use Maybe types to express this:
index : Nat -> List a -> Maybe a
index i [] = Nothing
index 0 (x :: xs) = Just x
index (S i) (x :: xs) = index i xs
{-- Either Types --}
-- parameterized type family elements
-- from either one type or another:
data Either' : (a : Type) -> (b : Type) -> Type where
-- an element is either an element of `a`:
Left' : a -> Either' a b
-- or it's an element of `b`:
Right' : b -> Either' a b
-- Either types can be used to signal exceptions.
-- Return the first element of a list (if any):
head' : List a -> Either String a
head' [] = Left "error: empty list"
head' (x :: xs) = Right x