Skip to content

Latest commit

 

History

History
393 lines (291 loc) · 10.5 KB

structures.org

File metadata and controls

393 lines (291 loc) · 10.5 KB

Structures

The structure command generates record types. They generalize the dependent product type by providing named fields. It can be viewed as a macro built on top of the inductive datatype provided by the Lean kernel. Every structure declaration introduces a namespace with the same name. The general form of a structure declaration is as follows:

structure <name> <parameters> <parent-structures> : Type :=
  <constructor> :: <fields>

Most parts are optional. Here is a small example

import logic

structure point (A : Type) :=
mk :: (x : A) (y : A)

Values of type point are created using point.mk a b, the fields of a point p are accessed using point.x p and point.y p. The structure command also generates useful recursors and theorems. Here are some of the constructions generated for the declaration above.

import logic

structure point (A : Type) :=
mk :: (x : A) (y : A)

-- BEGIN
check point
check point.rec_on       -- recursor
check point.induction_on -- recursor to Prop
check point.destruct     -- alias for point.rec_on
check point.x            -- projection/field accessor
check point.y            -- projection/field accessor
check point.eta          -- eta theorem
check point.x.mk         -- projection over constructor theorem
check point.y.mk         -- projection over constructor theorem
-- END

We can obtain the complete list of generated construction using the command print prefix.

import logic

structure point (A : Type) :=
mk :: (x : A) (y : A)

-- BEGIN
print prefix point
-- END

Here is some simple theorems and expressions using the generated constructions. As usual, we can avoid the prefix point by using the command open point.

import logic

structure point (A : Type) :=
mk :: (x : A) (y : A)

-- BEGIN
eval point.x (point.mk 10 20)
eval point.y (point.mk 10 20)

open point

example (A : Type) (a b : A) : x (mk a b) = a :=
x.mk a b

example (A : Type) (a b : A) : y (mk a b) = b :=
y.mk a b

example (A : Type) (a b : A) : y (mk a b) = b :=
!y.mk  -- let Lean figure out the arguments

example (A : Type) (p : point A) : mk (x p) (y p) = p :=
eta p
-- END

If the constructor is not provided, then a constructor named mk is generated.

import logic

namespace playground
-- BEGIN
structure prod (A : Type) (B : Type) :=
(pr1 : A) (pr2 : B)

check prod.mk
-- END
end playground

We can provide universe levels explicitly.

import logic

namespace playground
-- BEGIN
-- Force A and B to be types from the same universe, and return a type also in the same universe.
structure prod.{l} (A : Type.{l}) (B : Type.{l}) : Type.{max 1 l} :=
(pr1 : A) (pr2 : B)

-- Ask Lean to pretty print universe levels
set_option pp.universes true
check prod.mk
-- END
end playground

We use max 1 l as the resultant universe level to ensure the universe level is never 0 even when the parameter A and B are propositions. Recall that in Lean, Type.{0} is Prop which is impredicative and proof irrelevant.

We can extend existing structures by adding new fields.

import logic

-- BEGIN
structure point (A : Type) :=
mk :: (x : A) (y : A)

inductive color :=
red, green, blue

structure color_point (A : Type) extends point A :=
mk :: (c : color)
-- END

The type color_point inherits all the fields from point and declares a new one c : color. Lean automatically generates a coercion from color_point to point.

import logic
open num

structure point (A : Type) :=
mk :: (x : A) (y : A)

inductive color :=
red, green, blue

structure color_point (A : Type) extends point A :=
mk :: (c : color)
-- BEGIN
definition x_plus_y (p : point num) :=
point.x p + point.y p

definition green_point : color_point num :=
color_point.mk 10 20 color.green

eval x_plus_y green_point

-- Force lean to display implicit coercions
set_option pp.coercions true

check x_plus_y green_point

example : green_point = point.mk 10 20 :=
rfl

check color_point.to_point
-- END

The coercions are named to_<parent structure>. Lean always declare functions that map the child structure to its parents. We can request Lean to not mark these functions as coercions by using the private keyword.

import logic
open num

-- BEGIN
structure point (A : Type) :=
mk :: (x : A) (y : A)

inductive color :=
red, green, blue

structure color_point (A : Type) extends private point A :=
mk :: (c : color)

-- For private parent structures we have to use the coercions explicitly.
-- If we remove color_point.to_point we get a type error.
example : color_point.to_point (color_point.mk 10 20 color.blue) = point.mk 10 20 :=
rfl
-- END

We can “rename” fields inherited from parent structures using the renaming clause.

import logic

namespace playground
-- BEGIN
structure prod (A : Type) (B : Type) :=
pair :: (pr1 : A) (pr2 : B)

-- Rename fields pr1 and pr2 to x and y respectively.
structure point3 (A : Type) extends prod A A renaming pr1→x pr2→y :=
mk :: (z : A)

check point3.x
check point3.y
check point3.z

example : point3.mk 10 20 30 = prod.pair 10 20 :=
rfl
-- END

