Generic returns
#3930
Replies: 1 comment 2 replies
-
Handling numerics generically and with a zero-cost abstraction is one of the primary motivating use cases of shapes. |
Beta Was this translation helpful? Give feedback.
2 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
I am basing this discussion off of older proposals of mine (#3050, #3577). I feel the motivation behind the proposal and the solutions could be better discussed here.
Motivation
Suppose you have your own system of numeric types. It makes sense for all of them to implement a common interface, let's say this:
These methods are all total - they can accept any argument (let's forget about division by zero for now).
How do you write a method that takes any number?
How do you write a method that returns any number? How do you use the result?
Solutions
There are situations when a generic object/value can be encountered that doesn't provide necessary operations that don't require knowing the actual/concrete type of it. At this point, there is no
INumber
that could be used in its stead.How can this be solved?
dynamic
.INumber
, implemented byINumber<T>
.Let's skip
dynamic
and discuss the remaining options:Base interface
Arguably the easiest option is to do something like this:
This works, but having to treat the number as boxed decreases performance. Also
Add
is not a total function anymore - you have to make sure you only add compatible number types.This is something that is employed in .NET as well: All arrays derive from
Array
, which allows handling the values asobject
, and contains similar checks for input. The same is also true forList<T>
implementingICollection
(with the same issues), butICollection<T>
does not implementICollection
, so you cannot be sure there if a cast succeeds.Deciding on the type
You can return a number as
object
/ValueType
, find the firstINumber<T>
it implements, extract theT
, and either branch or specialize based on its value. This is explored in detail in #3050.In this specific case, one would have:
This works well in general, but unless CLI provides instructions or tools for this kind of operation, it has to involve heavy reflection to work. I'd still like to be able to use this some day.
Consuming the type
This involves creating something akin to a "generic-Invoke delegate", a piece of code that itself has type parameters. Stemming from #3577, we could have:
This is the object-oriented way of solving this issue, but unless C# simplifies it, it is extremely cumbersome, since every operation must have an accompanying type. There are various degrees of assistance C# can provide, for example:
delegate<T>
here being the syntax for an anonymous method with a type parameter.I am going to pause at the last one and elaborate. While this syntax doesn't make it easy to obtain the returned type, I think it is more than an ugly hack.
await
was created for this purpose of flattening the code, so broadening its support in this sense isn't all that meaningless. There are lots of issues with this approach though, sinceawait
works based on a pattern that doesn't really allow generic results. And it also requires an awaiter, which is not optimal in this case.The method itself should be its own simple awaiter:
C# could translate
ProcessNumber()
(without the consumer argument, or with another keyword likeawait
) to constructing the necessary types and calling the method in the proper way. The result of the consumer would be anything that is returned after the call.Beta Was this translation helpful? Give feedback.
All reactions