
-- This module gives an introduction to the module system of Agda.

module Introduction.Modules where

---------------------------------------------------------------------------
-- Simple sub-modules
---------------------------------------------------------------------------

-- As mentioned in 'Introduction.Basics' each file contains a single top-level
-- module. This module can contain any number of sub-modules. A sub-module is
-- declared in the same way as the top-level module, except that its name is
-- not qualified.

module Numbers where

  -- The contents of the top-level module do not have to be indented, but the
  -- contents of a sub-module do.

  data Nat : Set where
    zero : Nat
    suc  : Nat -> Nat

-- Outside a module its contents can be accessed using the name of the module.

one : Numbers.Nat
one = Numbers.suc Numbers.zero

-- Of course, this would get very tedious after a while, so to bring the
-- contents of a module into scope you can use an 'open' declaration.

open Numbers

two : Nat
two = suc one

-- When opening a module it is possible to control what names are brought into
-- scope. The open declaration supports three modifiers :

--  using (x1; ..; xn)    only bring x1 .. xn into scope
--  renaming (x to y;..)  bring y into scope and make it refer to the name x
--                        from the opened module.
--  hiding (x1; ..; xn)   bring everything except x1 .. xn into scope

-- The using and hiding modifiers can be combined with renaming but not with
-- each other.

-- For example, this will bring the names z and s (and nothing else) into
-- scope as new names for zero and suc.
open Numbers using () renaming (zero to z; suc to s)

-- We can now pattern match on the renamed constructors.
plus : Nat -> Nat -> Nat
plus  z    m = m
plus (s n) m = s (plus n m)

---------------------------------------------------------------------------
-- 'private' and 'abstract'
---------------------------------------------------------------------------

-- Above we saw how to control which names are brought into scope when opening
-- a module. It is also possible to restrict what is visible outside a module
-- by declaring things 'private'. Declaring something private will only prevent
-- someone from using it outside the module, it doesn't prevent it from showing
-- up after reduction, or from it to reduce.
-- To prevent something from reducing (effectively hiding the definition) it
-- can be declared 'abstract'.

module Datastructures where

  private

    data List (A : Set) : Set where
      nil  : List A
      _::_ : A -> List A -> List A

    _++_ : {A : Set} -> List A -> List A -> List A
    nil       ++ ys = ys
    (x :: xs) ++ ys = x :: (xs ++ ys)

    reverse : {A : Set} -> List A -> List A
    reverse  nil      = nil
    reverse (x :: xs) = reverse xs ++ (x :: nil)

  -- Not making the stack operations abstract will reveal the underlying
  -- implementation, even though it's private.
  Stack : Set -> Set
  Stack A = List A

  emptyS : {A : Set} -> Stack A
  emptyS = nil

  push : {A : Set} -> A -> Stack A -> Stack A
  push x xs = x :: xs

  abstract

    -- An abstract datatype doesn't reveal its constructors
    data Queue (A : Set) : Set where
      queue : (front back : List A) -> Queue A  -- invariant : if the front is
                                                -- empty, so is the back

    -- Abstraction is contagious, anything that pattern matches on a queue must
    -- also be abstract.
    private

      -- make sure the invariant is preserved
      flip : {A : Set} -> Queue A -> Queue A
      flip (queue nil back) = queue (reverse back) nil
      flip q                = q

    -- these functions will not reduce outside the module
    emptyQ : {A : Set} -> Queue A
    emptyQ = queue nil nil

    enqueue : {A : Set} -> A -> Queue A -> Queue A
    enqueue x (queue front back) = flip (queue front (x :: back))

open Datastructures

testS = push    zero emptyS
testQ = enqueue zero emptyQ

