123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- ..
- ::
- {-# OPTIONS --rewriting --sized-types #-}
- module language.abstract-definitions where
- open import language.built-ins
- .. _abstract-definitions:
- ********************
- Abstract definitions
- ********************
- Definitions can be marked as abstract, for the purpose of hiding
- implementation details, or to speed up type-checking of other parts.
- In essence, abstract definitions behave like postulates, thus, do not
- reduce/compute. For instance, proofs whose content does not matter
- could be marked abstract, to prevent Agda from unfolding them (which
- might slow down type-checking).
- As a guiding principle, all the rules concerning ``abstract`` are
- designed to prevent the leaking of implementation details of abstract
- definitions. Similar concepts of other programming language include
- (non-representative sample):
- UCSD Pascal's and Java's interfaces and ML's signatures.
- (Especially when abstract definitions are used in combination with modules.)
- Synopsis
- --------
- * Declarations can be marked as abstract using the block keyword ``abstract``.
- * Outside of abstract blocks, abstract definitions do not reduce, they are treated as postulates,
- in particular:
- * Abstract functions never match, thus, do not reduce.
- * Abstract data types do not expose their constructors.
- * Abstract record types do not expose their fields nor constructor.
- * Other declarations cannot be abstract.
- * Inside abstract blocks, abstract definitions reduce while type checking definitions,
- but not while checking their type signatures.
- Otherwise, due to dependent types, one could leak implementation
- details (e.g. expose reduction behavior by using propositional
- equality).
- Consequently information from checking the body of a definition cannot leak
- into its type signature, effectively disabling type inference for abstract
- definitions. This means that all abstract definitions need a complete type
- signature.
- * The reach of the ``abstract`` keyword block extends recursively to
- the ``where``-blocks of a function and the declarations inside of a
- ``record`` declaration, but not inside modules declared in an
- abstract block.
- Examples
- --------
- Integers can be implemented in various ways, e.g. as difference of two
- natural numbers::
- module Integer where
- abstract
- ℤ : Set
- ℤ = Nat × Nat
- 0ℤ : ℤ
- 0ℤ = 0 , 0
- 1ℤ : ℤ
- 1ℤ = 1 , 0
- _+ℤ_ : (x y : ℤ) → ℤ
- (p , n) +ℤ (p' , n') = (p + p') , (n + n')
- -ℤ_ : ℤ → ℤ
- -ℤ (p , n) = (n , p)
- _≡ℤ_ : (x y : ℤ) → Set
- (p , n) ≡ℤ (p' , n') = (p + n') ≡ (p' + n)
- private
- postulate
- +comm : ∀ n m → (n + m) ≡ (m + n)
- invℤ : ∀ x → (x +ℤ (-ℤ x)) ≡ℤ 0ℤ
- invℤ (p , n) rewrite +comm (p + n) 0 | +comm p n = refl
- Using ``abstract`` we do not give away the actual representation of
- integers, nor the implementation of the operations. We can construct
- them from ``0ℤ``, ``1ℤ``, ``_+ℤ_``, and ``-ℤ``, but only reason about
- equality ``≡ℤ`` with the provided lemma ``invℤ``.
- The following property ``shape-of-0ℤ`` of the integer zero exposes the
- representation of integers as pairs. As such, it is rejected by Agda:
- when checking its type signature, ``proj₁ x`` fails to type check
- since ``x`` is of abstract type ``ℤ``. Remember that the abstract
- definition of ``ℤ`` does not unfold in type signatures, even when in
- an abstract block! To work around this we have to define aliases for
- the projections functions::
- -- A property about the representation of zero integers:
- abstract
- private
- posZ : ℤ → Nat
- posZ = proj₁
- negZ : ℤ → Nat
- negZ = proj₂
- shape-of-0ℤ : ∀ (x : ℤ) (is0ℤ : x ≡ℤ 0ℤ) → posZ x ≡ negZ x
- shape-of-0ℤ (p , n) refl rewrite +comm p 0 = refl
- By requiring ``shape-of-0ℤ`` to be private to type-check, leaking of
- representation details is prevented.
- Scope of abstraction
- --------------------
- In child modules,
- when checking an abstract definition,
- the abstract definitions of the parent module are transparent::
- module M1 where
- abstract
- x : Nat
- x = 0
- module M2 where
- abstract
- x-is-0 : x ≡ 0
- x-is-0 = refl
- Thus, child modules can see into the representation choices of their
- parent modules. However, parent modules cannot see like this into
- child modules, nor can sibling modules see through each others abstract
- definitions. An exception to this is anonymous modules, which share
- abstract scope with their parent module, allowing parent or sibling
- modules to see inside their abstract definitions.
- The reach of the ``abstract`` keyword does not extend into modules::
- module Parent where
- abstract
- module Child where
- y : Nat
- y = 0
- x : Nat
- x = 0 -- to avoid "useless abstract" error
- y-is-0 : Child.y ≡ 0
- y-is-0 = refl
- The declarations in module ``Child`` are not abstract!
- Abstract definitions with where-blocks
- --------------------------------------
- Definitions in a ``where`` block of an abstract definition are abstract
- as well. This means, they can see through the abstractions of their
- uncles::
- module Where where
- abstract
- x : Nat
- x = 0
- y : Nat
- y = x
- where
- x≡y : x ≡ 0
- x≡y = refl
|