Code Monkey home page Code Monkey logo

optional's Introduction

Optional

Optional is a robust option/maybe type for C#.

Version: 4.0.0

What and Why?

Optional is a strongly typed alternative to null values that lets you:

  • Avoid those pesky null-reference exceptions
  • Signal intent and model your data more explictly
  • Cut down on manual null checks and focus on your domain

Features

  • Robust and well tested
  • Self contained with no dependencies
  • Easily installed through NuGet
  • Supports .NET 3.5+ and .NET Core (.NET Standard 1.0+)
  • Focused but full-featured API

Installation

Simply reference Optional.dll and you are good to go!

Optional is also available via NuGet:

PM> Install-Package Optional 

Or visit: https://www.nuget.org/packages/Optional/

Core concepts

The core concept behind Optional is derived from two common functional programming constructs, typically referred to as a maybe type and an either type (referred to as Option<T> and Option<T, TException> in Optional).

Many functional programming languages disallow null values, as null-references can introduce hard-to-find bugs. A maybe type is a type-safe alternative to null values.

In general, an optional value can be in one of two states: Some (representing the presence of a value) and None (representing the lack of a value). Unlike null, an option type forces the user to check if a value is actually present, thereby mitigating many of the problems of null values. Option<T> is a struct in Optional, making it impossible to assign a null value to an option itself.

Further, an option type is a lot more explicit than a null value, which can make APIs based on optional values a lot easier to understand. Now, the type signature will indicate if a value can be missing!

An either type is conceptually similar to a maybe type. Whereas a maybe type only indicates if a value is present or not, an either type contains an auxiliary value describing how an operation failed. Apart from this exceptional value, an either-type behaves much like its simpler counterpart.

Working with maybe and either types is very similar, and the description below will therefore focus on the maybe type, and only provide a quick summary for the either type.

Finally, Optional offers several utility methods that make it easy and convenient to work with both of the above described optional values.

Usage

Using the library

To use Optional simply import the following namespace:

using Optional;

A few auxiliary namespaces are provided:

using Optional.Linq; // Linq query syntax support
using Optional.Unsafe; // Unsafe value retrieval

Creating optional values

The most basic way to create optional values is to use the static Option class:

var none = Option.None<int>();
var some = Option.Some(10);

For convenience, a set of extension methods are provided to make this a little less verbose:

var none = 10.None(); // Creates a None value, with 10 determining its type (int)
var some = 10.Some();

Note that it is also allowed (but hardly recommended) to wrap null values in an Option instance:

string nullString = null;
var someWithNull = nullString.Some();

To make it easier to filter away such null values, a specialized extension method is provided:

string nullString = null;
var none = nullString.SomeNotNull(); // Returns None if original value is null

Similarly, a more general extension method is provided, allowing a specified predicate:

string str = "abc";
var none = str.SomeWhen(s => s == "cba"); // Return None if predicate is violated
var none = str.NoneWhen(s => s == "abc"); // Return None if predicate is satisfied

Clearly, optional values are conceptually quite similar to nullables. Hence, a method is provided to convert a nullable into an optional value:

int? nullableWithoutValue = null;
int? nullableWithValue = 2;
var none = nullableWithoutValue.ToOption();
var some = nullableWithValue.ToOption();

Retrieving values

When retrieving values, Optional forces you to consider both cases (that is if a value is present or not).

Firstly, it is possible to check if a value is actually present:

var hasValue = option.HasValue;

If you want to check if an option contains a specific value, you can use the Contains or Exists methods. The former checks if the optional contains a specified value, the latter if the contained value satisfies some predicate:

var isThousand = option.Contains(1000);
var isGreaterThanThousand = option.Exists(val => val > 1000);

The most basic way to retrieve a value from an Option<T> is the following:

// Returns the value if present, or otherwise an alternative value (10)
var value = option.ValueOr(10);
var value = option.ValueOr(() => SlowOperation());  // Lazy variant

In more elaborate scenarios, the Match method evaluates a specified function:

// Evaluates one of the provided functions and returns the result
var value = option.Match(x => x + 1, () => 10); 

// Or written in a more functional'ish style (think pattern matching)
var value = option.Match(
  some: x => x + 1, 
  none: () => 10
);

There is a similar Match function to simply induce side-effects:

// Evaluates one of the provided actions
option.Match(x => Console.WriteLine(x), () => Console.WriteLine(10)); 

// Or pattern matching'ish as before
option.Match(
  some: x => Console.WriteLine(x), 
  none: () => Console.WriteLine(10)
);

Finally, side-effect matching (that is matching without returning a value) can be carried out for each case separately:

// Evaluated if the value is present
option.MatchSome(x => 
{
    Console.WriteLine(x)
});

// Evaluated if the value is absent
option.MatchNone(() => 
{
    Console.WriteLine("Not found") 
});

Retrieving values without safety

In some cases you might be absolutely sure that a value is present. Alternatively, the lack of a value might be fatal to your program, in which case you just want to indicate such a failure.

In such scenarios, Optional allows you to drive without a seatbelt. However, to stress the lack safety, another namespace needs to be imported:

using Optional.Unsafe;

When imported, values can be retrieved unsafely as:

var value = option.ValueOrFailure();
var anotherValue = option.ValueOrFailure("An error message"); 

In case of failure an OptionValueMissingException is thrown.

In a lot of interop scenarios, it might be necessary to convert an option into a potentially null value. Once the Unsafe namespace is imported, this can be done relatively concisely as:

var value = option.ValueOrDefault(); // value will be default(T) if the option is empty.

Similarly, it is possible to convert an option into to a nullable (insofar as the inner value is a value type):

var nullable = option.ToNullable();

As a rule of thumb, such conversions should be performed only just before the nullable value is needed (e.g. passed to an external library), to minimize and localize the potential for null reference exceptions and the like.

Transforming and filtering values

A few extension methods are provided to safely manipulate optional values.

The Or function makes it possible to specify an alternative value. If the option is none, a some instance will be returned:

var none = Option.None<int>();
var some = none.Or(10); // A some instance, with value 10
var some = none.Or(() => SlowOperation()); // Lazy variant

Similarly, the Else function enables you to specify an alternative option, which will replace the current one, in case no value is present. Notice, that both options might be none, in which case a none-option will be returned:

var none = Option.None<int>();
var some = none.Else(10.Some()); // A some instance, with value 10
var some = none.Else(Option.None<int>()); // A none instance
var some = none.Else(() => Option.Some<int>()); // Lazy variant

The Map function transforms the inner value of an option. If no value is present none is simply propagated:

var none = Option.None<int>();
var stillNone = none.Map(x => x + 10);

var some = 10.Some();
var somePlus10 = some.Map(x => x + 10);

The FlatMap function chains several optional values. It is similar to Map, but the return type of the transformation must be another optional. If either the resulting or original optional value is none, a none instance is returned. Otherwise, a some instance is returned according to the specified transformation:

var none = Option.None<int>();
var stillNone = none.FlatMap(x => x.Some()); // Returns another Option<int>

var some = 10.Some();
var stillSome = some.FlatMap(x => x.Some()); 
var none = some.FlatMap(x => x.None()); // Turns empty, as it maps to none

FlatMap is useful in combination with methods that return optional values themselves:

public static Option<Person> FindPersonById(int id) { ... }
public static Option<Hairstyle> GetHairstyle(Person person) { ... }

var id = 10;
var person = FindPersonById(id);
var hairstyle = person.FlatMap(p => GetHairstyle(p));
hairstyle.Match( ... );

In case you end up with a nested optional (e.g. Option<Option<T>>), you might flatten it by flatmapping it onto itself, but a dedicated Flatten function is offered for convenience:

Option<Option<T>> nestedOption = ...
Option<T> option = nestedOption.Flatten(); // same as nestedOption.FlatMap(o => o)

Finally, it is possible to perform filtering. The Filter function returns none, if the specified predicate is not satisfied. If the option is already none, it is simply returned as is:

var none = Option.None<int>();
var stillNone = none.Filter(x => x > 10);

var some = 10.Some();
var stillSome = some.Filter(x => x == 10);
var none = some.Filter(x => x != 10);

A recurring scenario, when working with null-returning APIs, is that of filtering away null values after a mapping. To ease the pain, a specific NotNull filter is provided:

// Returns none if the parent node is null
var parent = GetNode()
    .Map(node => node.Parent)
    .NotNull(); 

Enumerating options

An option implements GetEnumerator, allowing you to loop over the value, as if it was a collection with either a single or no elements.

foreach (var value in option)
{
    Console.WriteLine(value);
}

As you might have noticed, this is a nice and lightweight alternative to Match in cases where you only want to do something if the value is present. Also, you should use this instead of the more verbose and unsafe combination of option.HasValue and option.ValueOrFailure(), which you might otherwise be tempted to try.

Notice, however, that options don't actually implement IEnumerable<T>, in order to not pollute the options with LINQ extension methods and the like. Although many LINQ methods share functionality similar to those offered by an option, they offer a more collection-oriented interface, and includes several unsafe functions (such as First, Single, etc).

Although options deliberately don't act as enumerables, you can easily convert an option to an enumerable by calling the ToEnumerable() method:

var enumerable = option.ToEnumerable();

Working with LINQ query syntax

Optional supports LINQ query syntax, to make the above transformations somewhat cleaner.

To use LINQ query syntax you must import the following namespace:

using Optional.Linq;

This allows you to do fancy stuff such as:

var personWithGreenHair =
  from person in FindPersonById(10)
  from hairstyle in GetHairstyle(person)
  from color in ParseStringToColor("green")
  where hairstyle.Color == color
  select person;

In general, this closely resembles a sequence of calls to FlatMap and Filter. However, using query syntax can be a lot easier to read in complex cases.

Equivalence and comparison

Two optional values are equal if the following is satisfied:

  • The two options have the same type
  • Both are none, both contain null values, or the contained values are equal

An option both overrides object.Equals and implements IEquatable<T>, allowing efficient use in both generic and untyped scenarios. The == and != operators are also provided for convenience. In each case, the semantics are identical.

The generated hashcodes also reflect the semantics described above.

Further, options implement IComparable<T> and overload the corresponding comparison operators (< > <= >=). The implementation is consistent with the above described equality semantics, and comparison itself is based on the following rules:

  • An empty option is considered less than a non-empty option
  • For non-empty options comparison is delegated to the default comparer and applied on the contained value

Options with exceptional values

As described above, Optional support the notion of an either type, which adds and exception value, indicating how an operation went wrong.

An Option<T, TException> can be created directly, just like the Option<T>. Unlike in this simple case, we need to specify potential exceptional values (and a lot of verbose type annotations - sorry guys):

var none = Option.None<int, ErrorCode>(ErrorCode.GeneralError);
var some = Option.Some<int, ErrorCode>(10);

// These extension methods are hardly useful in this case,
// but here for consistency
var none = 10.None(ErrorCode.GeneralError);
var some = 10.Some<int, ErrorCode>();

string str = "abc";
var none = str.SomeWhen(s => s == "cba", ErrorCode.GeneralError);
var none = str.SomeWhen(s => s == "cba", () => SlowOperation()); // Lazy variant

string nullString = null;
var none = nullString.SomeNotNull(ErrorCode.GeneralError); 
var none = nullString.SomeNotNull(() => SlowOperation()); // Lazy variant

