0

Background

As the Lean Reference's Instances for nested types example shows, in a function that implements a type class method for a certain type, one can already use the type class instance for that type itself (i.e., recursively) by making the function partial and defining a local instance inside the function (here called _):

inductive NatRoseTree where
  | node (val : Nat) (children : Array NatRoseTree)

partial def NatRoseTree.beq : (tree1 tree2 : NatRoseTree) → Bool
  | .node val1 children1, .node val2 children2 =>
    let _ : BEq NatRoseTree := ⟨NatRoseTree.beq⟩
    val1 == val2 &&
    children1 == children2

-- [subsequent global instance definition is omitted in the example,
--  but it's just BEq NatRoseTree := ⟨NatRoseTree.beq⟩ again]

Lean's instance search will find the _ instance and use it to make sense of the == expressions inside the function, even though no global BEq NatRoseTree instance has been defined yet.

The problem

I would like to apply the same technique to ForIn / ForM, so that I can iterate over values of a type with for ... in ... do inside that type's own ForM.forM definition.

For example, here is a simple "countdown" type with (pointless) recursive usage of for over a value of its own type in its forM implementation:

structure Countdown where
  val: Nat

partial def Countdown.forM
  [Monad m] (c : Countdown) (action : Nat → m PUnit) : m PUnit
:= do match c with
  | ⟨0⟩ => action 0
  | ⟨n⟩ =>

      -- ...
      -- This is where you would probably need to add
      -- a local `ForIn` instance to make `for` work
      -- ...

      action n
      for i in (⟨n-1⟩ : Countdown) do
        action i

instance : ForM m Countdown Nat := ⟨Countdown.forM⟩
instance : ForIn m Countdown Nat := ⟨ForM.forIn⟩

def main : IO Unit :=
  for x in (⟨3⟩ : Countdown) do
    IO.println s!"count: {x}"

What do I need to put at the indicated placeholder (and, potentially, change elsewhere in the code) to make this work?

Obviously, simply calling the forM function itself recursively instead of using an actual for loop doesn't count.

My attempts so far

I first tried naively applying the technique from the Lean Reference by creating local ForM and ForIn instances given Countdown.forM's own monad m:

      let _ := (⟨Countdown.forM⟩ : ForM m Countdown Nat)
      let _ := (⟨ForM.forIn⟩ : ForIn m Countdown Nat)

But that doesn't compile at all:

error: Main.lean:10:17: failed to synthesize
  ForM (StateT β✝ (ExceptT β✝ m)) Countdown Nat

So I tried to let the compiler infer the correct monads automatically instead by making them implicit:

      let {m'} := (⟨Countdown.forM⟩ : ForM m' Countdown Nat)
      let {m'} := (⟨ForM.forIn⟩ : ForIn m' Countdown Nat)

This then compiles just fine, but doesn't appear to actually run the for loop, only the preceding action (output is just count: 3). Upon closer inspection, it turns out that it segfaults, likely due to a stack overflow.

0

1 Answer 1

0

Compiler bug (Lean 4.22+)

After asking on the Lean Zulip, it turned out that the segfault is caused by a compiler bug (now filed as GitHub issue lean4#10925) present since Lean 4.22 and the solution above utilizing automatically inferred monads should have worked.

So, in non-buggy versions of the Lean compiler,

      let {m'} := (⟨Countdown.forM⟩ : ForM m' Countdown Nat)
      let {m'} := (⟨ForM.forIn⟩ : ForIn m' Countdown Nat)

is, in fact, a correct answer.

Workaround until the bug is fixed

Prior to asking on Zulip, I had boiled it down to an even more minimal example which also segfaulted when run, and found that at least a proximate cause of these segfaults is ForM.forIn being declared @[inline] (it wasn't the ultimate cause, as the minimal example from the GitHub issue mentioned shows by not involving any @[inline]).

We can get around this by defining our own ForM.forInNoInline whose sole purpose is to wrap ForM.forIn to prevent inlining and using that in the otherwise unmodified implicit-monad approach from above:

structure Countdown where
  val: Nat

def ForM.forInNoInline
  [Monad m] [ForM (StateT β (ExceptT β m)) ρ α]
  (x : ρ) (b : β) (f : α → β → m (ForInStep β)) : m β
:= ForM.forIn x b f

partial def Countdown.forM
  [Monad m] (c : Countdown) (action : Nat → m PUnit) : m PUnit
:= do match c with
  | ⟨0⟩ => action 0
  | ⟨n⟩ =>
      let {m'} := (⟨Countdown.forM⟩ : ForM m' Countdown Nat)
      let {m'} := (⟨ForM.forInNoInline⟩ : ForIn m' Countdown Nat)
      action n
      for i in (⟨n-1⟩ : Countdown) do
        action i

instance : ForM m Countdown Nat := ⟨Countdown.forM⟩
instance : ForIn m Countdown Nat := ⟨ForM.forIn⟩

def main : IO Unit :=
  for x in (⟨3⟩ : Countdown) do
    IO.println s!"count: {x}"

This then works as expected when run or evaluated:

count: 3
count: 2
count: 1
count: 0
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.