Skip to content

a lightweight & fluent Option/Optional/Maybe Implementation for .Net & Mono

License

Notifications You must be signed in to change notification settings

duffleit/fluentOptionals

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

fluentOptionals

Fluent Optionals

a lightweight & fluent Option/Optional/Maybe Implementation for .Net & Mono

☝️ Supporting .net-standard 2.0 and 2.1

NuGet
PM> Install-Package FluentOptionals

Build status Coverage Status

Usually this is the place to go into detail about Optionals and why you should use them. I won't do this as it is already done better than I ever could:


Creating Optionals

Basically there are two types of optinonals, those who have a value (we will call them Some-Optionals) and those who have no value (we will call them None-Optionals). Creating them is easy:

var some = Optional.Some(20);
var none = Optional.None<int>();

Through the existence of Null-References in C#, a common use case is to create Some or None-Optionals based on a Null-Check. That's why there's a shortcut for this:

//traditional way to create Optionals
var x = (value != null) ? Optional.Some(value) : Optional.None<int>();

//shortcut 1
var y = value.ToOptional(); 

//shortcut 2
var z = Optional.From(value)

Those three approaches will always produce the same result. If the given value is null a None-Optional is returned. In all other cases you will receive a Some-Optional.

If null is not the only decision criterion, ToOptional() or Optional.From() can be called with a predicate. Based on this a Some or None-Optional is created.

var value = "david";
var some = value.ToOptional(v => v == "david");
var none = value.ToOptional(v => v == "thomas");

//or
Optional.From(value, v => v == "david");

Consider if ToOptional() is called on null, it will always return a None-Optional without even evaluating the predicate (to avoid Null-Reference-Exceptions).

If you want to receive a Some or None-Optional explicit, you can use value.ToSome() or ToNone():

10.ToSome() //always produces a Some-Optional
            //except the value is none, this causes a 'SomeCreationWithNullException'
            
10.ToNone() //ignores the value and always produces a None-Optional

Retrieving values

When retrieving values, Optionals force you to always consider Some and None (if a value is present or not).

ValueOr() is the easiest approach to retrieve an Optional's value.

"Max".ToSome().ValueOr("unknown name"); //returns "Max"

"Max".ToNone().ValueOr("unknown name"); //returns "unknown name"

"Max".ToNone().ValueOr(() => nameServer.GetDefaultName()); //will evaluate value lazy

"Max".ToNone().ValueOrThrow(new Exception("name was not provided")); //throws exception 

To additionally verify if an Optional is Some or None the properties IsSome and IsNone are provided.

Match

Another way to get an Optional's value is to use Match(). Inspired by pattern matching it provides a nice way to handle Some and None-Optionals.

var optional = "Paul".ToSome();

optional.Match(
    some: name => Console.WriteLine("hey, " + name),
    none: ()   => Console.WriteLine("we don't know your name")
)

Match can also return a value:

var optional = "Paul".ToSome();

var displayName = optional.Match(
                        some: name => v.ToUpperCase(),
                        none: ()   => "unknown PERSON"
                    )

Beside Match() there are provided more specific methods called IfSome() and IfNone(). They only take one action.

Mapping Optionals

Map() can transform the value of an Optional. If the Optional is None the transformation won't be applied and the new Optional stays None. If the give map function returns null, the Optional becomes None.

10.ToOptional().Map(i => i * 2) //returns Some-Optional<int> -> 20
10.ToNone().Map(i => i * 2) //returns None-Optional<int>
"test".ToOptional().Map(i => null) //returns None-Optional<int> 

Map() can of course also change the Optional's type.

Optional<string> result = 10.ToOptional().Map(i => i.ToString()) //returns Some-Optional<String> -> "10"
Optional<string> result = 10.ToNone().Map(i => i.ToString()) //returns None-Optional<String>
Optional<string> result = Optional.None<int>().Map(i => null) //return None-Optional<String>

Shift() provides a possibility to change a Some to a None-Optional. If Shift() is called on a None-Optional, it doesn't alter anything.

var transformed = 10.ToOptional().Shift(v => v < 100); //returns None-Optional<int>
var transformed = 200.ToOptional().Shift(v => v < 100); //returns Some-Optional<int> -> 200

Joining Optionals

Joining Optionals can be achieved by using Join().

Optional
    .From(_priceWebService.GetProductPrice(productId))
    .Join(_reductionWebService.GetProductReduction(productId))
    .Join(_taxesWebService.GetProductTaxes(productId, country))
    .Match(
        some: (price, reduction, taxes) => $"{price - reduction} (excl. {taxes}% taxes)",
        none: ()                        => $"price is currently not available"
    )

A joined Optional evaluates to none as soon as a single None-Optional is joined. Fluent Optionals let you join up to seven Optionals, and beside Match(), these composite Optionals also provide IfSome(), IfNone(), IsSome and IsNone.

IEnumerable-Extensions

  • ToOptionalList(): transforms a IEnumerable to a IEnumerable of Optionals.

    IEnumerable<Optional<int>> OptionalList = new List<int>{ 1, 2, 3 }.ToOptionalList();
    
    new List<string>{ null, "test", null }.ToOptionalList();
    //returns a List of 3 Optionals: [None, Some, None] 
    
    new List<int>{ 0, 1, 3, 0 }.ToOptionalList(v => v > 0);
    //returns a List of 4 Optionals:  [None, Some, Some, None] 
  • FirstOrNone(): returns the first Some-Optional. If there is no first element, it returns a None-Optional..

  • LastOrNone(): returns the last Some-Optional. If there is no last element, it returns a None-Optional.

  • SingleOrNone(): if exactly one Some-Optional exists, this is returned. I all other cases it returns a None-Optional.

Contributors πŸ‘¨β€πŸ’»

David Leitner
David Leitner

Andreas Mewald
Andreas Mewald

Thomas Eizinger
Thomas Eizinger

Paul Rohorzka
Paul Rohorzka

Many thanks to all the others for providing inspiration and feedback. And of course to Tony Hoare, if he won't have made his one billion dollar mistake, this library would be absolutely useless. (:

About

a lightweight & fluent Option/Optional/Maybe Implementation for .Net & Mono

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages