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

Semantics of block parameter's type restriction output type #10931

Open
straight-shoota opened this issue Jul 13, 2021 · 9 comments
Open

Semantics of block parameter's type restriction output type #10931

straight-shoota opened this issue Jul 13, 2021 · 9 comments

Comments

@straight-shoota
Copy link
Member

This is a continuation of the discussions from #10467 (comment) and https://forum.crystal-lang.org/t/difference-between-underscore-and-empty-as-block-return-value/2998 and the problems faced with #10905.

Currently, there are two special values for the output type of a block parameter:

  • -> Nil or -> (empty output type is equivalent to Nil)
  • -> _ (underscore)

Both effectively act as wildcards accepting any output type.

For a captured block parameter, Nil casts the output type to Nil, whereas _ passes through the actual return type. These semantics are well defined, make sense and there don't appear to be any issues. So I won't focus on captured block parameter types in this issue.

For a yielding block, the actual type is always passed through.
There should technically be no difference between the two, but there are subtle, unintended ones (see https://forum.crystal-lang.org/t/difference-between-underscore-and-empty-as-block-return-value/2998 and #10928) The compiler's behaviour causes that underscore output type can involuntarily break code. As of now, underscore output type is essentially broken. One should always use Nil for yielding block parameters as a workaround (#10928 and #10929 apply that to stdlib).

There's really no need to have both, Nil and _ output types with the same semantics.
For simplicity, there should only be one or the other if they mean the same.

Alternatively, we could port the nil-casting semantics of Nil output type to yielding blocks. Not sure if that's much useful by itself, but it would help improve the expressiveness of yielding type restrictions.

@beta-ziliani
Copy link
Member

I prefer the consistency of nil-casting semantics for Nil, although this might break some code.

@asterite
Copy link
Member

With non-captured blocks the return type is really not important, because there's no need to create a Proc for them. So, if a return type is omitted from a non-captured block, I chose to consider it as "I don't care about the return type". I don't think there's a use case where you'd want a non-captured block to have the Nil type.

For now we can fix the underscore issue (already fixed here: #10933 ). But I see no good reason to further change the semantic for non-captured blocks.

@asterite
Copy link
Member

Also nil-casting semantics is sometimes wrong for captured blocks. See #10911

@asterite
Copy link
Member

Actually, I guess we could try (for 1.2) to have nil-casting semantics for non-captured blocks, if there's no explicit return type, but I'm afraid that's going to be a big breaking change.

@straight-shoota
Copy link
Member Author

The benefit of different output type semantics is expressing intent. For example, Enumerable#each(& : T ->) shows that it really doesn't care about the output type. It is simply ignored.
On the other hand, Array#sort_by(& : T -> _) shows that the value is actually used but the exact type doesn't matter. It's essentially shorthand for using a free variable.

@straight-shoota
Copy link
Member Author

I fear this is going to be a big breaking change as well.

@beta-ziliani
Copy link
Member

Do you think there will be significant amount of code relying of the current behavior? As a user I would rely on the mentioned intention.

@straight-shoota
Copy link
Member Author

I really don't know. But we should just try it 🤷

@straight-shoota
Copy link
Member Author

It should be fairly unproblematic to change the semantics of -> Nil to introduce an explicit nil cast.
If that breaks code, the output type restriction is just wrong.

With -> this is more difficult because it has no explicit output type. The omitted output type implicitly works like -> _. Breaking that experience is more serious and I don't think we can reasonably accept such a change. So -> should continue to work without nil-casting (equivalent to -> _) for the time being.

We can consider deprecating omitted output type, removing it in 2.0 and/or maybe re-adding it again with same semantics of -> Nil (as a short cut). That should be subject to more consideration.

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

3 participants