Zulip Chat Archive
Stream: new members
Topic: Cleanest way to compose arbitrary number of functions
Nicolò Cavalleri (Dec 28 2022 at 13:48):
Suppose n : ℕ
and that I have f : (fin n) → (α → α)
and a : α
. What is the cleanest way to write in Lean?
Arthur Paulino (Dec 28 2022 at 13:59):
I would write it like f $ g $ h x
Kevin Buzzard (Dec 28 2022 at 14:03):
That only works for n=3 though
Arthur Paulino (Dec 28 2022 at 14:05):
Ah, n
is an input of f
. I got confused because you wrote it subscripted in LaTeX (as if you had those functions at hand).
In Lean 4, I would foldl
on List.range n
Arthur Paulino (Dec 28 2022 at 14:15):
How do you provide an element of fin 0
though?
Henrik Böving (Dec 28 2022 at 14:25):
I would write something along the lines of
def composeN (f : Fin n → (α → α)) : α → α := Id.run do
match h1:n with
| 0 => id
| m + 1 =>
let mut fn := f 0
for h2:idx in [1:n] do
fn := f ⟨idx, by simp_all[(· ∈ ·)]⟩ ∘ fn
fn
the List.range
approach is annoying to work with because you would have to prove the bounds on the values inside List.range and I don't know whether we have built in lemmata for that. This works with a default Lean 4 installation.
Henrik Böving (Dec 28 2022 at 14:27):
Based on how you are writing the Fin
type here you are probably asking a Lean 3 quesiton though and I have no clue about that. There the List.range approachw ith something along the lines of
def composeN (f : Fin n → (α → α)) : α → α :=
let range := List.range n
range.foldl (init := id) (fun acc elem => f ⟨elem, sorry⟩ ∘ acc)
should work out but I have no clue about the things you have available.
Arthur Paulino (Dec 28 2022 at 14:28):
This question is related: https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/iterating.20over.20.60Fin.20n.60/near/314789036
Martin Dvořák (Dec 28 2022 at 14:30):
The following code works in Lean 3.
Unfortunately, I don't know how to typecast (fin 5) → (ℤ → ℤ)
to list (ℤ → ℤ)
.
import data.matrix.notation
def g : (fin 5) → (ℤ → ℤ) := ![(λ a, a - 1), (λ a, a * a), (λ a, a * a), (λ a, a / 2), (λ a, a + 1)]
def g' : list (ℤ → ℤ) := [(λ a, a - 1), (λ a, a * a), (λ a, a * a), (λ a, a / 2), (λ a, a + 1)]
def chain {α : Type*} (f : list (α → α)) : α → α := list.foldl (∘) id f
#eval (g 0) $ (g 1) $ (g 2) $ (g 3) $ (g 4) 9
#eval chain g' 9
Both #eval
s return 624.
Henrik Böving (Dec 28 2022 at 14:31):
def functions : Fin 3 → (Nat → Nat)
| 2 => h
| 1 => g
| 0 => f
example : composeN functions = h ∘ g ∘ f := by
simp[composeN, forIn', Std.Range.forIn', Std.Range.forIn'.loop, functions, Id.run]
formally verified :tm:
Eric Wieser (Dec 28 2022 at 15:08):
Martin Dvořák said:
Unfortunately, I don't know how to typecast
(fin 5) → (ℤ → ℤ)
tolist (ℤ → ℤ)
.
Eric Wieser (Dec 28 2022 at 15:10):
There might be a nice way to use docs#fin.cons_induction here
Martin Dvořák (Dec 28 2022 at 15:11):
I updates my solution above. Thanks!
Nicolò Cavalleri (Dec 28 2022 at 15:55):
Thanks to all
Nicolò Cavalleri (Dec 28 2022 at 15:56):
I am surprised this is not in mathlib yet though as I would have guessed somebody should have needed this at some point
Nicolò Cavalleri (Dec 28 2022 at 15:58):
I wonder if it should be pushed
Eric Wieser (Dec 28 2022 at 16:38):
@Martin Dvořák, why not list.foldr to avoid the need to reverse?
Eric Wieser (Dec 28 2022 at 16:41):
I doubt this is very useful to have in mathlib; if you're working frequently with functions from a type to itself, you can use docs#function.End
def comp_n (f : fin n → function.End α) := (list.of_fn f).prod
Martin Dvořák (Dec 28 2022 at 16:46):
Eric Wieser said:
Martin Dvořák, why not list.foldr to avoid the need to reverse?
I can do (list.of_fn f).reverse.foldr (∘) id
instead. But you probably meant something different, didn't you?
Martin Dvořák (Dec 28 2022 at 16:48):
(list.of_fn f).foldr (λ x, λ y, y ∘ x) id
Martin Dvořák (Dec 28 2022 at 16:49):
(list.of_fn f).foldl (λ x, λ y, y ∘ x) id
Martin Dvořák (Dec 28 2022 at 16:49):
These four functions seem (but I haven't really checked it) to do the same.
Martin Dvořák (Dec 28 2022 at 16:49):
Either I reverse the list, or I reverse the sides of the composition.
Martin Dvořák (Dec 28 2022 at 16:50):
There doesn't seem to be a difference for our purpose between these two:
@[simp] def foldl (f : α → β → α) : α → list β → α
| a [] := a
| a (b :: l) := foldl (f a b) l
@[simp] def foldr (f : α → β → β) (b : β) : list α → β
| [] := b
| (a :: l) := f a (foldr l)
Martin Dvořák (Dec 28 2022 at 16:52):
Eric Wieser said:
I doubt this is very useful to have in mathlib; if you're working frequently with functions from a type to itself, you can use docs#function.End
def comp_n (f : fin n → function.End α) := (list.of_fn f).prod
Why is it named after endomorphisms and not just general unary operations?
Martin Dvořák (Dec 28 2022 at 18:39):
Martin Dvořák said:
(list.of_fn f).foldl (λ x, λ y, y ∘ x) id
(list.of_fn f).foldl (flip (∘)) id
Last updated: Dec 20 2023 at 11:08 UTC