Option<T> does not support covariance
See original GitHub issueI was writing such library myself, but yours is much more complete so I was thinking that I can switch to yours. There is one issue though which prevents me from doing it (or I just missed something?). Option type (and I guess others as well) are not supporting covariance.
Please note that if B
is derived from A
then Option<B>
should be compatible with Option<A>
.
class A { public virtual string Greet() { return "A"; } }
class B: A { public override string Greet() { return "B"; } }
public static void DoGreet(Option<A> option)
{
option.Iter(o => Console.WriteLine(o.Greet()));
}
static void Main(string[] args)
{
var a = Prelude.Optional(new A());
var b = Prelude.Optional(new B());
DoGreet(a);
// DoGreet(b); does not compile
}
My solution was to make interface IOption<out T>
and use it to pass values around. The other advantage is that you can implement Map
, Bind
, etc as extensions methods therefore they would work on anything which supports quite minimal IOption
interface. Disadvantage being performance, of course.
Issue Analytics
- State:
- Created 7 years ago
- Comments:11 (7 by maintainers)
Top Results From Across the Web
Variance of Monad Transformers
A question that repeatedly pops up about Cats is why monad transformer types like OptionT and EitherT aren't covariant like their Option and ......
Read more >Why not set covariant as default when define subtype in ...
It means define trait Option[T] is same as trait Option[+T] . It's easy to consider val humanOpt: Option[Human] can point to a Option[Student] ......
Read more >Why doesn't C++ support covariance in STL containers like ...
So, the reason, STL containers are not covariant, is because C++ doesn't support that. Note that std::vector is mutable, so it cannot be...
Read more >Covariance Estimation
Covariance ::Options as the name implies is used to control the covariance estimation algorithm. Covariance estimation is a complicated and numerically sensitive ...
Read more >Covariance and Contravariance - Manual
Covariance allows a child's method to return a more specific type than the return type of its parent's method. Whereas, contravariance allows a...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
@bbarvish It’s not so much a dogmatic approach, just more in keeping with the functional style. Certainly interfaces and the OO style can sometimes have benefits, but what I’ve found with new projects is that my approach is:
Build a ‘schema’ of data types
These are very much like record-types in F#. Classes with nothing but
readonly
fields and no methods or properties. If you take a look at an answer I wrote on Stack Overflow about general purpose immutable classes in C# you’ll see what these look like.Where there are methods or properties, they have no external dependencies (to the class). So for example you may have a class called
Customer
, with aDateOfBirth
field, and perhaps a property calledAge
that calculates the age of the customer from theDateOfBirth
. But I wouldn’t have a method calledSaveToDb()
or something like that.The constructors of these classes validate that incoming data is sound. That means if you have a reference to an immutable data structure, it can never be in a broken state.
This ‘schema’ represents all of the data in your application, and you design it in a similar way to designing a RDBMS schema. It’s all about the shape of the data and its constraints, and keeping external dependencies (especially IO related) away. That means you can safely build your schema project and include the DLL in other projects without any pain-points. If you’re building a multi/micro-service system this is very useful.
Separate code and data
This is the one thing that the OO world holds onto like some badge of honour. But it just gets in the way nearly all of the time. Especially in this world where we’re constantly pushing data over the wire.
If every function is really a projection from
A
toB
(input arguments to return type), andA
andB
can only ever be in valid states (because they’re immutable and you validate in the constructor). Then you can start to create ‘islands’ of functionality (modules) that are related but separate from the data; rather than grouping all processing with the data.So let’s say you had a
Customer
data-type, you could then have:This is a slightly contrived and not super informative example (I’m flu’d up atm!). But hopefully you get the point. The data is public, immutable, non-corruptible. The functionality just maps from one state to another.
In the OO world this is considered an anti-pattern: Anaemic Domain Model, but not in the functional world. This is pretty much how modules and record types work in F#, and is similar in Haskell too.
I would definitely recommend this video by Rich Hickey. When you understand that the concept of time is poorly managed in OO then you’ll see the value of the approach above.
Dependency injection and IO
I don’t use and actively despise the dependency-injection frameworks that are out there. It’s horrible magic dust that is nearly never to help the architecture of the code or the programmers on the team, it’s nearly always to facilitate unit-tests. By using the approach above everything is unit-testable by default. The areas that aren’t are the classic problems of IO.
If you spend any time in Haskell you’ll see that they’ve put a lot of effort into ‘managing’ the IO problem to maintain referential transparency. IO often looks like this:
It’s next to impossible to test this correctly. In the Haskell world you could imagine something like this happening:
The
World
in its current state is passed in, and the function maps to a newWorld
with the changes in. Obviously that isn’t exactly how it works out, but it’s kind of the theory.It’s quite difficult to do this in C#, so I tend to find a pragmatic approach is best. That is:
Then unit-test the hell out of the pure bit. That’s where your logic is. This could be a web-request, a CRUD operation, a file-batch job, whatever. This is why I built the
LanguageExt.Process
library. It allows for a cluster operations like the above.If I do need dependency injection, then I use Funcs rather than interfaces, because that’s why functional programming is called what it is 😃
Get over the OO reuse/interface thing
Unless you’re writing a library like this, then it’s actually rare to need reusable code. The other myth is that by abstracting everything out to interfaces you can somehow in the future just replace the implementation and everything will ‘just work’. Unless you’re writing a file-system or something that needs multiple implementations of ‘a thing’ that needs to be hot-swapped, then interfaces are usually overkill, or they’re only there for dependency injection reasons. I manage an enormous (5 million LOC+) web-application, and we got caught in that abstraction trap, for very little gain. So mostly now I work with concrete data-types with module-like static functionality. The classic complaint that comes up is that it’s a refactoring nightmare if you need to change it. Actually it isn’t, with statically typed languages and a decent IDE, changes are found quickly. Even when interfaces are used, they tend to be tied closer to the underlying concrete implementation than you’d expect.
Conclusion
Func
This isn’t prescriptive, this is just what works for me (and I’m not looking for any arguments here)
I’d also take a look at fsharpforfunandprofit. It has a ton of tutorials on how to think functionaly.
@MiloszKrajewski As mentioned above it’s due to the types being structs to protect against
null
. If you’re programming in a more functional style you’d be using interfaces less anyway. Working with data-types that are just like product types in functional languages (immutable and with no methods attached), and static ‘module like’ classes I see this issue very rarely. I do understand it’s a pain point however.Introduction of
IOption<A>
won’t work for this library, it has its own problems, as @Hinidu says it turns your bi-state value into a tri-state value (becausenull
is available), which kills the entire reason for theOption
type. It also causes unnecessary boxing. And finally, even a minimal implementation ofIOption<A>
can’t be covariant or contravariant, because at the very least you’d want aMatch
function, which has both input and output arguments:So the best course of action is to map using
option.Map(x => x as A)
.