Zulip Chat Archive

Stream: mathlib4

Topic: List.mem


Kevin Buzzard (May 22 2021 at 19:48):

namespace List

def mem : α  List α  Prop
| a, [] => False
| a, h :: t => a = h  mem a t

end List

would work for me, but I'm not sure it's Lean 4 style. Judging by Lean 4 definition of List.length

def List.lengthAux {α : Type u} : List α  Nat  Nat
  | nil,       n => n
  | cons a as, n => lengthAux as (Nat.succ n)

def List.length {α : Type u} (as : List α) : Nat :=
  lengthAux as 0

they are taking tail recursion seriously. What is a fast implementation of List.mem? Where does one look to answer such a question? Of course if you go for speed you still have to prove List.length_cons right?
they are taking

Daniel Fabian (May 22 2021 at 20:06):

@Kevin Buzzard, since lean is implemented in lean, the core functions really want to be as fast as possible. Arrays are also used quite a lot. If you care mostly about proving things, you can probably be a bit less careful and focus on making proofs easier.

The core of lean functions were written with performance at mind, because it's quite important for the compiler itself. And given the size of some of the functions, and thus expressions, it does matter.

On the other hand, how much do you plan to rely on evaluation to prove things? Do you plan to prove things mostly by rfl, i.e. computation or not? It is my understanding, that in mathlib3, at least, computation doesn't really play much of a role. In fact, quite a lot of definitions are noncomputable, correct?

Kevin Buzzard (May 22 2021 at 20:39):

Yes, we do not rely too much on definitional equality "higher up the food chain", however for things like List it's a different story with lots of cheeky rfl abuse in mathlib3.

Kevin Buzzard (May 22 2021 at 20:40):

I've since found List.elem but it assumes BEq (lawless decidable equality)

Mario Carneiro (May 23 2021 at 03:14):

List.mem has been defined in Data.List.Basic, as well as List.length' and List.append' which use the "naive" implementations with better defeq rather than the tail recursive versions in the core library. (I'm still not sure how much we should make use of those functions over the ones in the core library for proving activities.)

François G. Dorais (May 23 2021 at 05:16):

Using implementedBy would be a convenient compromise:

def lengthImplAux : List α  Nat  Nat
  | List.nil, n => n
  | List.cons a as, n => lengthImplAux as n.succ

def lengthImpl (as : List α) : Nat := lengthImplAux as 0

@[implementedBy lengthImpl]
def length : List α   Nat
| [] => 0
| _::t => length t + 1

example (x : α) (xs : List α) : length (x::xs) = length xs + 1 := rfl -- works
example (x : α) (xs : List α) : lengthImpl (x::xs) = lengthImpl xs + 1 := rfl -- fails

However, as far as actual code generated, length and lengthImpl are the same: they both generate the more efficient tail recursive code.

Admittedly, every instance of implementedBy is a "trust me" statement, which doesn't look good in the core library! But, as a counterpoint, complicated code without any proof that it satisfies the expected equations is not trustworthy either.

Mario Carneiro (May 23 2021 at 06:04):

I agree that length' should use implementedBy length in mathlib, and if we can get it upstreamed length' should become length and length should become lengthImpl

Mario Carneiro (May 23 2021 at 06:06):

I don't think we should be stingy with implementedBy in the library since it is able to resolve the tradeoff between ease of proving and performance

Mario Carneiro (May 23 2021 at 06:07):

When we get the ability to do custom attributes, we can add a variant on implementedBy that requires a proof of equality, so that we can also avoid growing the compiler TCB


Last updated: Dec 20 2023 at 11:08 UTC