end playground

Structures can be tagged as a class. The class-instance resolution is used to synthesize implicit arguments marked with the [] modifier. Another difference is that the structure is an instance implicit argument for every projection. The idea is that the actual structure is inferred by Lean using the class-instance resolution.

import logic

structure has_mul [class] (A : Type) :=
mk :: (mul : A → A → A)

check @has_mul.mul

-- Since [s : has_mul A] is an instance implicit argument for has_mul.mul.
-- The operation has_mul.mul can be used as a binary operator.
infixl `*`   := has_mul.mul

section
  -- The structure s in the local context is used to synthesize
  -- the implicit argument in a * b
  variables (A : Type) (s : has_mul A) (a b : A)
  check a * b
end

When a structure is marked as a class, the functions mapping a child structure to its parents is also marked as an instance unless the private modifier is used. Moreover, whenever an instances of the parent structure is required, and instance of the child structure can be provided. In the following example, we use this mechanism to “reuse” the notation defined for the parent structure with the child structure.

import logic

structure has_mul [class] (A : Type) :=
mk :: (mul : A → A → A)

-- Since [s : has_mul A] is an instance implicit argument for has_mul.mul.
-- The operation has_mul.mul can be used as a binary operator.
infixl `*`   := has_mul.mul

structure semigroup [class] (A : Type) extends has_mul A :=
mk :: (assoc : ∀ a b c, mul (mul a b) c = mul a (mul b c))

section
  -- The structure s in the local context is used to synthesize
  -- the implicit argument in a * b
  variables (A : Type) (s : semigroup A) (a b : A)
  check a * b

  -- We can see what is going by asking Lean to display implicit
  -- arguments, coercions, and disable notation.
  set_option pp.implicit true
  set_option pp.notation false
  set_option pp.coercions true

  check a * b
end

Here is a fragment of the algebraic hierarchy defined using this mechanism. In Lean, we can also inherit from multiple structures. Moreover, fields with the same name are merged. If the types do not match an error is generated. The “merge” can be avoided by using the renaming clause.

import logic

structure has_mul [class] (A : Type) :=
mk :: (mul : A → A → A)

structure has_one [class] (A : Type) :=
mk :: (one : A)

structure has_inv [class] (A : Type) :=
mk :: (inv : A → A)

infixl `*`   := has_mul.mul
postfix `⁻¹` := has_inv.inv
notation 1   := has_one.one

structure semigroup [class] (A : Type) extends has_mul A :=
mk :: (assoc : ∀ a b c, mul (mul a b) c = mul a (mul b c))

structure comm_semigroup [class] (A : Type) extends semigroup A :=
mk :: (comm : ∀a b, mul a b = mul b a)

structure monoid [class] (A : Type) extends semigroup A, has_one A :=
mk :: (right_id : ∀a, mul a one = a) (left_id : ∀a, mul one a = a)

-- We can suppress := and :: when we are not declaring any new field.
structure comm_monoid [class] (A : Type) extends monoid A, comm_semigroup A

-- The common fields of monoid and comm_semigroup have been merged
print prefix comm_monoid

The renaming clause allow us to perform non-trivial merge operations such as combining an abelian group with a monoid to obtain a ring.

import logic

structure has_mul [class] (A : Type) :=
(mul : A → A → A)

structure has_one [class] (A : Type) :=
(one : A)

structure has_inv [class] (A : Type) :=
(inv : A → A)

infixl `*`   := has_mul.mul
postfix `⁻¹` := has_inv.inv
notation 1   := has_one.one

structure semigroup [class] (A : Type) extends has_mul A :=
(assoc : ∀ a b c, mul (mul a b) c = mul a (mul b c))

structure comm_semigroup [class] (A : Type) extends semigroup A renaming mul→add:=
(comm : ∀a b, add a b = add b a)

infixl `+` := comm_semigroup.add

structure monoid [class] (A : Type) extends semigroup A, has_one A :=
(right_id : ∀a, mul a one = a) (left_id : ∀a, mul one a = a)

-- We can suppress := and :: when we are not declaring any new field.
structure comm_monoid [class] (A : Type) extends monoid A renaming mul→add, comm_semigroup A

structure group [class] (A : Type) extends monoid A, has_inv A :=
(is_inv : ∀ a, mul a (inv a) = one)

structure abelian_group [class] (A : Type) extends group A renaming mul→add, comm_monoid A

structure ring [class] (A : Type)
  extends abelian_group A renaming
    assoc→add.assoc
    comm→add.comm
    one→zero
    right_id→add.right_id
    left_id→add.left_id
    inv→uminus
    is_inv→uminus_is_inv,
  monoid A renaming
    assoc→mul.assoc
    right_id→mul.right_id
    left_id→mul.left_id
:=
(dist_left  : ∀ a b c, mul a (add b c) = add (mul a b) (mul a c))
(dist_right : ∀ a b c, mul (add a b) c = add (mul a c) (mul b c))