Zulip Chat Archive
Stream: lean4
Topic: postfix monad arrow
James Gallicchio (Jun 27 2023 at 05:44):
have we discussed adding a way to monad-bind as a postfix operation? something like (x : m a).←
would work for me. can this be implemented as a macro on expressions, or would it require modifying core do notation?
Mario Carneiro (Jun 27 2023 at 05:46):
macros are expanded before expandLiftMethod
, so it should be possible to implement outside core
James Gallicchio (Jun 27 2023 at 05:48):
oh huge, let me see if the braindead macro implementation works
James Gallicchio (Jun 27 2023 at 05:53):
:thinking:
syntax term ".←" : term
macro_rules
| `($t.←) => `(← $t)
#eval show IO _ from do
IO.getStdin.←.flush -- invalid use of `(<- ...)`, must be nested inside a 'do' expression
return 0
James Gallicchio (Jun 27 2023 at 05:56):
from trace.Elab.do
:
with_annotate_term(Term.paren "(" (Term.proj (Term.paren "(" («term_.←» `IO.getStdin ".←") ")") "." `flush) ")")
Bind.bind✝ (((IO.getStdin.←).flush) : ?m✝ PUnit✝)
(fun (_ : PUnit✝) => with_annotate_term(Term.doReturn "return" [(num "0")]) Pure.pure✝ 0)
I'm not entirely sure how to parse this trace
Mario Carneiro (Jun 27 2023 at 05:57):
you can ignore the with_annotate_term(...)
parts, that is just for error messages
Mario Carneiro (Jun 27 2023 at 05:57):
it appears to have macro expanded the do
block without expanding the IO.getStdin.←
part first
Mario Carneiro (Jun 27 2023 at 05:58):
the invalid use of `(<- ...)`, must be nested inside a 'do' expression
error is what happens when you try to directly elaborate the expression <- e
Mario Carneiro (Jun 27 2023 at 05:58):
it is supposed to be eliminated as part of the macro expansion for the do
, which finds and replaces them
James Gallicchio (Jun 27 2023 at 05:59):
Right. so it seems like the do elaborator doesn't fully elaborate the inner expressions before eliminating arrows.
Mario Carneiro (Jun 27 2023 at 06:00):
I see this:
match (← liftMacroM <| expandMacro? doElem) with
| some doElem => doSeqToCode (doElem::doElems)
| none =>
match (← liftMacroM <| expandDoIf? doElem) with
| some doElem => doSeqToCode (doElem::doElems)
| none =>
let (liftedDoElems, doElem) ← expandLiftMethod doElem
...
but now I'm thinking that the first expandMacro?
line is only expanding doElem
s that are directly macro terms
James Gallicchio (Jun 27 2023 at 06:01):
I assume in the above example the (macroful) term is wrapped in a seq
doElem or whatever?
Mario Carneiro (Jun 27 2023 at 06:02):
a doExpr
(which embeds term
in doElem
)
Mario Carneiro (Jun 27 2023 at 06:02):
the case that would be handled here is something like repeat
or assert
, which are actually doElem
s that expand to other doElem
s
Mario Carneiro (Jun 27 2023 at 06:03):
expansion of <-
terms is done by expandLiftMethod
which is not at all user-extensible:
private def liftMethodDelimiter (k : SyntaxNodeKind) : Bool :=
k == ``Parser.Term.do ||
k == ``Parser.Term.doSeqIndent ||
k == ``Parser.Term.doSeqBracketed ||
k == ``Parser.Term.termReturn ||
k == ``Parser.Term.termUnless ||
k == ``Parser.Term.termTry ||
k == ``Parser.Term.termFor
private partial def hasLiftMethod : Syntax → Bool
| Syntax.node _ k args =>
if liftMethodDelimiter k then false
else if k == ``Parser.Term.liftMethod then true
else args.any hasLiftMethod
| _ => false
James Gallicchio (Jun 27 2023 at 06:05):
can we elaborate the term while it's still contained in a doExpr
and before expandLiftMethod
?
James Gallicchio (Jun 27 2023 at 06:06):
oh, but we'd have to explicitly elaborate in every term place in the do notation
Mario Carneiro (Jun 27 2023 at 06:06):
no, macro expansion happens from the outside in
Mario Carneiro (Jun 27 2023 at 06:06):
you could do it if you replaced do
(which you can do in this case without actually rewriting that whole file)
Mario Carneiro (Jun 27 2023 at 06:07):
(implementation aside, I think e.←
looks really silly so I hope you keep it to your personal projects :stuck_out_tongue: .)
James Gallicchio (Jun 27 2023 at 06:08):
hey, the syntax is up for debate, I just frequently want a way to do it in a pipe chain, and I'd be surprised if you don't feel the same urge to not have to make intermediary let binds just to monad bind :)
James Gallicchio (Jun 27 2023 at 06:09):
and directly calling bind
isn't ergonomic there either
James Gallicchio (Jun 27 2023 at 06:10):
x |> bind (.|>.foo)
looks so much worse than x.←.foo
James Gallicchio (Jun 27 2023 at 06:13):
This is a much spicier proposal: .!
. Then we can have e.g. (e : Except String Int).! + 5
in monadic contexts (a treat for the rustaceans)
Mario Carneiro (Jun 27 2023 at 06:17):
import Lean
open Lean
macro (name := postfixLift) x:term noWs "." noWs "←" : term => `(← $x)
macro (name := pipeLift) x:term:min "|>." noWs "←" : term => `(← $x)
syntax:arg (name := do') (priority := high) ppAllowUngrouped "do " doSeq : term
@[macro do'] def expandDo' : Macro := fun stx =>
stx.setKind ``Parser.Term.do |>.replaceM fun stx =>
if stx.getKind == ``postfixLift || stx.getKind == ``pipeLift then
Macro.expandMacro? stx
else
pure none
#eval show IO _ from do
IO.getStdin.←.flush
return 0
James Gallicchio (Jun 27 2023 at 06:35):
super cool. i'm assuming you still don't want it in Std?
Alex Keizer (Jun 27 2023 at 08:41):
James Gallicchio said:
x |> bind (.|>.foo)
looks so much worse thanx.←.foo
There's also (←x) foo
, right? A bit more parenthesis than the postfix version, but still better than explicit bind
or let
-bindings.
James Gallicchio (Jun 27 2023 at 16:04):
Yeah, the parentheses are what I don't like
James Gallicchio (Jun 27 2023 at 16:04):
let me find the snippets from my code
James Gallicchio (Jun 27 2023 at 16:09):
I just grepped and found this in Aesop, it's this sort of thing where I'd rather have postfix ←:
(← (← getRootGoal).get).traceTree .tree
James Gallicchio (Jun 27 2023 at 16:11):
let _ ← (← bdRef.get).1.markAllSols
is also a fun one :joy:
James Gallicchio (Jun 27 2023 at 16:11):
(from LeanSAT)
James Gallicchio (Jun 27 2023 at 16:11):
the arrows just get in the way for me when I'm reading this kind of code, since i have to jump back and forth from left to right instead of reading left to right continuously
James Gallicchio (Jun 27 2023 at 16:13):
it's obviously different when you're using normal (i.e. backwards to any good category theorist) function application and are reading right to left anyways
Last updated: Dec 20 2023 at 11:08 UTC