-
Notifications
You must be signed in to change notification settings - Fork 1k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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
Proposal: never/bottom type #538
Comments
It would be really nice to be able to use never with variance. Then we could use IReadOnlyList as any type (or just class types?). This would, of course require CLR support. |
I don't understand how "never" could work in generic containers? Would |
That's the idea. It would represent an empty list. It would only be possible to use never in covariant/out positions, of course. |
Java manages the same without a "never" type. That only really works in the JVM due to generic type erasure and the lack of generics over primitives. With the CLR |
@orthoxerox
You could also add TError and make ResultValue specify never as that type. |
Well, you also have that pesky issue of classes not allowed to be variant, only delegates and interfaces. |
True. I'll make it an interface. |
|
I'm clearly missing something over your use of public sealed class ResultError : IResult<object>
{
public string ErrorMessage { get; }
} but - presumably - that isn't what you are trying to achieve in your example. |
In that case,
But as I stated before, the use of |
In your example, you have made public interface IResult<in T> { }
public sealed class ResultValue<T> : IResult<T>
{
// ...
public T Value { get; }
}
public sealed class ResultError : IResult<object>
{
public string ErrorMessage { get; }
}
_ = new List<IResult<string>> {new ResultError()}; But again, I guess I'm just missing the point. |
Switching to contravariant makes |
Oh! "A new type that has no instances and is a subtype of every other type". Got there in the end. Yes, I now get how it would work with covariant types. That's really nice: I could really use that! 😀 I'm not sure that |
Well, |
/cc @cston |
Since @DavidArno stumbled over this, maybe it would be a good idea to make it more explicit: A bottom type is a type which has
The bottom type represents a computation that doesn't return, e.g. an infinite loop, an error, an exit, etc. Contrast this with a unit type which has
The unit type represents a computation that returns nothing. (In many languages, the unit type is written simply as the 0-tuple type and the unit value is written as an empty tuple, and in fact, it is isomorphic to an empty tuple: an empty tuple conveys no useful information, and it has only one value, or, all empty tuples are the same and indistinguishable.)
Here's a question: what is the type of an empty list? In Haskell, an empty list has the polymorphic ("generic" in C♯) type So, second try: If we want to be able to assign the empty list to a list of strings, we need something that is a subtype of string. If we want to be able to assign the empty list to a list of integers, we need something that is a subtype of integers. And so on … we need something that is a subtype of In Scala, bottom is called ChangeLog: Sorry, @svick, @yaakov-h, @HaloFour for the confusion, I used |
@JoergWMittag Nice explanation! , |
@JoergWMittag Though it could (in theory, again no class can be variant) work with |
Why can't you have |
Variance in the CLR is enforced at the type level, not the method level. A |
@yaakov-h You could have Consider: List<nothing> listOfNothing = new List<nothing>();
List<MemoryStream> listOfStream = listOfNothing;
listOfStream.Add(new MemoryStream());
List<string> listOfString = listOfNothing;
Console.WriteLine(listOfString[0]); |
Can someone explain how having a bottom/never type would benefit the language and its users? |
|
@orthoxerox Are functions that are known not to return common enough that we'd need special language syntax to call them out? As for your #2, the compiler can model these branch expressions without the need of introducing new language keywords. |
@mattwar There are a number of such methods in the BCL, but that is not the principal motivation for this feature. It is more motivated by generics scenarios where there is a top type ( |
@gafter assume I'm a user of generics, and writing a generic method or class. How is a bottom type going to affect the code I write? Where does it show up? |
@mattwar: Assume you write some sort of immutable container. Think If you want a single value that you can assign to a reference of type class None : IOptional<nothing> {} where Similary, if you want to have a single empty list to which you can polymorphically prepend an Or, put it another way:
Actually, the whole point of this proposal is to make it possible for library authors to implement these abstractions so that you don't have to introduce "special language syntax" or "new keywords". We just want to add one type to the BCL, and one rule to the type system. Think about it this way: we do have "special language syntax" and a keyword because of the lack of a |
This comment has been minimized.
This comment has been minimized.
What on earth is that and how is it related to a bottom type? |
@juliusfriedman I've marked your comments as off topic as they appear to be entirely unrelated to the never type. If you want to put more detail in your comments than just a giant code blob with no explanation, feel free. Just ping me after they've been edited and I'll unhide them. |
A good use for this would be the ThrowHelper pattern, where you use helper functions to throw to improve codegen size and help inlining analysis. Because it's a function, it can't be used in all places where a throw expression can ( public static never ThrowArgumentException(string name, string message)
{
throw new ArgumentException(name, message);
}
var result = item switch
{
"hello" => 0,
42 => 1,
_ => ThrowHelper.ThrowArgumentException(nameof(item), "Invalid item")
}; Without the never type (using There is a workaround, which is making [DoesNotReturn]
public static T ThrowArgumentException<int>(string name, string message)
{
throw new ArgumentException(name, message);
}
var result = item switch
{
"hello" => 0,
42 => 1,
_ => ThrowHelper.ThrowArgumentException<int>(nameof(item), "Invalid item")
}; |
The Never type doesn't have to be a subtype of every other type. It should just be a type with no elements. Then there will be a generic function |
@gdar91 Implicit conversions are not enough. One of the main use cases for a bottom type is in generics (e.g. |
As we discussed lists, optionals I also want to point out that Something like this interface IChoice<out TLeft, out TRight> {}
record Left<T>(T Value) : IChoice<T, Never>;
record Right<T>(T Value) : IChoice<Never, T>;
IChoice<int, string> x = Left(1);
x = Right("Hello") Without Never what should the Right type be for |
The very big interest of a bottom type used in conjection with covariance is a variation of the If we have a type
The thing is that releasing a resource is by nature a best effort thing, if we can't release it, at some point we have to give up. Without a bottom type, we can't express the required infaillible behaviour, and we can't use the covariance to return |
The gurantee of being infaillible is the fact that the bottom type is impossible to instantiate, it is just a marker, what we call a |
Yes, actually the previous |
I don't know about for other people, but I often defer my exception throwing to other methods (that obviously never return), usually for the sake of ensuring that all exceptions are written in a standard style eg. (this is a simplified version of it, since I don't want to dig up the original code this example is from) private static void ThrowNullBufferException(string buffername)
{
throw new ArgumentNullException(buffername, $"'{buffername}' cannot be null or contain less than one element");
}
// then anywhere I'm validating a buffer
public static void SomeBufferRelatedMethod(object[] buffer)
{
if(buffer == null || buffer.Length < 1)
ThrowNullBufferException(nameof(buffer));
}
// or in some other cases where the object might not be a buffer, but follows similar rules
public static object SomeListRelatedMethod(IList<object> list)
{
if(list == null || list.Count < 1
ThrowNullBufferException(nameof(list));
} or in my current situation (why I even found this page), I have a number of methods that are guaranteed to throw (like in the previous examples) but they have additional actions that must be performed before the actual throwing can occur (logging the exceptions to a logfile). I'm not sure however that I like either 'bottom' or 'never' as the keyword, perhaps it might be clearer if the syntax was
|
@LupusInferni315 |
For me the big use-case for me for a But I am not a compiler programmer so I am likely wrong. |
That's true, but in the example I gave this would specifically apply to methods that always throw, not situations that enter an endless loop (since the 'endlessness' of a loop is hard to 'determine'), however; I had not considered methods that call |
@LupusInferni315 |
For me (who regularly uses functional languages) the real value of a This is particularly useful for things like With a proper option type, this should be possible:
|
@Richiban |
You're right, I shouldn't have used a value type in my example. I'll remove it for future reference |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
Previously discussed in dotnet/roslyn#1226.
A new type that has no instances and is a subtype of every other type can be introduced, called
never
orbottom
. It will be the official return type of thethrow
expression and will allow transformingreturn
,break
,continue
andgoto
into expressions (see #176). This will allow them in all expression contexts wherenever
is combined with another type (&&
,||
,??
,?:
) or discarded (expression statements). Instantiating variables/fields of typenever
should be impossible.It can either be implemented as a real CLR type (well, as real as
void
) or as a compiler trick (never
-returning methods are actuallyvoid
with aNeverReturnsAttribute
).The text was updated successfully, but these errors were encountered: