Zulip Chat Archive
Stream: new members
Topic: Self-referential let
Serena (Oct 19 2025 at 22:53):
Is there a way to do a self-referential let? I want to write this code, but it is complaining that arr is not defined inside the anonymous function.
let arr := Array.ofFn
fun (n' : Fin (n + 1)) =>
Thunk.mk fun _ => match n' with
| 0 => some []
| n' => shortest? <| coins.filterMap
fun (c : PNat) =>
if let some changeThunk := arr[n' - c.val]? >>= Thunk.get then
some <| c :: changeThunk
else none
Eric Wieser (Oct 19 2025 at 23:03):
let rec?
Serena (Oct 19 2025 at 23:07):
Eric Wieser said:
let rec?
If this is wrong please correct me -- I thought that let rec would create recursive definitions, in this case I only want 1 definition (self-referential), not recursive? Setting let rec gives an error because it can't show that it terminates.
Aaron Liu (Oct 19 2025 at 23:18):
what do you mean "self-referential"
Aaron Liu (Oct 19 2025 at 23:20):
I think I see what you want to do here
Aaron Liu (Oct 19 2025 at 23:20):
I'm not sure about all those thunks though
Anthony Wang (Oct 19 2025 at 23:20):
(deleted)
Eric Wieser (Oct 19 2025 at 23:21):
Can you make this a #mwe?
Aaron Liu (Oct 19 2025 at 23:22):
oh it's not possible to prove it terminates because it doesn't terminate
Aaron Liu (Oct 19 2025 at 23:23):
especially if one of the coins is zero
Anthony Wang (Oct 19 2025 at 23:23):
It seems like the coins have type PNat which rules out the possibility that one of the coins is 0.
Aaron Liu (Oct 19 2025 at 23:24):
oh I see
Aaron Liu (Oct 19 2025 at 23:24):
yeah but it still doesn't "terminate" in the sense that the termination checker wants you to prove
Serena (Oct 19 2025 at 23:25):
This is all of the code
def shortest? : List (List α) → Option (List α)
| [] => none
| a::as => some <| as.foldl (
fun (a : List α) (b : List α) => if a.length < b.length then a else b
) a
abbrev PNat := { n : Nat // n > 0 }
def findChange (coins : List PNat) : Nat -> Option (List PNat)
| 0 => some []
| n => shortest? <| coins.filterMap
fun (c : PNat) =>
if n >= c then
if let some change := findChange coins (n - c) then
some <| c :: change
else none
else none
def findChangeMemo (coins : List PNat) (n : Nat) : Option (List PNat) :=
let arr := Array.ofFn
fun (n' : Fin (n + 1)) =>
Thunk.mk fun _ => match n' with
| 0 => some []
| n' => shortest? <| coins.filterMap
fun (c : PNat) =>
if let some changeThunk := arr[n' - c.val]? >>= Thunk.get then
some <| c :: changeThunk
else none
arr[n].get
Regular version works fine, memo version does not
Aaron Liu (Oct 19 2025 at 23:26):
what happens if I swap out Array.ofFn for my Array.ofFn'? Does it still "terminate"?
def Array.ofFn' : type_of% @Array.ofFn :=
fun f => (Array.ofFn fun n => f n.rev).reverse
Anthony Wang (Oct 19 2025 at 23:47):
Since you're doing memoization, it might be easier to use a local imperatively function rather than thunks:
def shortest? : List (List α) → Option (List α)
| [] => none
| a::as => some <| as.foldl (
fun (a : List α) (b : List α) => if a.length < b.length then a else b
) a
abbrev PNat := { n : Nat // n > 0 }
def findChange (coins : List PNat) : Nat -> Option (List PNat)
| 0 => some []
| n => shortest? <| coins.filterMap
fun (c : PNat) =>
if n >= c then
if let some change := findChange coins (n - c) then
some <| c :: change
else none
else none
def findChangeMemo (coins : List PNat) (n : Nat) : Option (List PNat) := Id.run do
let mut arr := Vector.replicate (n + 1) none
arr := arr.set 0 <| some []
for h : i in [1:n + 1] do
arr := arr.set i <| shortest? <| coins.filterMap
fun c =>
if c.val ≤ i then
have : i < n + 1 := Membership.get_elem_helper h rfl
arr[i - c.val].map (c :: ·)
else
none
arr[n]
#eval findChange [⟨3, show 3 > 0 by grind⟩, ⟨5, show 5 > 0 by grind⟩, ⟨14, show 14 > 0 by grind⟩] 69
#eval findChangeMemo [⟨3, show 3 > 0 by grind⟩, ⟨5, show 5 > 0 by grind⟩, ⟨14, show 14 > 0 by grind⟩] 69
Serena (Oct 19 2025 at 23:54):
@Anthony Wang thanks, I was thinking about using do-notation but was still trying to figure it out. (I was thinking that thunks might be better to avoid mutability). Can I ask why you use Vector instead of Array? Vector isn't in the FPIL book afaik (or I missed it)
Anthony Wang (Oct 19 2025 at 23:55):
A vector has fixed length, so it makes the bounds check proofs go through.
Vlad Tsyrklevich (Oct 20 2025 at 06:36):
Serena said:
Vectorisn't in the FPIL book afaik (or I missed it)
It is equivalent to Vect from Chapter 7
Last updated: Dec 20 2025 at 21:32 UTC