int? nullableWithoutValue = null;
int? nullableWithValue = 2;
var none = nullableWithoutValue.ToOption(ErrorCode.GeneralError);
var some = nullableWithValue.ToOption(ErrorCode.GeneralError);
var some = nullableWithValue.ToOption(() => SlowOperation()); // Lazy variant

Retrieval of values is very similar as well:

var hasValue = option.HasValue;
var isThousand = option.Contains(1000);
var isGreaterThanThousand = option.Exists(val => val > 1000);

var value = option.ValueOr(10);
var value = option.ValueOr(() => SlowOperation()); // Lazy variant
var value = option.ValueOr(exception => (int)exception); // Mapped from exceptional value

// If the value and exception is of identical type, 
// it is possible to return the one which is present
var value = option.ValueOrException(); 

The Match methods include the exceptional value in the none-case:

var value = option.Match(
  some: value => value + 1, 
  none: exception => (int)exception
);

option.Match(
  some: value => Console.WriteLine(value), 
  none: exception => Console.WriteLine(exception)
);

option.MatchSome(value => Console.WriteLine(value));
option.MatchNone(exception => Console.WriteLine(exception));

And again, when Optional.Unsafe is imported, it is possible to retrieve the value without safety:

var value = option.ValueOrFailure();
var anotherValue = option.ValueOrFailure("An error message"); 
var potentiallyNullValue = option.ValueOrDefault();

Values can be conveniently transformed using similar operations to that of the Option<T>. It is however important to note, that these transformations are all short-circuiting! That is, if an option is already none, the current exceptional value will remain, and not be replaced by any subsequent filtering. In this respect, this exceptional value is very similar to actual exceptions (hence the name).

var none = Option.None<int, ErrorCode>(ErrorCode.GeneralError);
var some = none.Or(10);
var some = none.Or(() => SlowOperation()); // Lazy variant
var some = none.Or(exception = (int)exception); // Mapped from exceptional value
var some = none.Else(10.Some<int, ErrorCode>()); // A some instance with value 10
var some = none.Else(Option.None<int, ErrorCode>(ErrorCode.FatalError)); // A none instance carrying a ErrorCode.FatalError
var some = none.Else(() => 10.Some<int, ErrorCode>()); // Lazy variant
var some = none.Else(exception = Option.None<int, ErrorCode>(exception)); // Mapped from exceptional value

// Mapping

var none = Option.None<int, ErrorCode>(ErrorCode.GeneralError);
var stillNone = none.Map(x => x + 10);

var some = Option.Some<int, ErrorCode>(10);
var somePlus10 = some.Map(x => x + 10);

// Flatmapping

var none = Option.None<int, ErrorCode>(ErrorCode.GeneralError);
var stillNone = none.FlatMap(x => x.Some<int, ErrorCode>());

var some = Option.Some<int, ErrorCode>(10);
var stillSome = some.FlatMap(x => x.Some<int, ErrorCode>()); 
var none = some.FlatMap(x => x.None(ErrorCode.GeneralError));

Option<Option<int, ErrorCode>, ErrorCode> nestedOption = ...
Option<int, ErrorCode> option = nestedOption.Flatten();

// Filtering

var result = Option.Some<int, ErrorCode>(10)
    .Filter(x => true, ErrorCode.GeneralError) // Stil some
    .Filter(x => false, ErrorCode.GeneralError) // Now "GeneralError"
    .Filter(x => false, ErrorCode.IncorrectValue) // Still "GeneralError"
    .Filter(x => false, () => SlowOperation()); // Lazy variant

var result = Option.Some<string, ErrorCode>(null)
    .NotNull(ErrorCode.GeneralError) // Returns none if the contained value is null
    .NotNull(() => SlowOperation()); // Lazy variant

Enumeration works identically to that of Option<T>:

foreach (var value in option)
{
    // Do something
}

var enumerable = option.ToEnumerable();

