-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
do blocks, anonymous functions, and control flow #1288
Comments
Another more subtle issue that I just encountered is this: reqs = parse_requires("REQUIRES")
Git.each_submodule(false) do pkg, path, sha1
reqs = [reqs,parse_requires("$path/REQUIRES")]
end There's more context, of course, and it occurs in a function body, but basically, this doesn't work because the inner assignment to |
As for the first problem: ha! have fun implementing that. The second problem I don't believe, because variable bindings are inherited by default. I get the following:
|
I honestly don't understand why we suddenly need these do blocks all over the place. Now you have the option of handling control flow in either the caller or the callee, and it's not clear which one to use. If we want to change control flow to be closure-based, we need to take a good 3 months to redesign a lot of things. |
Have fun implementing what? I wasn't proposing a particular behavior, just pointing out that this is a potential point of confusion that we should consider. The second one I observed, but apparently it only happens in the repl, not inside a function as I had claimed (the repl is where I observed it, but the code example was in a function body): julia> x = 1
1
julia> bar() do y
x = [x,y]
end
in anonymous: x not defined
in anonymous at no file:2
in bar at none:1 |
This is the same behavior we've had forever, where globals aren't overwritten inside functions by default. So perhaps do blocks are not just syntactic sugar for functions. In hindsight I can see I should not have added them so hastily. This is a good example of everything starting to fall apart once you start throwing out new features too quickly. |
The point of opening the issue was to discuss problems raised by do-block syntax. Honestly, I'm not entirely in love with it. Maybe it's a failed experiment, but if that's the case then we need another way to handle things like opening files with a guarantee that they'll get closed again. The fact that do-block syntax raises these issues — which it does in Ruby as well — may be a design smell. If so, let's figure out a better way to do it. There was the idea for regex matching of using a for loop, as in: for m = match(r"^(\w+)=(.*)$", line)
# handle match, doesn't execute if no match
end If there were else blocks on for loops, then you can also handle non-matching smoothly: for m = match(r"^(\w+)=(.*)$", line)
# handle match, doesn't execute if no match
else
# handle not matching
end I like this approach a lot for regex matching. It doesn't rely on a closure, inlines very nicely, and the else version relies on a generally useful feature that we wouldn't mind adding anyway. This approach, however, doesn't obviously help in the case of things like For what it's worth, I experimented in the package code with both using do-blocks and using coroutines (Task objects) to do complex iterations and I much prefer using task objects with a for loop. Sure, there's some overhead in switching tasks, but then again, there's overhead in using closures. The coroutines have the clear advantage of looking like control flow and actually just being control flow (so |
A couple of ideas... Have something like Add for f = open("file")
# use open file handle f
end
for d = cd("dir")
# do something in directory
end This kind of thing would arguably make Julia a for-loop-oriented language (which might not be a bad thing). |
These are good points. I do like for/else and while/else. They also play nice with henchmen unrolling, since both require copying the condition. You can simply make the first copy of the branch point to the else block instead of the end of the loop. |
So one obvious takeaway is that we ought to have for-else and while-else: #1289. |
Sort of like go's http://blog.golang.org/2010/08/defer-panic-and-recover.html
Sort of like python's
which translates roughly as:
|
Yes, both are very apt comparisons. More familiar names would, of course, be welcomed (although I'm not sure Go's choice of |
I realize that I like the
We could also consider adding named blocks so you can return from any block at any point, including nested loops etc. |
The trouble with that is that the whole point of the do block syntax is that it obscures what's really going on. It almost seems like we want a here doc syntax for function arguments... |
I'm not sure what that syntax proposal means without an example. Obscuring what's going on doesn't seem to work well, since it makes it hard to predict what happens in edge cases like return, break, and continue without running to the manual. |
It wasn't even a proposal, just saying that what you're describing with named blocks sounds like here documents. |
I'm into the idea of a with-like syntax. Of course, in Julia, there would be something like with f = open("file")
# do stuff with file handle f
end would get expanded into something like this: let f = open("file")
try
# do stuff with file handle f
catch err
witherr(f,err)
end
withok(f)
end The default It would also just be generally useful to have something like Go's defer mechanism or D's scope guards. For open/close, those are easy enough: f = open("file")
defer close(f)
# do stuff with file handle f But what about changing directories? The code for that is significantly more complicated since the robust implementation opens the current directory, keeps a file handle to it, and later uses |
From a "logical construction of software" perspective, having guards ... for/else while/else until/else are guarded block expressions of a kind ... makes designing bad path avoidance and direction good program flow easier. The more capable, concise, consistent and complete guard is enactable as gatekeeper and as guide. Software development is more efficient where tangle avoidance is given consistent expression. And it is just fine to enfold the capability, as occurs some with be-iteratively/else and do-currently/later. |
FYI the clear sense from your discussion is that Julia should ditch the do block and better solve a simpler (not less powerful, less complex and more enabling) intent. |
Yes, the basic issues with do blocks are:
Both of these mismatches cause problems. |
I use this:
It is in this file but i haven't pushed it yet.. I have also changed
Note this has an abstraction leak, it won't call
Maybe everything that can be done with a macro, should be? The dropped |
If we're re-entering a chaos period, can I nominate this issue as particularly in need of a decision? |
Agreed. |
related: #441 |
Decision: we will keep it as-is. The reason is that often you want |
The other part of this is that we recommend against using do blocks for iteration constructs, in favor of expressing all iteration using for loops. If you don't care too much about performance, you can use a task and get complex iteration behavior; if you do care about performance, you need to introduce an object that supports our start/done/next iteration protocol. Another thought was that it might be more suggestive of the fact that the do block body is an anonymous function if we required parens around the block arguments: listen("localhost", 2000) do (sock,status)
# do something with the socket
end |
while playing around with some other changes, I came across this syntax as a pretty close approximation to the do-else form (using match as an example): if match(r"egex", "egex") do m
println(m)
return :something
end === nothing
println(nothing)
end
function Base.match(f, re, str)
m = match(re, str)
m !== nothing && return f(m)
nothing
end |
Now that we have the do-block syntax, one is tempted to write things like this:
Of course, since the do-block syntax is just shorthand for passing an anonymous function to
cd
, this throws an error, rather than returning the contents of"$dir/$file"
. I would like to keep do-blocks as just a syntactic sugar over anonymous functions. Is there some way we can make these things work more conveniently, or is that just going to cause complications? It's also convenient to use do-blocks for loop-like constructs, at which point one often wants features likebreak
andcontinue
, which raises similar issues.The text was updated successfully, but these errors were encountered: