Zulip Chat Archive

Stream: new members

Topic: Absence of termination not detected with Id.run do


Sébastien Boisgérault (Aug 05 2025 at 15:08):

Hi everyone!

I was pretty sure that Lean would blame me for not flagging the following function as partial but it doesn't.

def loop : Nat := Id.run do
  let mut n <- 0
  while true do
    n <- n + 1
  return n

Is there a simple explanation of why Lean "fails" to detect the absence of termination here?

Cheers!

Sébastien

Aaron Liu (Aug 05 2025 at 15:13):

You can include partial function inside other functions without having to mark the enclosing function as partial

Sébastien Boisgérault (Aug 05 2025 at 15:24):

Ah, interesting! Is there a way to detect all functions using partial functions then?

Eric Wieser (Aug 05 2025 at 15:28):

To elaborate on what Aaron said, while is always partial

Robin Arnez (Aug 05 2025 at 16:22):

#print opaques <your definition name> (import Batteries.Tactic.PrintOpaques) gives you a list of all opaque and partial definitions that a function depends on. Some of these might not be a opaque due to partial though (in particular e.g. IO operations) so if you want to know which of these are partials you can check whether <name>._unsafe_rec exists. Example:

import Batteries.Tactic.PrintOpaques

def loop : Nat := Id.run do
  let mut n <- 0
  while true do
    n <- n + 1
  return n

def testMe : IO Unit := do
  IO.println "hi"

/-- info: 'loop' depends on opaque or partial definitions: [Lean.Loop.forIn.loop] -/
#guard_msgs in
#print opaques loop

#print Lean.Loop.forIn.loop._unsafe_rec -- exists => is defined using `partial`

/-- info: 'testMe' depends on opaque or partial definitions: [IO.getStdout] -/
#guard_msgs in
#print opaques testMe

#print IO.getStdout._unsafe_rec -- doesn't exist => is opaque for other reasons

Kyle Miller (Aug 05 2025 at 16:52):

There's a tension between Lean as a system for mathematical reasoning and Lean as a programming language.

Currently while loops are mathematically opaque, but, when making executable code, the compiler will translate them into C that does the looping.

partial is a convenient way to create mathematically opaque definitions with a particular runtime specification. It doesn't really track whether functions terminate or not unfortunately.

Infinite loops in pure code are sort of an undefined behavior; the compiler is free to assume pure functions terminate, so for example the following #eval returns with 0.

def loop : Nat := Id.run do
  let mut n <- 0
  while true do
    n <- n + 1
  return n

def useloop : Nat :=
  let x := loop
  0

#eval useloop
-- 0

Interestingly, sometimes it's even possible to mathematically evaluate functions with while, provided they're sufficiently trivial:

def loop : Nat := Id.run do
  let mut n <- 0
  while true do
    n <- n + 1
  return n * 0

#reduce loop
-- 0

(The trick is that even though it looks like there's a data dependency, n * 0 is definitionally equal to 0, and then the whole thing can reduce to 0.)

Sébastien Boisgérault (Aug 05 2025 at 17:17):

Thanks all of you for your answers, that was very educational! :folded_hands:


Last updated: Dec 20 2025 at 21:32 UTC