nlkl / Optional

A robust option type for C#
MIT License
903 stars 73 forks source link

Combine / Merge Options #59

Closed rhaly closed 5 years ago

rhaly commented 6 years ago

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?

dnikolovv commented 6 years ago

Hey!

What you're describing here seems to be a great case for Map and FlatMap.

Map applies a function to the inner value of the Option, if there's any. For example:

Option<int> numberOption= 10.Some();
Option<int> mappedOption = numberOption.Map(n => n + 10); // becomes an option of 20
Option<string> stringOption = numberOption.Map(n => n.ToString()) // there's no problem if you want to change the type

FlatMap applies a function that returns an Option to an Option value, if there's any.

Option<int> numberOption = 10.Some();
string someString = "10";

// Becomes an option holding "10"
Option<string> = numberOption.FlatMap(n =>
  someString.SomeWhen(str => n.ToString() == str))

Obviously these examples are trivial and don't make much sense in the real world. If you would like to provide the actual scenario that got you thinking about a Merge function in the first place, I could give you better ones.

nlkl commented 5 years ago

Hi!

First of all, thank your for your interest in Optional, and I'm very sorry for the late reply.

In general, the way I handle this myself is to use Optional.Linq:

return from name in nameOrNone
       from position in positionOrSome
       select MapSomethingHere(name, position);

(as the other answer describes, you can also use FlatMap directly, but this is mostly a matter of taste, and I normally prefer the Linq syntax for the purpose)

This works well, when you are simply transforming data. However, when you need the data to trigger some side-effects, this is a bit cumbersome, and I can see the reason behind the suggestion, as it would certainly make some scenarios a bit more lightweight.

I will give it a bit more thought (and probably respond directly to your PR), particularly regarding which exact APIs Optional should offer.

/ Nils

nlkl commented 5 years ago

Closing as there is an open PR for this