LINQ query syntax is supported, with the notable exception of the where operator (as it doesn't allow us to specify an exceptional value to use in case of failure):

var optionalDocument =
  from file in user.GetFileFromDatabase()
  from document in FetchFromService(file.DocumentId)
  select document;

optionalDocument.Match(
    some: document => Console.WriteLine(document.Contents), 
    none: errorCode => Console.WriteLine(errorCode)
);

Interop between Option<T> and Option<T, TException>

To make interop between Option<T> and Option<T, TException> more convenient, several utility methods are provided for this purpose.

The most basic of such operations, is to simply convert between the two types:

var some = Option.Some("This is a string");

// To convert to an Option<T, TException>, we need to tell which 
// exceptional value to use if the current option is none
var someWithException = some.WithException(ErrorCode.GeneralError);
var someWithException = some.WithException(() => SlowOperation()); // Lazy variant

// It is easy to simply drop the exceptional value
var someWithoutException = someWithException.WithoutException();

When flatmapping, it is similarly possible to flatmap into a value of the other type:

// The following flatmap simply ignores the new exceptional value
var some = Option.Some("This is a string");
var none = some.FlatMap(x => x.None(ErrorCode.GeneralError));

// The following flatmap needs an explicit exceptional value 
// as a second argument
var some = Option.Some<string, ErrorCode>("This is a string");
var none = some.FlatMap(x => Option.None<string>(), ErrorCode.GeneralError);
var none = some.FlatMap(x => Option.None<string>(), () => SlowOperation()); // Lazy variant

Working with collections

Optional provides a few convenience methods to ease interoperability with common .NET collections, and improve null safety a bit in the process.

LINQ provides a lot of useful methods when working with enumerables, but methods such as FirstOrDefault, LastOrDefault, SingleOrDefault, and ElementAtOrDefault, all return null (more precisely default(T)) to indicate that no value was found (e.g. if the enumerable was empty). Optional provides a safer alternative to all these methods, returning an option to indicate success/failure instead of nulls. As an added benefit, these methods work unambiguously for non-nullable/structs types as well, unlike their LINQ counterparts.

var option = values.FirstOrNone();
var option = values.FirstOrNone(v => v != 0);
var option = values.LastOrNone();
var option = values.LastOrNone(v => v != 0);
var option = values.SingleOrNone();
var option = values.SingleOrNone(v => v != 0);
var option = values.ElementAtOrNone(10);

(Note that unlike SingleOrDefault, SingleOrNone never throws an exception but returns None in all "invalid" cases. This slight deviation in semantics was considered a safer alternative to the existing behavior, and is easy to work around in practice, if the finer granularity is needed.)

Optional provides a safe way to retrieve values from a dictionary:

var option = dictionary.GetValueOrNone("key");

GetValueOrNone behaves similarly to TryGetValue on an IDictionary<TKey, TValue> or IReadOnlyDictionary<TKey, TValue>, but actually supports any IEnumerable<KeyValuePair<TKey, TValue>> (falling back to iteration, when a direct lookup is not possible).

Another common scenario, is to perform various transformations on an enumerable and ending up with a sequence of options (e.g. IEnumerable<Option<T>>). In many cases, only the non-empty options are relevant, and as such Optional provides a convenient method to flatten a sequence of options into a sequence containing all the inner values (whereas empty options are simply thrown away):

var options = new List<Option<int>> { Option.Some(1), Option.Some(2), Option.None<int>() };
var values = option.Values(); // IEnumerable<int> { 1, 2 }

When working with a sequence of Option<T, TException> a similar method is provided, as well a way to extract all the exceptional values:

var options = GetOptions(); // IEnumerable<Option<int, string>> { Some(1), None("error"), Some(2) }
var values = options.Values(); // IEnumerable<int> { 1, 2 }
var exceptions = options.Exceptions(); // IEnumerable<string> { "error" }

optional's People

Contributors

atifaziz avatar awesley avatar bartecargo avatar mattkotsenas avatar nlkl avatar orthographic-pedant avatar vain0x avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

optional's Issues

PCL support

Hi,
I know .NET Standard is the way to go...in the future... but is it possible to support PCLs, too?
BR

Nuget package automatically rolled back in VS2019

Perhaps I've run into an uncommon issue. I could probably look into it myself eventually.

Potential reproduction

  • Visual Studio 2019
  • Mixed solution
    • DotNet4.7.2
    • DotNetCore 2.1
    • EF.Core 2.1.1 (template seems to ship with VS2019)
    • Add nuget package Optional

Result
After first compile and nuget restore all seems fine. After a while the nuget package removed and references no longer valid and code gives compilation error.

Expected outcome
Probably that nuget package remains in the NetCore 2.1 project and remains there.

Notes
I could probably refine with some more details of the intricacies of VS2019 behavior by logging more information, shortly.

Mark library as CLS compliant

Microsoft advises that library implementers should mark their APIs as CLS compliant using CLSCompliantAttribute:

Since we mark our libraries as CLS compliant in accordance with this, and since your library is by default not CLS compliant, we would need to scatter [CLSCompliant(false)] statements throughout our APIs if we used your library.

Unfortunately, this is a show-stopper for us.

One good way to make sure that issues like this don't affect down-stream users of your library is to take advantage of Visual Studio's Code Analysis feature, which warns library developers about common issues like this.

Puzzling case regarding Match

I have problem in figuring out what's wrong with the code (underlined in red). If I replaced the none section with throw x, it will work.

puzzling-code

This is the code

https://github.com/dodyg/Vltava/blob/master/Vltava.Web/Startup.cs

This will work however

             //Load the rss listed at opml subscription file 
                    var syndication = await (await RenderPipeline.OpmlReadingAsync(subscriptionListFile.ValueOrFailure())).Match(
                        some : async opmlXml => await RenderPipeline.OpmlParsing(opmlXml).Match(
                            some : async opml => await RenderPipeline.GetSyndicationUri(opml).Match(
                                some : async  uris => (await RenderPipeline.ProcessSyndicationAsync(uris)), 
                                none:  x => throw x
                            ),
                             none: x => throw x
                        ),
                        none: x => throw x
                    );```

This variation will fail

       //Load the rss listed at opml subscription file 
                var syndication = await (await RenderPipeline.OpmlReadingAsync(subscriptionListFile.ValueOrFailure())).Match(
                    some : async opmlXml => await RenderPipeline.OpmlParsing(opmlXml).Match(
                        some : async opml => await RenderPipeline.GetSyndicationUri(opml).Match(
                            some : async  uris => (await RenderPipeline.ProcessSyndicationAsync(uris)), 
                            none:  x => Option.None<List<ComplexSyndication>, Exception>(x)
                        ),
                         none: x => Option.None<List<ComplexSyndication>, Exception>(x)
                    ),
                    none: x => Option.None<List<ComplexSyndication>, Exception>(x)
                );

Combine / Merge Options

Sometimes there is a need to compose one option containing data from other options.
For example let say there can be unrelated optional data which makes sense.

Option<int> position = 10.Some();
Option<string> name ="John";

Now to execute some code when two of those option has value you have to do something like this:

position.MatchSome(x =>{
  name.MatchSome(n =>{
   DoSomethingWith(x, n);
  });
});

Which is rather verbose. Can you introduce method like Combine or Merge which will combine those two or more options into one option of tupple?

Usage will look like this:

 Option<(int Position, string Name)> example = position.Merge(name);
 example.MatchSome(data=> DoSomething(data.Position, data.Name));

The implementation could look like this:

 public static Option<(T1, T2)> Merge<T1, T2>(this Option<T1> option1, Option<T2> option2)
        {            
            return option1.Match(
                some: op1 =>
                {                    
                    return option2.Match(
                        some: op2 => Option.Some((op1, op2)),
                        none: Option.None<(T1, T2)>);
                },
                none: Option.None<(T1, T2)>);
        }

Or maybe there is an easier way to execute some code when both options has value and pass those values to function as parameters?

Async map & bind

I'm having some code that needs to transform an Option using async functions.
But it is getting out of hand.

public async Task<Option<GetMaterialFlowResponse, string>> Handle(int id)
{
    var externalFlowId = (await 
            _context.AuthenticFlows
                .Where(a => a.Id == id)
                .Select(a => a.Id).ToArrayAsync())
                .SingleOrNone();

    var staticMeta = await _flowWebService.GetMetaDataAsync();
    var materialMeta = staticMeta.FirstOrNone(m => m.LogicType.ToLower() == "material" && m.ConditionsFields.Any(c => c.Name ==  "MsgTo"));

    foreach (var meta in materialMeta)
    {
        foreach (var flowId in externalFlowId)
        {
            var flowDetail = await GetFlowDetail(flowId);
            foreach (var detail in flowDetail)
            {
                var msgToId = meta.ConditionsFields.First(c => c.Name == "MsgTo").ID;
                var msgToOption = detail.Conditions.FirstOrNone(c => c.ConditionFieldID == msgToId).Map(c => c.Value);
                foreach (var msgTo in msgToOption)
                {
                    var outputsOption = await new GetFlowOutputFormatsHandler(_messageTypeMetaDataService, _rampService).Handle(msgTo);
                    foreach (var outputs in outputsOption)
                    {
                        var conditions = detail.Conditions.Select(c =>
                            new FlowConditionItem((int) c.ConditionFieldID, (int) c.ConditionOperatorID,
                                c.Value)).ToArray();
                        return Option.Some<GetMaterialFlowResponse, string>(
                            new GetMaterialFlowResponse(detail.Name, conditions, outputs));
                    }
                    return Option.None<GetMaterialFlowResponse, string>(
                        $"Could not determine output types for {msgTo}");
                }
                return Option.None<GetMaterialFlowResponse, string>("No MsgTo found in flow");
            }
            return Option.None<GetMaterialFlowResponse, string>($"No flow found in the WCF service with Id {flowId}");
        }
        return Option.None<GetMaterialFlowResponse, string>($"No flow found for authentic Id {id}");
    }

    return Option.None<GetMaterialFlowResponse, string>("No meta data found for material");
}

In the most inner foreach I'm using an early return to return the happy path but this is getting out of hand and just messy.
Is there any way to improve the code?

Richer Some / None models

Something that I find lacking with Optional is that the main type doesn't really allow us to add metadata.

We have Exception, and it's good that you can specify it to be of a type of your choice, but unfortunately, there's no way to add that kind of metadata to a Some instance.

I'm very inspired from FluentResult's rich Error / Success models.

Not only do they allow you to supply a custom object for the exceptional value in the equivalent Some (Success) instance, they also allow you to add an arbitrary amount of Metadata to an attached collection.

See these files:

https://github.com/altmann/FluentResults/blob/master/src/FluentResults/Reasons/Reason.cs
https://github.com/altmann/FluentResults/blob/master/src/FluentResults/Reasons/Error.cs
https://github.com/altmann/FluentResults/blob/master/src/FluentResults/Reasons/Success.cs

Make build script work out of the box

I just cloned the repo and tried to .\build.ps1. This is the output:

C:\Program Files\dotnet\sdk\2.1.104\Sdks\Microsoft.NET.Sdk\build\Microsoft.PackageDependencyResolution.targets(327,5): error : Assets file '
D:\src\Projects\Optional\src\Optional\obj\project.assets.json' not found. Run a NuGet package restore to generate this file. [D:\src\Project
s\Optional\src\Optional\Optional.csproj]
C:\Program Files\dotnet\sdk\2.1.104\Sdks\Microsoft.NET.Sdk\build\Microsoft.PackageDependencyResolution.targets(167,5): error : Assets file '
D:\src\Projects\Optional\src\Optional\obj\project.assets.json' not found. Run a NuGet package restore to generate this file. [D:\src\Project
s\Optional\src\Optional\Optional.csproj]
C:\Program Files\dotnet\sdk\2.1.104\Sdks\Microsoft.NET.Sdk\build\Microsoft.PackageDependencyResolution.targets(327,5): error : Assets file '
D:\src\Projects\Optional\src\Optional\obj\project.assets.json' not found. Run a NuGet package restore to generate this file. [D:\src\Project
s\Optional\src\Optional\Optional.csproj]
C:\Program Files\dotnet\sdk\2.1.104\Sdks\Microsoft.NET.Sdk\build\Microsoft.PackageDependencyResolution.targets(167,5): error : Assets file '
D:\src\Projects\Optional\src\Optional\obj\project.assets.json' not found. Run a NuGet package restore to generate this file. [D:\src\Project
s\Optional\src\Optional\Optional.csproj]
C:\Program Files\dotnet\sdk\2.1.104\Sdks\Microsoft.NET.Sdk\build\Microsoft.PackageDependencyResolution.targets(327,5): error : Assets file '
D:\src\Projects\Optional\src\Optional\obj\project.assets.json' not found. Run a NuGet package restore to generate this file. [D:\src\Project
s\Optional\src\Optional\Optional.csproj]
C:\Program Files\dotnet\sdk\2.1.104\Sdks\Microsoft.NET.Sdk\build\Microsoft.PackageDependencyResolution.targets(167,5): error : Assets file '
D:\src\Projects\Optional\src\Optional\obj\project.assets.json' not found. Run a NuGet package restore to generate this file. [D:\src\Project
s\Optional\src\Optional\Optional.csproj]
C:\Program Files\dotnet\sdk\2.1.104\Sdks\Microsoft.NET.Sdk\build\Microsoft.PackageDependencyResolution.targets(327,5): error : Assets file '
D:\src\Projects\Optional\src\Optional\obj\project.assets.json' not found. Run a NuGet package restore to generate this file. [D:\src\Project
s\Optional\src\Optional\Optional.csproj]
C:\Program Files\dotnet\sdk\2.1.104\Sdks\Microsoft.NET.Sdk\build\Microsoft.PackageDependencyResolution.targets(167,5): error : Assets file '
D:\src\Projects\Optional\src\Optional\obj\project.assets.json' not found. Run a NuGet package restore to generate this file. [D:\src\Project
s\Optional\src\Optional\Optional.csproj]

Imo there should be a one-shot build script that takes care of cleaning up previous artifacts, restoring NuGet packages and other dependencies, ...

If you like, I could figure it out and make a PR.

Interopt with Async Entityframework functions

I'm having the following code:

var terms = await _context.Terms.FindAsync(id);
return terms != null ?
Option.Some<TermsModel, Exception>(new TermsModel(terms.Id.ToString(), terms.DocumentUrl)) :
Option.None<TermsModel, Exception>(new RecordNotFoundException($"No general terms were found for the configured id {id}"));

Is/was there a way to avoid the null check and use an extension method to get an option of the FindAsync result?

Should .Map return None if mapping returns null?

I was surprised by this behavior:

var obj = new { ThisIsNull = (string)null };
var result = Option.Some(obj)
       .Map(o => o.ThisIsNull)
       .ValueOr("hello");

Expected: "hello"
Actual: null

Isn't this unintuitive behavior? Looking at Java 8's Optional and Rust's std::Option, the result of .Map is None if the mapping produces null.

I realize we can get this functionality with .Map(...).NotNull(), however, the existing behavior remains unintuitive. Thoughts?

ValueOrFailure with specified exception.

Since Nullable cannot support class type, I am now using Option to design my apis.

How do I return a value if it exists otherwise throw a specified exception?

For now we have two overloads:

// When imported, values can be retrieved unsafely as:

var value = option.ValueOrFailure();
var anotherValue = option.ValueOrFailure("An error message"); 

We can specify the error message, but cannot specify the specified exception.

To achieve this, I am now using:

var value = option.ValueOr(() => throw new CustomException());

But I think this is something like a hack.

My expected:

var value = option.ValueOrFailure(() => new CustomException());
// Or
var value = option.ValueOrFailure<CustomException>();

This one makes more sense and same as other failure. Any ideas?

Retrieve value from optional as from a dictionary

What is your opinion about following extension method:

public static bool TryGetValue<T>(this Option<T> @this, out T value)
{
    value = @this.ValueOr(default(T));
    return @this.HasValue;
}

I know that this is not "functional style" but it allows to write this kind of "fail-fast" code:

void Foo() 
{
    string res;
    if (!option.TryGetValue(out res)) return ; // fail-fast
}

Q: filter a list of either monads

I have a list of Option<A,B> and I want to filter to have a list of A and a list of B

List<Option<A,B>> source = GetList();

var a =
    source.Select(x => x.Match(a => a, b => null).Where(x => x != null);

var b =
    source.Select(x => x.Match(a => null, b => b).Where(x => x != null);

Is there a better way of doing this?

Mark Map and FlatMap as Obsolete and rename to F# variants (not Scala)

I guess Bind and Return are better for .NET functions ecosystem rather than FlatMap and Map, I if am not mistaken. So people could easily jump from C# and F# still having same vocabulary. I guess it is more fruitful than allow Scala programmers to jump into C#. I guess Haskell also have bind . Other alternative retain only SelectMany and Select, but document in method documentation that these are Bind and Return like in F#.

https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions

As of now this project has no so best option from available regarding these names. Documentation still could retain what is what. Including some metaphors like aggregatable, composable (Scot Wlaschin like). But API would have one and only one method to do the things.

Thanks

JSON Serialization please

@nlkl, I love love love how perfect and small your library is and I think you have the perfect combination of readable methods combined with the correct approach to eliminating null reference exceptions.

That being said, it's difficult to use your library without a way of serializing it to JSON.

I'd like to propose providing something like this: https://github.com/DavidArno/SuccincT/blob/14ad21767ba40661785a8aa2623d0ed987a976ba/src/SuccincT.JSON/JSON/OptionConverter.cs

ValueOrFailure with custom exception

There are cases when it would be nice to have custom exception throwing as a default behavior in case of Optional has no value since default exception (OptionalValueMissing..) is too private and forces you to handle it. The user-defined exception will be much common.
Something like
Optional.Map(..).ValueOrFailure(() => AnyKindOfCustomException())

I noticed that you have Optional<T, TException> but this solution also problematic since it requires to define the optional of this particular type in advance, while exceptional behavior might pop up only on runtime..

Option.None should be a value

The Option type is defined as data Option = None | Some x.
Option.None should be a value.
In the library, it is implemented as a type function.
None<int>() isn't semantic, since absence of a value does not carry type information.

Publish version 4 on NuGet

It appears that you've already prepared to release version 4 on NuGet: 61aadc6.

Are you confident enough to make version 4 the official version? It has some useful features that I need, and I currently have to manually select the pre-release version.

Make Option<T>.ToString return string value of Some or blank

Option<T>.ToString() is currently implemented to return a string representation of Option<T>'s state when ideally it should simply return a blank when None or Some(null) and the string representation of the value of Some otherwise. In other words:

        public override string ToString() =>
            hasValue && value != null ? value.ToString() : string.Empty;

Doing so will go a long way to help Option<T> to be easily substituted in code for Nullable<T> and references where one desires. The trouble with the current implementation is that any refactoring towards Option<T> will require one to thoroughly review all code where string formatting is used. For example, suppose the following code:

var d = (DateTime?) new DateTime(2010, 12, 31)
var s = string.Format(new CultureInfo("de-DE"), "{0:D}", d);
Console.WriteLine(s);

This prints:

Freitag, 31. Dezember 2010

However, if you use Option<T> instead:

var d = Option.Some(new DateTime(2010, 12, 31));
var s = string.Format(new CultureInfo("de-DE"), "{0:D}", d);
Console.WriteLine(s);

then output changes to (depending on the thread's default culture) something along the lines of:

Some(31/12/2010 00:00:00)

If you have unit tests then you can catch this sort of issue otherwise your luck will be your friend or your enemy. Either way, the fix looks ugly:

var d = Option.Some(new DateTime(2010, 12, 31));
var s = string.Format(new CultureInfo("de-DE"), "{0:D}",
                      d.Match(v => v, () => default(DateTime?)));
Console.WriteLine(s);

For debugging purposes, one can still rely on DebuggerDisplayAttribute to display the string representation as it is today.

Transforming IEnumerable<Option<T>> to IEnumerable<T>

What would be the correct way to transform an IEnumerable of Option to simply an IEnumerable of T?

Let's say for example that I have a list of values and I only care about the ones that definitely have values and would like to both filter out the ones that are None as well as cause the type signature to match.

Give the readme a facelift

The readme has grown rather convoluted as features have been added. For 4.0.0 the readme should be rewritten or at the very least cleaned up heavily.

Option Constructor?

I've read the documentation, and what I don't currently see is a convenient way to construct an Option from a value.

This would be convenient for wrapping calls to other APIs that don't use options. For example

var x = Option.From(MyOperation());

x in this case would be Some() if MyOption() returned a non-null value, and None otherwise.

Possible implementation would be

static public Option<T> OptionFrom<T>(T value)
{
  return value == null ? Option.Some(value) : Option.None<T>();
}

This would be similar to Option(x) works in Scala.

ToOption()

Hi, new to the library and first things that really strikes me that it is possible to create Some(null). Shouldn't Option be a guard against nulls? Current design gives no guarantees that Some state / HasValue actually means that Option has value. Anyways, what do you think about ToOption() extension method that would actually check for null and return None in that case and Some otherwise?

IEnumerable<Option<T>>.Flatten() to remove Nones from collection of Option<T> (and unwrap Somes)

btw thanks for the awesome library!

Borrowing from scala Option functionality, it would be very convenient/intuitive to have a Flatten() method for a collection of Option that unwraps the values, removing the Nones, as in this extension method (another name is fine with me too...Unwrap?):

        public static IEnumerable<T> Flatten<T>(this IEnumerable<Option<T>> options)
        {
            return options.SelectMany(o => o.ToEnumerable());
        }

And test:

      [Test]
       public void Flatten_Always_RemovesNonesAndUnwrapsSomes()
       {
           var options = new List<Option<int>>()
           {
               1.Some(),
               Option.None<int>(),
               3.Some(),
               Option.None<int>(),
               Option.None<int>(),
               6.Some()
           };

           var flattened = options.Flatten();

           CollectionAssert.AreEqual(new int[] { 1, 3, 6}, flattened);
       }

Implicit operator for option type

Would it be very awful if Option type had implicit operator for converting value from T => Option<T>?

public static implicit operator Option<T>(T value)
{
     return Option.Some(value);
}

It would allow to use shortcut for assigning option values. Instead of Option<bool> opt = Option.Some(true); I could use Option<bool> opt = true;

Implicit conversions between T / null and Option<T>

Wouldn't it be nice if you could do the following:

public Option<Foo> GetFoo()
{
    if(SomeCondition) return null;
    return new Foo();
}

Well, by adding the following to Option<T>:

public static implicit operator Option<T>(T valueOrNull)
{
    return valueOrNull == null ? Option.None<T>() : valueOrNull.Some();
}

you actually can.

Granted, it's less explicit and those who use Optional to get rid of all nulls may shudder to see return null...

Any objections? If not, I'd happily open a PR while adding a few tests and whatnot.

Match always needs both options

In some scenarios you don't care if the Option is None.

           myVal.Match(t =>
            {
                Log(t);
            }, () => { });

Would it not make sense to add an overload that doesn't require an none delegate?

Covariance in Either

Is it intended that in Option<T, E> (aka Either), both T and E are not covariant? I.e, I cannot do this:

interface Thinger {
  Option<T, ErrorResult> DoSomething<T>();
}

class ErrorResult {}
class DifferentErrorResult extends ErrorResult {}

class ThingerImplementation : Thinger {
  Option<T, ErrorResult> DoSomething<T>() {
    // Compiler error: Option<T, DifferentErrorResult> cannot be implicitly converted to Option<T, ErrorResult>
    return Option.None<T, DifferentErrorResult>(new DifferentErrorResult());
  }
}

IsNoneOr(predicate)

Hi Nils!

I couldn't find a method to check if an Option is empty or satisfies a given predicate.
I'm looking for an equivalent for scala's forall behaviour on Option.

I'm seeing two ways of getting this currently:

if (!opt.HasValue || opt.Exists(Predicate)) { ... }
if (opt.Map(Predicate).ValueOr(true)) { ... }

Is there some easier way that I missed?

If not, would you consider adding something like if (opt.IsEmptyOr(Predicate)), which would be more readable IMO. I might work on a PR if you are willing to accept it.

Optional.Collection nuget package v.1.1.0 misses Values and Exceptions extension methods

Hi,

It seems Optional.Collection misses Values and Exceptions extension methods in Nuget package 1.1.0.

By the way, I was wondering what is the best way of accessing the Exception property of an Option<T,TException>?
Is it opt.Match(v => Option.None<TException>(), ex => ex.Some())?
Maybe an extension method (ToException ?) that does that would be a fine addition to the lib.
An ExceptionOrFailure in unsafe might also be provided.

I have to say this lib changed my programmer's life! Thanks a lot for your work.

Manuel

Allow access to Exception member

There are certain use cases that require access to that member

just as the Unsafe namespace allows access to Value, same should be with Exception

Usafe method to turn Option<T, TException> to Option<T> while throwing TException

Sometimes, exception is not a bad thing. A code within library could return Option<T, TException>, but the exception cannot be properly handled in calling code, so just fall-back to C# exception handling by throwing this exception like normally using unsafe method.

One problem is that TException can be of any type and not just Exception. My suggestion is : If TException is Exception, then throw InvalidOperationException (or something similar) and exception as inner exception. If it isn't Exception, then create new Exception type and save the exception as object within it.

Sign NuGET build artifacts

Could you please sign binaries, produced for NuGET?
This will allow for your libraries to be referenced from strong named assemblies (signed).

Port nuget to .NET core

.NET Core csproj can have nuget build in. Really simplifies solution and does all from dotnet cli easy.

Better integration of Option<T> with string formatting

This is a follow-on issue from #17.

If #19 is merged then the issue of formatting still remains; that is, the following:

var d = new DateTime(2010, 12, 31).Some();
var s = string.Format(new CultureInfo("de-DE"), "{0:D}", d);
Console.WriteLine(s);

will print:

31/12/2010 00:00:00

This is better than printing Some(31/12/2010 00:00:00) but not the expected value of Freitag, 31. Dezember 2010.

Option<T> could be made to integrate better with string formatting by making it implement IFormattable and then format the value (when Some) using the format and IFormatProvider passed to IFormattable.ToString().

Add ValueOrDefault() to Optional.Unsafe namespace

Based on the discussion in #13, I think it would be fair idea to add a ValueOrDefault() method (returning the value or default(TValue)) to the Unsafe namespace.

The need for such a function arises rather often due to interop with the rest of .NET, and writing option.ValueOr(default(TValue)) becomes rather cumbersome for complicated type signatures for TValue. Similarly, option.ValueOr(null) is unfortunately a bit of a trap of its own, as it will default to the Func-overload of the method, and throw an ArgumentNullException.

Integrate Trasvis CI into the project

At some stage, it will be nice to have Travis CI into the project along with other DevOps tools. I am not a DevOps, but I can invest some time in helping you for that. I am junior developer and I like the idea of the project. Nothing will make me happier than contributing with whatever I can.

If you are interested we can get in touch ๐Ÿ‘

Either verbosity

I am testing this library with one project, however either type seems insanely verbose. Heres my code

        public Option<IEnumerable<ImageEvent>, AggregateException> UpdateK8sDeployments (string newImageUri)
        {
            var parsedUri = ImageUriParser.ParseUri (newImageUri);

            var result = _shell.Run ("kubectl get deployments --all-namespaces -o json")
                .Match<Option<IEnumerable<ImageRow>, Exception>> (
                    success =>
                    {
                        var asJObject = JObject.Parse (success);

                        return ParseImageRowsFromJsonResponse (asJObject)
                            .Where (image => SelectOnlyMatchingDeployments (image, parsedUri))
                            .Some<IEnumerable<ImageRow>, Exception> ();

                    }, error => Option.None<IEnumerable<ImageRow>, Exception> (error))
                .Match<Option<IEnumerable<ImageEvent>, AggregateException>> (images =>
                    {
                        var setResult = images
                            .Select (image =>
                                RunSetNewImageCommand (parsedUri, image));

                        if (setResult.Exceptions ().Any ())
                            return Option.None<IEnumerable<ImageEvent>, AggregateException> (new AggregateException (setResult.Exceptions ()));

                        return Option.Some<IEnumerable<ImageEvent>, AggregateException> (setResult.Values ());
                    },
                    error => Option.None<IEnumerable<ImageEvent>, AggregateException> (new AggregateException (error)));

            result.Match(
                    some => _logger.LogInformation($"Updated images: {string.Join(",", some.Select(x => x.Image))}"),
                    none => _logger.LogError(none.ToString()));

            return result;
        }

Basically this parses json that kubernetes returns and then runs bunch of other terminal commands based on that information + logging. Usual stuff. Those complex generics however really reduces readability of code so i am asking advice is there other solutions to reduce noise or even refactor this alltogether to get better readibility? Theres some things i dont grasp yet.

There was issue about implicit casting as option which was denied since it creates its own problems. I agree totally with simple Option type, however on either version of it it reduces generic that and that boilerplate greatly. LanguageExt uses that solution with either which is nice, however that library API is kind of mess on other areas.

Another way i figured out could be to use static imports, however that is blocked since generic is given as part of method not type. Eg. Option<T,TE>.Some() vs Option.Some<T,TE>().

Is only thing left to refactor those generic definitions out with helper methods to shorten calls?

Advice required: how to deal with APIs that don't use Option?

I want to use Option in my code, but sometimes I need to get a null value when passing to another API. I'd like to be able to treat Nullable<T> value types in a similar way to class types. What are your thoughts on the idea in the code below?

    public static class OptionalExtensions
    {
        public static T? GetUnsafeValueOrStructNull<T>(this Option<T> option)
            where T : struct
        {
            return option.HasValue
                ? option.ValueOrFailure()
                : (T?)null;
        }

        public static T GetUnsafeValueOrClassNull<T>(this Option<T> option)
            where T : class
        {
            return option.ValueOr(default(T));
        }
    }

Ideally, I'd just use your library rather than create non-standard extensions for something so fundamental, so if you could point me in the right direction, I'd like to avoid using these extension methods.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.