-- import Mathlib.Init.Set

Predicates

You've seen that in predicate logic, a proposition is a declarative statement that asserts that some state of affairs holds in some domain of discourse. For example, the natural number, four, is an even is a proposition. We've seen one way to formalize such a proposition using the mod operator.

4 % 2 = 0 : Prop
4: Nat
4
%
2: Nat
2
=
0: Nat
0

Families of Propositions

Indeed, there is an infinite family of propositions, all just like this on~`e except for the particular number we plug in instead of four. As another example, the natural number, five, is even is also a proposition. And there's one such proposition for each and every natural number.

We can write this family of propositions by abstracting the value, four, to a variable: e.g., the natural number, n, is even, where n can be any natural number. Now we have a predicate. Applying it to a specific number then returns a proposition about that number.

We could say that applying the predicate, the natural number, n, is even, to the specific number, four, returns the proposition, the natural number, four, is even.

In Lean and related logics, we represent a predicate as a function: from one or more parameter values to propositions. Here's our simple example reformulated.

def 
is_even: Nat → Prop
is_even
:
Nat: Type
Nat
Prop: Type
Prop
:= λ
n: Nat
n
=>
n: Nat
n
%
2: Nat
2
=
0: Nat
0
is_even 4 : Prop
is_even: Nat → Prop
is_even
4: Nat
4
0 = 0
is_even: Nat → Prop
is_even
4: Nat
4
is_even 5 : Prop
is_even: Nat → Prop
is_even
5: Nat
5

You can see that is_even is a predicate by checking its type. Indeed, it's a function from a natural number to a proposition about that number: namely that the given number mod two is zero. The type of our predicate is thus Nat → Prop.

is_even : Nat Prop
(
is_even: Nat → Prop
is_even
) -- Nat → Prop

Applying a Predicate to Arguments Yields a Proposition

Given a predicate we derive a proposition by applying it to one or more arguments of the specified types. The is_even predicate is appliable to a natural number as an argument. Here are two examples applying the is_even predicate.

is_even 4 : Prop
is_even: Nat → Prop
is_even
4: Nat
4
is_even 5 : Prop
is_even: Nat → Prop
is_even
5: Nat
5

Note that Lean reduced n%2 in each case to 0 or 1, leaving us with simpler propositions involving just 0 and 1.

To Satisfy a Predicate

We will say that specific parameter values satisfy a predicate if they yield a proposition that is true. In a sense, a proposition thus specifies a property (such as that of being even) that a value might or might not have. For example, four has the property of being even but five doesn't.

Predicates Specify Properties

In this way a predicate picks out the subset of parameter values with a specified property. As an example, we can make a list of natural numbers from 0 to 5, apply is_even to each, determine which resulting propositions are true, and thus pick out the natural numbers with the property of being even.

0 = 0
is_even: Nat → Prop
is_even
0: Nat
0
-- ✓
1 = 0
is_even: Nat → Prop
is_even
1: Nat
1
-- ×
0 = 0
is_even: Nat → Prop
is_even
2: Nat
2
-- ✓
1 = 0
is_even: Nat → Prop
is_even
3: Nat
3
-- ×
0 = 0
is_even: Nat → Prop
is_even
4: Nat
4
-- ✓
1 = 0
is_even: Nat → Prop
is_even
5: Nat
5
-- ×

Indeed, as we'll see in more depth shortly, we can understand the set of all objects having a particular property as those objects that satisfy the predicate that specifies the property. In Lean, we can specify the set of even numbers as follows, and evens here becomes nothing but a shorthand for is_even. We'll see more of this top when we get to lectures on set theory.

def 
evens: {x : Sort u_1} → {Set : x} → sorryAx (Sort u_2) true
evens
:
Set: ?m.355
Set
Nat := {
n: ?m.371
n
| is_even n }
sorryAx ?m.545 true
evens: {x : Sort ?u.537} → {Set : x} → sorryAx (Sort ?u.536) true
evens
4
sorryAx ?m.557 true
evens: {x : Sort ?u.549} → {Set : x} → sorryAx (Sort ?u.548) true
evens
5

Predicates of Multiple Arguments

Predicates can take any number of arguments. Here are some examples.

Ordered pairs of numbers and their squares

def 
square_pair: Nat × Nat → Prop
square_pair
:
Nat: Type
Nat
×
Nat: Type
Nat
Prop: Type
Prop
| (
n1: Nat
n1
,
n2: Nat
n2
) =>
n2: Nat
n2
=
n1: Nat
n1
^
2: Nat
2
1 = 1
square_pair: Nat × Nat → Prop
square_pair
(
1: Nat
1
,
1: Nat
1
) -- ✓
4 = 4
square_pair: Nat × Nat → Prop
square_pair
(
2: Nat
2
,
4: Nat
4
) -- ✓
9 = 9
square_pair: Nat × Nat → Prop
square_pair
(
3: Nat
3
,
9: Nat
9
) -- ✓
20 = 25
square_pair: Nat × Nat → Prop
square_pair
(
5: Nat
5
,
20: Nat
20
) -- × def
square_pairs: {x : Sort u_1} → {Set : x} → sorryAx (Sort u_2) true
square_pairs
:
Set: ?m.877
Set
(Nat × Nat) := {
p: Nat × Nat
p
:
Nat: Type
Nat
×
Nat: Type
Nat
| square_pair p }
sorryAx ?m.1064 true
square_pairs: {x : Sort ?u.1056} → {Set : x} → sorryAx (Sort ?u.1055) true
square_pairs
(3, 9)
sorryAx ?m.1076 true
square_pairs: {x : Sort ?u.1068} → {Set : x} → sorryAx (Sort ?u.1067) true
square_pairs
(3, 10)
sorryAx ?m.1115 true
(
3: Nat
3
,
9: Nat
9
)
square_pairs: {x : Sort ?u.1107} → {Set : x} → sorryAx (Type ?u.1079) true
square_pairs

Here it is again but with two arguments rather than one pair. This material is new relative to that presented in class. Take an extra few minutes to study the precise differences in syntax and sense between these two examples. In one, separate arguments are packed into pairs, whereas in the second, they're not. They're disaggregated.

def 
square_pair': Nat → Nat → Prop
square_pair'
:
Nat: Type
Nat
Nat: Type
Nat
Prop: Type
Prop
|
n1: Nat
n1
,
n2: Nat
n2
=>
n2: Nat
n2
=
n1: Nat
n1
^
2: Nat
2
1 = 1
square_pair': Nat → Nat → Prop
square_pair'
1: Nat
1
1: Nat
1
-- ✓
4 = 4
square_pair': Nat → Nat → Prop
square_pair'
2: Nat
2
4: Nat
4
-- ✓
9 = 9
square_pair': Nat → Nat → Prop
square_pair'
3: Nat
3
9: Nat
9
-- ✓
20 = 25
square_pair': Nat → Nat → Prop
square_pair'
5: Nat
5
20: Nat
20
-- × def
square_pairs': {x : Sort u_1} → {Set : x} → sorryAx (Sort u_2) true
square_pairs'
:
Set: ?m.1425
Set
(Nat × Nat) := {
p: Nat × Nat
p
:
Nat: Type
Nat
×
Nat: Type
Nat
| square_pair' p.1 p.2 }
sorryAx ({x : Sort u_1} {Set : x} sorryAx (Sort u_2) true) true
square_pairs: {x : Sort u_1} → {Set : x} → sorryAx (Sort u_2) true
square_pairs
sorryAx ?m.1649 true
(
3: Nat
3
,
9: Nat
9
)
square_pairs: {x : Sort ?u.1641} → {Set : x} → sorryAx (Type ?u.1613) true
square_pairs

When we specify multi-argument predicates our practice is to present the arguments one by one in disaggregated form. Among other things we can then more easily partially evaluate the function on any one of its actual parameters.

Pythagorean triples

def 
pythagorean_triple: Nat → Nat → Nat → Prop
pythagorean_triple
:
Nat: Type
Nat
Nat: Type
Nat
Nat: Type
Nat
Prop: Type
Prop
|
h: Nat
h
,
x: Nat
x
,
y: Nat
y
=>
h: Nat
h
^
2: Nat
2
=
x: Nat
x
^
2: Nat
2
+
y: Nat
y
^
2: Nat
2
25 = 25
pythagorean_triple: Nat → Nat → Nat → Prop
pythagorean_triple
5: Nat
5
4: Nat
4
3: Nat
3
def
py_trips: {x : Sort u_1} → {Set : x} → sorryAx (Sort u_2) true
py_trips
:
Set: ?m.2035
Set
(Nat × Nat × Nat) := {
t: ?m.2051
t
| t.1^2 = t.2.1^2 + t.2.2^2}
sorryAx ?m.2221 true
py_trips: {x : Sort ?u.2213} → {Set : x} → sorryAx (Sort ?u.2212) true
py_trips
(5,4,3)

Homework

(1) Define a predicate, ev_len_str, expressing the property of a string of being of an even-length.

-- Here

def 
ev_len_str: String → Prop
ev_len_str
:
String: Type
String
Prop: Type
Prop
|
s: String
s
=>
s: String
s
.
length: String → Nat
length
%
2: Nat
2
=
0: Nat
0
/- (2) Use #check to typecheck an expression for the set of all even length strings. -/ #check {
s: String
s
:
String: Type
String
| ev_len_str s } -- Here /- (3) Define a predicate, str_eq_len, applicable to any String value, s, and to any Nat value, n, that is satisfied just in those cases where s.length equals n. -/ def
str_eq_len: String → Nat → Prop
str_eq_len
:
String: Type
String
Nat: Type
Nat
Prop: Type
Prop
|
s: String
s
,
n: Nat
n
=>
s: String
s
.
length: String → Nat
length
=
n: Nat
n
-- Here /- (4) Define str_eq_lens : set String × Nat, to be the *set* of all ordered pairs, p = ⟨ s, n ⟩, such that n = s.length. -/ -- Here def
str_eq_lens: {x : Sort u_1} → {Set : x} → sorryAx (Sort u_2) true
str_eq_lens
:
Set: ?m.2407
Set
(String × Nat) := {
p: ?m.2423
p
| str_eq_len p.1 p.2} /- (5) Use "example" in Lean to state and prove the proposition that ⟨ "I love Logic!", 13 ⟩ ∈ str_eq_lens. -/ -- Here
example: sorryAx Prop true
example
:
Error: invalid constructor ⟨...⟩, expected type must be an inductive type ?m.2587
str_eq_lens: {x : Sort ?u.2598} → {Set : x} → sorryAx (Type ?u.2585) true
str_eq_lens
:=
rfl: ∀ {α : Sort ?u.2606} {a : α}, a = a
rfl
/- (6) Use "example" in Lean again to state and prove that ⟨ "I love Logic!", 1 ⟩ ∉ str_eq_lens. That's shorthand notation for ¬("I love Logic!", 1⟩ ∈ str_eq_lens. And you know what that means. -/ -- Here
example: ¬sorryAx Prop true
example
:
Error: invalid constructor ⟨...⟩, expected type must be an inductive type ?m.2725
str_eq_lens: {x : Sort ?u.2730} → {Set : x} → sorryAx (Type ?u.2723) true
str_eq_lens
:= λ (
t: sorryAx Prop true
t
:
Error: invalid constructor ⟨...⟩, expected type must be an inductive type ?m.2741
str_eq_lens: {x : Sort ?u.2752} → {Set : x} → sorryAx (Type ?u.2739) true
str_eq_lens
) =>
Error: missing cases: _
t: sorryAx Prop true
t

(7) Write a formal definition, in Lean, of party, as a set of objects of type Person. Make the Person type inhabited by giving it the single constructor, Person.jim. Hi, jim. Optionally use "structure" for this type, even if you don't know how to change the default constructor name, mk, to jim.

-- Here