{"action":"created","issue":{"url":"https://api.github.com/repos/crystal-lang/crystal/issues/7157","repository_url":"https://api.github.com/repos/crystal-lang/crystal","labels_url":"https://api.github.com/repos/crystal-lang/crystal/issues/7157/labels{/name}","comments_url":"https://api.github.com/repos/crystal-lang/crystal/issues/7157/comments","events_url":"https://api.github.com/repos/crystal-lang/crystal/issues/7157/events","html_url":"https://github.com/crystal-lang/crystal/issues/7157","id":388310106,"node_id":"MDU6SXNzdWUzODgzMTAxMDY=","number":7157,"title":"Signature of yielding methods","user":{"login":"straight-shoota","id":466378,"node_id":"MDQ6VXNlcjQ2NjM3OA==","avatar_url":"https://avatars0.githubusercontent.com/u/466378?v=4","gravatar_id":"","url":"https://api.github.com/users/straight-shoota","html_url":"https://github.com/straight-shoota","followers_url":"https://api.github.com/users/straight-shoota/followers","following_url":"https://api.github.com/users/straight-shoota/following{/other_user}","gists_url":"https://api.github.com/users/straight-shoota/gists{/gist_id}","starred_url":"https://api.github.com/users/straight-shoota/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/straight-shoota/subscriptions","organizations_url":"https://api.github.com/users/straight-shoota/orgs","repos_url":"https://api.github.com/users/straight-shoota/repos","events_url":"https://api.github.com/users/straight-shoota/events{/privacy}","received_events_url":"https://api.github.com/users/straight-shoota/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":2,"created_at":"2018-12-06T17:07:21Z","updated_at":"2018-12-06T17:56:14Z","closed_at":null,"author_association":"CONTRIBUTOR","body":"There are a few problems with signatures of yielding methods, which matter especially for API documentation.\r\n\r\nTwo methods with the same name and arguments can still be different if one of them yields and thus requires to be called with a block:\r\n```cr\r\ndef foo(bar : Bar)\r\nend\r\n\r\ndef foo(bar : Bar)\r\n  yield\r\nend\r\n```\r\nHowever, both methods would appear in the API docs as `#foo(bar : Bar)`. There is no distinction whether a block is expected.\r\nDetails can obviously be stated in the description, but that's less prominent and not always available.\r\n\r\n**The fact whether a method yields is part of it's signature and should be represented in the API docs.**\r\n\r\nTo make the yielding nature visible, such methods are often declared with a block argument:\r\n\r\n```cr\r\ndef foo(bar : Bar, &block)\r\n  yield\r\nend\r\n```\r\n\r\nThis is already an improvement over no distinction in the method signature. But this creates a new issue: The block argument is really just a dummy and not actually used by the method. This make it impossible to distinguish a yielding method (inlining a block with `yield`) from a method receiving an *actual* block argument (calling `block.call` on a proc/captured block). These are not interchangeable and thus need to be distinguishable.\r\n\r\nBecause it's just a dummy, the compiler doesn't even validate the type of the block argument, so you could declare `&block : (Foo, Bar) -> Baz` and it wouldn't even complain if the types don't fit. This could easily result in incorrect documentation in a place where you would expect the compiler to notify of any errors.\r\n\r\nIn order to solve this, we need a designation for yielding methods which distinguishes it from a block argument. It should be automatically applied by the docs generator but allow for manual specification to allow defining expected types.\r\n\r\nMy suggestion for this would be a single ampersand `&`. Alternatively, it could be followed by an underscore `&_` which represents \"a block argument bound to an unused variable\".\r\n\r\nA type declaration could follow just as with a regular block argument and would look like this:\r\n```cr\r\ndef foo(bar : Bar, & : Bar -> Foo) : Foo\r\n  yield bar\r\nend\r\n```\r\nThis uses a `Proc` type declaration while there is no actual `Proc` involved in a yielding method, but I think that's acceptable as long as it is explicitly distinguished from a block argument.\r\n\r\nIt would be a great enhancement if the compiler could verify that the declared types match the applied block signatures. But I guess that might be a little bit tricky.\r\n\r\nIf no types are declared, the docs generator would add a single `&` to the end of the method signature.\r\nThe arity (number of block arguments) could also be expressed as a number directly following the `&`, such that the method from the first example would be formatted as `#foo(bar : Bar, &1)`.\r\n\r\nDeclaring a real block argument (`&block`) on a yielding method should be a syntax error."},"comment":{"url":"https://api.github.com/repos/crystal-lang/crystal/issues/comments/444966691","html_url":"https://github.com/crystal-lang/crystal/issues/7157#issuecomment-444966691","issue_url":"https://api.github.com/repos/crystal-lang/crystal/issues/7157","id":444966691,"node_id":"MDEyOklzc3VlQ29tbWVudDQ0NDk2NjY5MQ==","user":{"login":"asterite","id":209371,"node_id":"MDQ6VXNlcjIwOTM3MQ==","avatar_url":"https://avatars0.githubusercontent.com/u/209371?v=4","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":"2018-12-06T17:56:14Z","updated_at":"2018-12-06T17:56:14Z","author_association":"CONTRIBUTOR","body":"The logic is here:\r\n\r\nhttps://github.com/crystal-lang/crystal/blob/30212064383ca791d8c70dc4daac2b655cafb326/src/compiler/crystal/tools/doc/method.cr#L145-L152\r\n\r\nIf there's an explicit block arg then it's printed. If not, but the method yields (this is the case you care about) a `&block` is appended."}}
{"actor":{"display_login":"asterite"}}