/functional.option

C# implementation of F# option

Primary LanguageC#

functional.option

NuGet

C# implementation of F# option

Null is contagious and once a method returns null, the whole graph of related method calls requires null check. With Option, you can make it explicit that this method should handle null type check. Coding becomes very pleasant when you do not have to worry of null return type.

The symbolic definition is as follows

Option<T> = None | Some(T)
  • None: a special value that indicates the absence of a value. The Option is none, if it has no inner value.
  • Some(T): a container that wraps the value of type T. The Option is Some if if has an inner value.

Some functional languages use Maybe for Option and it's the same concept.

examples

Taking Option as parameter

[Fact]
public void Should_handle_option_T()
{
    // arrange
    Option<string> _ = F.None;
    Option<string> batman = F.Some("Batman");

    // act
    var greet1 = greet(_);
    var greet2 = greet(batman);

    // assert
    Assert.Equal("Sorry, who?", greet1);
    Assert.Equal("Hello, Batman", greet2);
}

string greet(Option<string> greetee)
    => greetee.Match(
        None: () => "Sorry, who?",
        Some: (name) => $"Hello, {name}"
    );

Returning a value from the database wrapped in Option

public async Task<Option<Events>> GetBy(
    tenant t,
    Id id,
    EventId eventId)
{
    using (var conn = _appSettings.ToSqlConnection(t))
    {
        await conn.OpenAsync();
        var event = await conn.QuerySingleOrDefaultAsync<Events>(
            @"SELECT *
                FROM Events
               WHERE EventId = @eventId
                 AND id = @id",
            new
            {
                eventId = eventId.Id,
                id = id.Id
            });

        return event == null
            ? F.None
            : F.Some(event);
    }
}

var event = await _repository.GetBy(@event.t.To<t>(), 
        new Id(id), @event.eventId);
await event.Match(
    None: () => 
    {
        return F.UnitAsync();
    },
    Some: e => _repository.Update(@event.t.To<t>(),
        e.EventId,
        e.Id,
        EventStatus.Completed));
}

You can use Map to apply func to the inner value.

TempOfflineType = TempOffline.Map(v => v.To<TempOfflineType>());

IsSome, IsNone

duration.Match(
    None: () => { },
    Some: d =>
        DateTime.Today.AddDays(1).AddHours(5)
            .Pipe(tomorrow5Am => LocalDateTime.FromLocal(tenant, tomorrow5Am))
            .Pipe(localDateTime => new EventAction(localDateTime, localDateTime.ToUtc(), ActionType.BringOnline))
            .Pipe(eventAction => Actions.Add(eventAction))
    );

if (duration.IsSome || !endDateTime.HasValue)
    return;