question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Option<T> does not support covariance

See original GitHub issue

I 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:closed
  • Created 7 years ago
  • Comments:11 (7 by maintainers)

github_iconTop GitHub Comments

4reactions
louthycommented, Oct 17, 2016

@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 a DateOfBirth field, and perhaps a property called Age that calculates the age of the customer from the DateOfBirth. But I wouldn’t have a method called SaveToDb() 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 to B (input arguments to return type), and A and B 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:

    public static class CustomerBilling
    {
        public static Invoice CreateInvoice(Customer c) 
        {
            // code to generate an empty invoice with pre-populated customer data
        }
    }

    public static class CustomerAccount
    {
        public static Customer SetContactOptions(Customer c, Option<string> phone, Option<string> email)
        {
            return c.With(Phone: phone, Email: email);
        }
    }

    public static class CustomerDatabase
    {
        public static Option<int> Write(Customer c) => ...
        public static Option<Customer> Read(int id) => ...
    }

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:

    public static class ThingProvider
    {
        public static void DoSomethingWithGlobalSideffect(Thing useThisThing);
    }

It’s next to impossible to test this correctly. In the Haskell world you could imagine something like this happening:

    public static class ThingProvider
    {
        public static World DoSomethingWithGlobalSideffect(World world, Thing useThisThing);
    }

The World in its current state is passed in, and the function maps to a new World 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:

  • Load all required data from IO sources into the immutable ‘schema’ objects
  • Do your pure processing on the immutable objects, which gives you immutable objects back (with no side-effects)
  • Save all changes to the IO destination

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

  • Immutable data types
  • Static ‘module like’ classes with islands of pure functionality
  • Push IO operations to ‘the edge’
  • Don’t use dependency injection frameworks, just use Func
  • Build concrete stuff first, then abstract later if necessary

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.

1reaction
louthycommented, Oct 17, 2016

@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 (because null is available), which kills the entire reason for the Option type. It also causes unnecessary boxing. And finally, even a minimal implementation of IOption<A> can’t be covariant or contravariant, because at the very least you’d want a Match function, which has both input and output arguments:

    public B Match<B>(Func<A, B> Some, Func<B> None) => ...

So the best course of action is to map using option.Map(x => x as A).

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found