Zulip Chat Archive
Stream: mathlib4
Topic: Conveniently apply function if parameter is `some`
Thomas Murrills (Jan 31 2026 at 21:32):
Every so often I find myself in need of something like
def Function.apply? {α} {β} (param? : Option α) (f : α → β → β) (val : β) : β :=
match param? with
| some param => f param val
| none => val
i.e., apply a function if param? is some, and leave the value alone otherwise. It's mainly for code style, to allow something like f.applyIfSome p? val so that I don't need to write out the match and duplicate val, use (if let some p := p? then f p else id) val, or worse.
Does this wrapper exist somewhere? If not, should we have it? And if so, what should it be called, and what should it be dot notation on (Function or Option)? (This seems tied up in the name to me.)
(I think apply? is the best I've come up with so far, but I also considered applyIfSome, through, finallyWith? (but confusing if your value is actually monadic, in which case this is not related to finally)... Technically this is Option.foldr, but that seems quite bad for readability.)
Robin Arnez (Jan 31 2026 at 21:35):
Is param?.elim id f val fine?
Thomas Murrills (Jan 31 2026 at 21:37):
I consider using elim pretty unreadable for (meta) code I'd like to commit to mathlib. :) (Having a wrapper also lets us add documentation!)
Robin Arnez (Jan 31 2026 at 21:40):
Well, in do notation at least the cleanest solution would be
if let some x := x? then
val := f x val
Or is this for term mode?
Thomas Murrills (Jan 31 2026 at 21:42):
Having a one-off let mut like that is fine for normal values, but in my case(s) val is sometimes a monad action (such as the motivating one, behind the scenes :grinning_face_with_smiling_eyes:), and
let mut val := do
<code block>
...
if let some x := x? then
val := f x val
val
isn't great for readability...
Thomas Murrills (Jan 31 2026 at 21:43):
Whereas
(monadCombinator · foo).applyIfSome param? do
<code block>
...
isn't too bad.
Thomas Murrills (Jan 31 2026 at 21:53):
(Thinking about it, the same friction also occurs with regular Bools and f : β → β, so it might be nice to have a similar one for Bool. applyIf and applyIfSome, maybe?)
Robin Arnez (Jan 31 2026 at 22:02):
Now, if it were possible, what about
if flag then
with_cont cont => monadCombinator cont
<code block>
or
if let some x := x? then
with_cont cont => monadCombinator cont x
<code block>
Robin Arnez (Jan 31 2026 at 22:03):
(the do elaborator is getting a makeover anyways, so maybe there's something worth putting in an RFC here)
Thomas Murrills (Jan 31 2026 at 22:41):
Ooh, interesting! Hmm, I'd be a little concerned about the apparent nonlocality: visually, the action is happening underneath an if branch, but we pop out to the place that it affects. Unlike let mut, we wouldn't have a neon sign earlier in the code to watch out for that. (Or maybe we would?) I also think the naming would need some work; "continuation" isn't the most transparent term! But maybe it can be molded further, it would certainly be nice to bind a variable and take advantage of existing things like if and let...
Thomas Murrills (Jan 31 2026 at 22:44):
Robin Arnez said:
(the do elaborator is getting a makeover anyways, so maybe there's something worth putting in an RFC here)
Oh, nice! Are there any public discussions on potential feature requests for do? It might be nice to have a central place in which the community can mention ideas and assess their popularity; I've got a couple to toss into the ring. :)
Robin Arnez (Jan 31 2026 at 23:27):
I don't there is a feature discussion thread yet but we'll at least get do elaborator support, so if you want a feature you can implement it easily! This, together with the fact that the new do elaborator is continuation-based makes it pretty easy to implement a custom with_cont:
import Lean
open Lean Meta Elab Term
elab tk:"with_cont " fn:term : doElem <= dec => do
let combinator ← elabTerm fn none
let type ← inferType combinator
let .forallE _ t b _ ← whnfForall type | throwFunctionExpected combinator
let u := (← read).monadInfo.u
let cont ← withLetDecl dec.resultName (.const ``PUnit [u.succ]) (.const ``PUnit.unit [u.succ]) fun e => do
mkLetFVars #[e] (← dec.k)
let contTy ← inferType cont
unless ← isDefEq t contTy do
throwErrorAt tk "error"
let res := (← read).doBlockResultType
let monad := (← read).monadInfo.m
let expect := .app monad res
unless ← isDefEq b expect do
throwErrorAt tk "other error"
return combinator.beta #[cont]
def test (n : Nat) : ReaderM Nat Nat := do
for i in *...n do
with_cont withReader (· + 1)
read
#guard ((test 0).run 3).run == 3
#guard ((test 5).run 3).run == 8
(fyi this is on https://github.com/leanprover/lean4/tree/sg/newdo)
Robin Arnez (Feb 01 2026 at 09:35):
And of course, the real reason you'd want this:
/-- info: [a_0, a_1, a_2, a_3, a_4, a_5, a_6, a_7, a_8, a_9] -/
#guard_msgs in
run_meta
let mut vars : Array Expr := #[]
for i in *...10 do
let e ← with_cont withLocalDeclD ((`a).appendIndexAfter i) (mkConst ``Nat)
vars := vars.push e
Lean.logInfo m!"{vars}"
Last updated: Feb 28 2026 at 14:05 UTC