I don’t think this is accurate. I can at least suggest one alternative lowering (which is more or less how FLoops.jl works). Also, current do
is not completely non-disruptive since it changes how return
/continue
/break
/@goto
works/not work; although you probably meant non-disruptive in terms of scoping (since that’s what we are discussing). But let me shoehorn a “fix” for this, too, anyway. Consider:
s = 0
foreach(v) do x
s += x
s > 0 && return s # this is now `return` in outer scope
end
We can lower this to
# Defined in `Core`:
struct Return{T} <: Terminate
value::T
end
struct C{X}
x::X
end
function (c::C)(x)
s = c.s
s += x
s > 0 && return Return(s)
return C(s)
end
s = 0
c = foreach(C(s), v)
if c isa Return
return c.value
end
s = c.s
To support this, function that is intended to be used with the do
-block must follow the calling convention c = c(args...); c isa Terminate && return c
; i.e., foreach
is
function foreach(c, xs)
for x in xs
c = c(x)
c isa Terminate && return c
end
return c
end
Likewise, we can define Break <: Terminate
, Continue <: Terminate
and Goto <: Terminate
(how foreach
should handle Break
and Continue
is a bit of a discussion). Undef variables can also be handled using a special sentinel type.
The definition of open
is unchanged:
function open(c, name) # ignoring options
f = open(name)
try
return c(f)
finally
close(f)
end
end
Actually, it may be better to lower f(args.....) do
to (say) __do__(f, args..)
so that we won’t accidentally invoke incompatible functions.
Also, relying on the users to correctly invoke c = c(x); c isa Terminate && return c
may not be the best API. But we can easily put a syntax sugar on top of the functional API to make it hard to mis-define the API. This is pretty much how FGenerators.jl defines foldl
using @yield
.
A possible syntax sugar (not important)
For example, we can have a macro @do
such that
@do foreach(xs) begin
for x in xs
@yield x
end
end
lowers to
foreach(f, xs) = __do__(foreach, Stateless(f), xs) # "legacy" API
function __do__(::typeof(foreach), c, xs)
for x in xs
c = c(x)
c isa Terminate && return c
end
return c
end
where Stateless
is an appropriate callable wrapper:
struct Stateless{F}
f::F
end
function (c::Stateless)(args...)
c.f(args...)
return c
end