Skip to content
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

[RFC] break, next and return and blocks #420

Closed
jhass opened this issue Feb 16, 2015 · 6 comments
Closed

[RFC] break, next and return and blocks #420

jhass opened this issue Feb 16, 2015 · 6 comments

Comments

@jhass
Copy link
Member

jhass commented Feb 16, 2015

Currently the rules for those keywords are dramatically different depending on whether the block is captured or not.

With a not captured block

  • break exits from the calling method, the argument provides the methods return value
  • next exits from the block, the argument provides the blocks return value
  • return exits from the method the block is defined in

With a captured block

  • break is disallowed
  • next is disallowed
  • return exits from the block, the argument provides the blocks return value

Proposal

I think it would be more consistent to swap the meanings for next and return in the captured block case. This way, if the library decides to not capture the block or vice versa, less code on the caller side is affected. More importantly the caller doesn't need to think about whether the block is captured or not in order to understand what the return inside a block is doing.

@vendethiel
Copy link
Contributor

"return" escaping closures is the same in other languages, such as scala. That's why it's generally discouraged.

@jhass
Copy link
Member Author

jhass commented Feb 16, 2015

Maybe, that's not my point though. My point is that I shouldn't have to think about whether the block will be captured or not when I want to exit it early.

@asterite
Copy link
Member

@jhass I think what you propose makes total sense. For example this code:

def foo
  bar do
    return 1
  end
end

Without knowing what bar does, I read it and I associate that return with exiting foo. If bar doesn't capture the block, that's fine. But if it captures it, the meaning will suddenly change and I will probably have a bug in my code. If we change it to what you propose, I will get a compile-time error, knowing that the block was captured, and that I have to rethink the code.

/cc @waj, @bcardiff what do you think?

@asterite
Copy link
Member

@jhass What about inline procs?

f = ->{
  break # disallowed

  # Which of the following exists f?
  next
  return
}

@jhass
Copy link
Member Author

jhass commented Feb 19, 2015

Mmh, tricky. Ruby differentiates whether it's Proc.lambda? apparently.

# In Ruby
proc { break :a; :b }.call # LocalJumpError
proc { return :a; :b }.call # LocalJumpError
proc { next :a; :b }.call # :a
def foo; proc {  break :a; :b }.call; :c; end; foo # LocalJumpError
def foo; proc {  return :a; :b }.call; :c; end; foo # :a
def foo; proc {  next :a; :b }.call; :c; end; foo # :c
def foo(a) a.call; :c; end; foo proc { break :a; :b } # LocalJumpError
def foo(a) a.call; :c; end; foo proc { return :a; :b } # LocalJumpError
def foo(a) a.call; :c; end; foo proc { next :a; :b } # :c

-> { break :a; :b }.call # :a
-> { return :a; :b }.call # :a
-> { next :a; :b }.call # :a
def foo; -> {  break :a; :b }.call; :c; end; foo # :c
def foo; -> {  return :a; :b }.call; :c; end; foo # :c
def foo; -> {  next :a; :b }.call; :c; end; foo # :c
def foo(a) a.call; :c; end; foo -> { break :a; :b } # :c
def foo(a) a.call; :c; end; foo -> { return :a; :b } # :c
def foo(a) a.call; :c; end; foo -> { next :a; :b } # :c

So the lambda is very close to an anonymous function but retains the block semantics of break and next too. Since in Crystal the literal is even closer to an anonymous function, I think it would be fair to disallow next and break and only allow return to exit the function itself.

However that means Crystal would need to differentiate the -> { } literal and a captured block def foo(&block), which I'm not sure it does atm. This also raises the question on what to do in the foo(&-> { }) case, I'd argue to retain the -> { } semantics since that's what the caller sees and knows.

So I'm not 100% sure, what do you think?

PS: I think we also should work out proper names for the closures, so far I use "block" for def foo; yield; end; foo { }, and captured block for def foo(&block); end, how about "anonymous function" for -> { }?

@ozra
Copy link
Contributor

ozra commented Oct 13, 2015

What is the status on this one. I'm glad it will be changed to be consistent to the users intention (if I didn't misunderstand).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants