Zulip Chat Archive

Stream: new members

Topic: Functional way to check if a value appears in the image


Nick Pilotti (Sep 18 2021 at 19:27):

To get some practice with Lean I am trying to write some of the basic definitions of model theory. I have defined a term and I am trying to write a function that returns whether a particular variable occurs in a term. To to this I will need to recursively call the function on the list of terms that follow a function symbol. What would be the most concise functional way to implement something like this with the way I have defined things?

inductive term
| var : ℕ → term
| cnst : L.const → term
| func (n : ℕ) : L.functions n → (fin n → term) → term

/-- If a variable occurs in a term -/
def occurs_in_term : ℕ → term L → bool
| n (var m) := if n = m then true else false
| _ (cnst c) := false
| _ (func _ _ t) := -- TODO: Want to check true for something in range the range of t

Mario Carneiro (Sep 18 2021 at 19:49):

#backticks

Mario Carneiro (Sep 18 2021 at 19:50):

#mwe

Mario Carneiro (Sep 18 2021 at 19:53):

import data.nat.basic

def sig : Type := sorry
variable (L : sig)
def sig.const : sig  Type := sorry
def sig.functions : sig    Type := sorry

inductive term
| var :   term
| cnst : L.const  term
| func (n : ) : L.functions n  (fin n  term)  term
open term

/-- If a variable occurs in a term -/
def occurs_in_term (x : ) : term L  bool
| (var m) := if x = m then true else false
| (cnst c) := false
| (func n _ t) := to_bool $  i, occurs_in_term (t i)

Mario Carneiro (Sep 18 2021 at 19:54):

@Nick Pilotti

Nick Pilotti (Sep 18 2021 at 20:30):

(deleted)

Nick Pilotti (Sep 18 2021 at 20:31):

Thank you, can you explain the meaning of to_bool $ ?

Nick Pilotti (Sep 18 2021 at 20:40):

open data.nat.basic

/-- Def 1.2.1. -/
structure language :=
(functions :   set char) (relations :   set char)

namespace language

/-- The type of constants in a given language. -/
@[nolint has_inhabited_instance] def const (L : language) := L.functions 0

/-- The language of number theory -/
def L_NT : language :=
  λ n,
  if      n = 0 then {'0'}
  else if n = 1 then {'S'}
  else if n = 2 then {'+', '⬝', 'E'}
  else {},
  λ n,
  if      n = 2 then {'<'}
  else {}⟩

def var : term L_NT := func 2 '+' (var 0) (cnst '0')

Nick Pilotti (Sep 18 2021 at 20:41):

I'm having another problem. Here I am trying to define a term but I get the error :

type mismatch at application
  func 2 '+'
term
  '+'
has type
  char
but is expected to have type
  (?m_1.functions 2)

Ruben Van de Velde (Sep 18 2021 at 20:41):

$ is function application with low precedence, so these lines are equivalent:

| (func n _ t) := to_bool $  i, occurs_in_term (t i)
| (func n _ t) := to_bool ( i, occurs_in_term (t i))

Nick Pilotti (Sep 18 2021 at 20:43):

Thanks, and to_bool will convert a Prop into a bool I'm assuming? Does Lean compute this by iterating over all of the possible values of i?

Mario Carneiro (Sep 18 2021 at 20:47):

Yes. There is a decidability instance that says that if \forall i : fin n, decidable (f i) then decidable (\forall i : fin n, f i), and it works by iterating over all the elements of fin n

Mario Carneiro (Sep 18 2021 at 20:47):

the to_bool uses that decidability instance to turn the proposition into a bool

Mario Carneiro (Sep 18 2021 at 20:48):

docs#nat.decidable_forall_fin

Nick Pilotti (Sep 18 2021 at 20:50):

Oh ok, I think I can see what decidable means now.

Mario Carneiro (Sep 18 2021 at 20:50):

Oops, except this is not correct for the given definition since you are looking for any occurrence; it should use \exists instead of \forall

Nick Pilotti (Sep 18 2021 at 20:50):

So in the forall case it will be decidable only if the domain in finite?

Mario Carneiro (Sep 18 2021 at 20:50):

there is another decidability instance for that, of course

Mario Carneiro (Sep 18 2021 at 20:50):

Yes

Nick Pilotti (Sep 18 2021 at 20:50):

Good catch thanks

Mario Carneiro (Sep 18 2021 at 20:51):

Well in some cases it might be decidable for other reasons, for example divisibility of integers is defined using an unbounded existential but it is still decidable because of the theorem a | b <-> b % a = 0

Nick Pilotti (Sep 18 2021 at 20:52):

