{"action":"created","issue":{"url":"https://api.github.com/repos/manastech/crystal/issues/391","labels_url":"https://api.github.com/repos/manastech/crystal/issues/391/labels{/name}","comments_url":"https://api.github.com/repos/manastech/crystal/issues/391/comments","events_url":"https://api.github.com/repos/manastech/crystal/issues/391/events","html_url":"https://github.com/manastech/crystal/issues/391","id":56550301,"number":391,"title":"yield introduced through macro is not respected in method signature","user":{"login":"jhass","id":141294,"avatar_url":"https://avatars.githubusercontent.com/u/141294?v=3","gravatar_id":"","url":"https://api.github.com/users/jhass","html_url":"https://github.com/jhass","followers_url":"https://api.github.com/users/jhass/followers","following_url":"https://api.github.com/users/jhass/following{/other_user}","gists_url":"https://api.github.com/users/jhass/gists{/gist_id}","starred_url":"https://api.github.com/users/jhass/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jhass/subscriptions","organizations_url":"https://api.github.com/users/jhass/orgs","repos_url":"https://api.github.com/users/jhass/repos","events_url":"https://api.github.com/users/jhass/events{/privacy}","received_events_url":"https://api.github.com/users/jhass/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/manastech/crystal/labels/bug","name":"bug","color":"fc2929"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":2,"created_at":"2015-02-04T16:29:55Z","updated_at":"2015-02-04T17:57:13Z","closed_at":null,"body":"```ruby\r\nmacro a\r\n  yield\r\nend\r\n\r\ndef b(&block)\r\n  a\r\nend\r\n\r\nb { puts \"hi\" } # works\r\n\r\ndef c\r\n  a\r\nend\r\n\r\nc { puts \"hi\" } # claims c doesn't take a block\r\n```"},"comment":{"url":"https://api.github.com/repos/manastech/crystal/issues/comments/72904196","html_url":"https://github.com/manastech/crystal/issues/391#issuecomment-72904196","issue_url":"https://api.github.com/repos/manastech/crystal/issues/391","id":72904196,"user":{"login":"asterite","id":209371,"avatar_url":"https://avatars.githubusercontent.com/u/209371?v=3","gravatar_id":"","url":"https://api.github.com/users/asterite","html_url":"https://github.com/asterite","followers_url":"https://api.github.com/users/asterite/followers","following_url":"https://api.github.com/users/asterite/following{/other_user}","gists_url":"https://api.github.com/users/asterite/gists{/gist_id}","starred_url":"https://api.github.com/users/asterite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/asterite/subscriptions","organizations_url":"https://api.github.com/users/asterite/orgs","repos_url":"https://api.github.com/users/asterite/repos","events_url":"https://api.github.com/users/asterite/events{/privacy}","received_events_url":"https://api.github.com/users/asterite/received_events","type":"User","site_admin":false},"created_at":"2015-02-04T17:57:13Z","updated_at":"2015-02-04T17:57:13Z","body":"There's a big, undocumented thing about Crystal regarding blocks: if you use the name of a `&block` argument, that **always** means the method will create a function/closure out of the block. Because of this, the compiler requires you to say the types of the function/closure (similar to how you need to say the type of a generic type).\r\n\r\nThe avoid this capture, you can invoke a method and yield again from inside the block:\r\n\r\n```crystal\r\ndef popen2\r\n  popen_run(1, 2) do |a, b|\r\n    yield a, b\r\n  end\r\nend\r\n\r\ndef popen_run(a, b)\r\n  yield a, b\r\nend\r\n\r\npopen2 { |a, b| } # works\r\n```\r\n\r\nYou might wonder why this is like this. Well, the problem is that when the compiler sees \"popen2 { |a, b| }\", how can it know if it needs to create a closure for the block or not? Right now the rule is simple: if the block name is used inside the method, it's a closure. Otherwise it's a regular block, and the compiler will always inline it, without creating a closure.\r\n\r\nNow, if you have:\r\n\r\n```crystal\r\ndef popen2(&block)\r\n  popen_run(1, 2, &block)\r\nend\r\n```\r\n\r\nDoes that method capture the block? Well, it depends on whether `popen_run` captures the block, but we only know that once we instantiate `popen2` and type it (do semantic checks), so it's kind of hard...\r\n\r\nWe still have a pending refactor related to how blocks work, which might happen or not. This will allow forwarding a non-captured block. But the syntax for block args will be a bit different (I don't remember the details right now).\r\n\r\nHowever, I think I like the current way. In Ruby you really don't care about this little detail, \"I just pass the block along and Ruby will understand\". Well, it seems Ruby works more or less the same way as we think about blocks: if you name them and use them (even if you just pass them along), Ruby probably creates a closure. Otherwise it just \"kind of inlines\" the call (the VM goes back to the calling method and continues there, probably). Here's the [proof](https://gist.github.com/asterite/7267571938a96989f3b1). (it's very curious that if you name the block but don't use it, it's even slower... maybe ruby has to translate that yield to invoking the block, so that makes it slower)\r\n\r\nSo in Crystal if you don't want to capture the block you must always use yield, and use it to \"pass along\" a non-captured block, and the compiler forces you to do that. And in that way you save yourself some runtime time.\r\n"}}