But decidable essentially means that is an instance somewhere which defines an algorithm to computer the answer? Something like that?

Nick Pilotti (Sep 18 2021 at 20:52):

So in the case of divisibility the algorithm would be division with remainder

Nick Pilotti (Sep 18 2021 at 20:53):

And forall would be to iterate through all the finite possibilities

Mario Carneiro (Sep 18 2021 at 20:53):

right

Nick Pilotti (Sep 18 2021 at 20:54):

Thanks for the help, I knew there had to be an elegant way to do this without having to write a for loop

Nick Pilotti (Sep 18 2021 at 20:55):

Can I get help on why this gives a type error as well?

/-- Def 1.2.1. -/
structure language :=
(functions :   set char) (relations :   set char)

namespace language

/-- The type of constants in a given language. -/
@[nolint has_inhabited_instance] def const (L : language) := L.functions 0

/-- The language of number theory -/
def L_NT : language :=
  λ n,
  if      n = 0 then {'0'}
  else if n = 1 then {'S'}
  else if n = 2 then {'+', '⬝', 'E'}
  else {},
  λ n,
  if      n = 2 then {'<'}
  else {}⟩

def var : term L_NT := func 2 '+' (var 0) (cnst '0')

Mario Carneiro (Sep 18 2021 at 20:55):

to be fair, there is a for loop (ish) involved, it's just buried in a library

Nick Pilotti (Sep 18 2021 at 20:56):

right, I just didn't have to write one myself

Mario Carneiro (Sep 18 2021 at 20:56):

again, see #mwe

Nick Pilotti (Sep 18 2021 at 20:57):

sorry

Mario Carneiro (Sep 18 2021 at 20:57):

your example is not self contained so I don't know what error you are talking about

Nick Pilotti (Sep 18 2021 at 20:57):

yes thank you, i forgot to include the definition of term

Mario Carneiro (Sep 18 2021 at 20:58):

The definition of L_NT is not going to compute very well, which means that you probably won't be able to write things like cnst '0'

Mario Carneiro (Sep 18 2021 at 20:59):

if I had to guess I think you should be writing cnst \<'0', _\> where _ is a proof that '0' \in L_NT 0

Mario Carneiro (Sep 18 2021 at 20:59):

which is probably by simp [L_NT]

Mario Carneiro (Sep 18 2021 at 20:59):

same for the '+'

Nick Pilotti (Sep 18 2021 at 21:00):

import data.nat.basic

namespace first_order

structure language :=
(functions :   set char) (relations :   set char)

namespace language

/-- The type of constants in a given language. -/
def const (L : language) := L.functions 0

/-- The language of number theory -/
def L_NT : language :=
  λ n,
  if      n = 0 then {'0'}
  else if n = 1 then {'S'}
  else if n = 2 then {'+', '⬝', 'E'}
  else {},
  λ n,
  if      n = 2 then {'<'}
  else {}⟩

variable (L : language)

/-- Def 1.3.1. -/
inductive term
| var           :   term
| cnst          : L.const  term
| func (n : )  : L.functions n  (fin n  term)  term

open term

def var : term L_NT := cnst '0'

end language

end first_order

Mario Carneiro (Sep 18 2021 at 21:04):

import data.matrix.notation
...
def var : term L_NT :=
func 2 '+', by norm_num [L_NT]⟩
  ![var 0, cnst '0', by simp [L_NT, const]⟩]

Mario Carneiro (Sep 18 2021 at 21:04):

If I were you I would define zero, add etc to make this nicer

Mario Carneiro (Sep 18 2021 at 21:07):

instance : has_zero (term L_NT) := cnst '0', by simp [L_NT, const]⟩⟩
def succ (x : term L_NT) : term L_NT := func 1 'S', by norm_num [L_NT]⟩ ![x]
instance : has_add (term L_NT) := λ x y, func 2 '+', by norm_num [L_NT]⟩ ![x, y]⟩
instance : has_mul (term L_NT) := λ x y, func 2 '⬝', by norm_num [L_NT]⟩ ![x, y]⟩
instance : has_pow (term L_NT) (term L_NT) := λ x y, func 2 'E', by norm_num [L_NT]⟩ ![x, y]⟩

def var : term L_NT := var 0 + 0

Nick Pilotti (Sep 18 2021 at 21:08):

I see, thank you

Nick Pilotti (Sep 18 2021 at 21:08):

What purpose does norm_num serve here?

Mario Carneiro (Sep 18 2021 at 21:09):

It is like simp, but it also knows how to solve equations involving numerals like 2 = 0 <-> false which come up when reducing your if statement definition

Mario Carneiro (Sep 18 2021 at 21:09):

If you used a match then I think simp alone would get it

Nick Pilotti (Sep 18 2021 at 21:10):

Ok got it

Nick Pilotti (Sep 18 2021 at 21:10):

So basically what lean expects is an element and then a proof that element is of the correct type?

Nick Pilotti (Sep 18 2021 at 21:11):

Is that what the notation means?

Mario Carneiro (Sep 18 2021 at 21:11):

This happens because when you wrote L.const → term , that doesn't immediately typecheck because L.const is a set, not a type

Mario Carneiro (Sep 18 2021 at 21:11):

it gets promoted ("coerced") to a type using subtype

Mario Carneiro (Sep 18 2021 at 21:12):

which is the type of pairs of an element x and a proof h : x \in A

Nick Pilotti (Sep 18 2021 at 21:13):

So is a set char really just a subtype of char?

Mario Carneiro (Sep 18 2021 at 21:14):

it is the collection of all predicates on char

Mario Carneiro (Sep 18 2021 at 21:14):

like the powerset

Mario Carneiro (Sep 18 2021 at 21:14):

er, that's the type itself

Mario Carneiro (Sep 18 2021 at 21:14):

an element of the type is some particular predicate on char

Mario Carneiro (Sep 18 2021 at 21:14):

and to apply the predicate on a particular element you write x \in A

Mario Carneiro (Sep 18 2021 at 21:15):

so you could have also written that inductive without any coercions like this:

inductive term
| var           :   term
| cnst          :  x, x  L.const  term
| func (n : )  :  x, x  L.functions n  (fin n  term)  term

Mario Carneiro (Sep 18 2021 at 21:16):

and now cnst takes the arguments separately rather than as a pair

instance : has_zero (term L_NT) := cnst '0' (by simp [L_NT, const])⟩

Nick Pilotti (Sep 18 2021 at 21:17):

I see

Mario Carneiro (Sep 18 2021 at 21:17):

The more leanish way to do this would be to just have a bespoke inductive type family for L_NT.functions

Nick Pilotti (Sep 18 2021 at 21:18):

Can you show an example of that?

Nick Pilotti (Sep 18 2021 at 21:19):

I see now

def set (α : Type u) := α  Prop

Mario Carneiro (Sep 18 2021 at 21:21):

import data.matrix.notation

namespace first_order

structure language :=
(functions :   Type) (relations :   Type)

namespace language

/-- The type of constants in a given language. -/
def const (L : language) := L.functions 0

inductive L_NT_func :   Type
| zero : L_NT_func 0
| succ : L_NT_func 1
| add : L_NT_func 2
| mul : L_NT_func 2
| exp : L_NT_func 2

inductive L_NT_rel :   Type
| lt : L_NT_rel 2

/-- The language of number theory -/
def L_NT : language :=
  L_NT_func, L_NT_rel

variable (L : language)

/-- Def 1.3.1. -/
inductive term
| var           :   term
| cnst          : L.const  term
| func {n : }  : L.functions n  (fin n  term)  term

open term

instance : has_zero (term L_NT) := cnst L_NT_func.zero
def succ (x : term L_NT) : term L_NT := func L_NT_func.succ ![x]
instance : has_add (term L_NT) := λ x y, func L_NT_func.add ![x, y]⟩
instance : has_mul (term L_NT) := λ x y, func L_NT_func.mul ![x, y]⟩
instance : has_pow (term L_NT) (term L_NT) := λ x y, func L_NT_func.exp ![x, y]⟩

def var : term L_NT := var 0 + 0

end language

end first_order

Nick Pilotti (Sep 18 2021 at 21:23):

That looks nicer, thanks again

Mario Carneiro (Sep 18 2021 at 21:23):

By the way, I would also remove the cnst constructor and make it a definition like this:

inductive term
| var           :   term
| func {n : }  : L.functions n  (fin n  term)  term

def term.cnst {L : language} (c : L.const) : L.term := term.func c ![]
open term

Mario Carneiro (Sep 18 2021 at 21:23):

Otherwise, you will have cnst '0' and func '0' ![] and they are distinct

Nick Pilotti (Sep 18 2021 at 21:24):

What does ![] mean here?

Mario Carneiro (Sep 18 2021 at 21:25):

I'm using the notation from data.matrix.notation which makes it easy to write fin n -> A functions as if they were arrays

Mario Carneiro (Sep 18 2021 at 21:25):

I'm using them in add, succ etc as well

Nick Pilotti (Sep 18 2021 at 21:27):

Ok


Last updated: Dec 20 2023 at 11:08 UTC