Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions CSharpFunctionalExtensions.Examples/LinqQuery/ResultQueryExamples.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace CSharpFunctionalExtensions.Examples.LinqQuery
{
/// <summary>
/// Demonstrates using LINQ query syntax (sugar) as an alternative to
/// fluent method chaining to compose operations in a monadic style,
/// analogous to F# computation expressions or Haskell's do-notation.
/// </summary>
public class ResultQueryExamples
{
public async Task Demo()
{
// Using the LINQ query syntax:
var validCustomer =
from name in CustomerName.Create("jsmith")
from email in Email.Create("jsmith@example.com")
select new Customer(name, email);

// Equivalent to:

var validCustomer_ = CustomerName
.Create("jsmith")
.Bind(name =>
Email.Create("jsmith@example.com").Map(email => new Customer(name, email))
);

// Success(Customer(Name: jsmith, Email: jsmith@example.com))
Console.WriteLine(validCustomer);

var invalidCustomer =
from name in CustomerName.Create("jsmith")
from email in Email.Create("no email")
select new Customer(name, email);

// Failure(E-mail is invalid)
Console.WriteLine(invalidCustomer);

//------------------------------------------------------------------------------
// Also works with async methods.

var billing = await (
from customer in CustomerRepository.GetByIdAsync(1) // Task<Result<Customer>>
from billingInfo in PaymentGateway.ChargeCustomerAsync(customer, 1_000) // Task<Result<BillingInfo>>
select billingInfo
);

// Equivalent to:
var billing_ = await CustomerRepository
.GetByIdAsync(1)
.Bind(customer => PaymentGateway.ChargeCustomerAsync(customer, 1_000));

// Success(BillingInfo(Customer: jsmith@example.com, ChargeAmount: 1000))
Console.WriteLine(billing);

var failedBilling = await (
from customer in CustomerRepository.GetByIdAsync(1)
from billingInfo in PaymentGateway.ChargeCustomerAsync(customer, 5_000_000)
select billingInfo
);

// Failure(Insufficient balance.)
Console.WriteLine(failedBilling);
}
}

public class Email
{
private readonly string _value;

private Email(string value)
{
_value = value;
}

public override string ToString()
{
return _value;
}

public static Result<Email> Create(string email)
{
if (string.IsNullOrWhiteSpace(email))
return Result.Failure<Email>("E-mail can't be empty");

if (email.Length > 100)
return Result.Failure<Email>("E-mail is too long");

if (!Regex.IsMatch(email, @"^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$"))
return Result.Failure<Email>("E-mail is invalid");

return Result.Success(new Email(email));
}
}

public class CustomerName
{
private readonly string _value;

private CustomerName(string value)
{
_value = value;
}

public override string ToString()
{
return _value;
}

public static Result<CustomerName> Create(string name)
{
if (string.IsNullOrWhiteSpace(name))
return Result.Failure<CustomerName>("Name can't be empty");

if (name.Length > 50)
return Result.Failure<CustomerName>("Name is too long");

return Result.Success(new CustomerName(name));
}
}

public class Customer
{
public CustomerName Name { get; private set; }
public Email Email { get; private set; }

public Customer(CustomerName name, Email email)
{
if (name == null)
throw new ArgumentNullException("name");
if (email == null)
throw new ArgumentNullException("email");

Name = name;
Email = email;
}

public override string ToString()
{
return $"{nameof(Customer)}({nameof(Name)}: {Name}, {nameof(Email)}: {Email})";
}
}

public class CustomerRepository
{
public static async Task<Result<Customer>> GetByIdAsync(int id)
{
var customer =
from email in Email.Create("jsmith@example.com")
from name in CustomerName.Create("jsmith")
select new Customer(name, email);

return customer;
}
}

public class BillingInfo
{
public Customer Customer { get; set; }
public decimal ChargeAmount { get; set; }

public override string ToString()
{
return $"{nameof(BillingInfo)}({nameof(Customer)}: {Customer.Email}, {nameof(ChargeAmount)}: {ChargeAmount})";
}
}

public class PaymentGateway
{
public static async Task<Result<BillingInfo>> ChargeCustomerAsync(
Customer customer,
decimal chargeAmount
)
{
if (chargeAmount > 1_000_000)
return Result.Failure<BillingInfo>("Insufficient balance");

return Result.Success(
new BillingInfo { Customer = customer, ChargeAmount = chargeAmount }
);
}
}
}
16 changes: 16 additions & 0 deletions CSharpFunctionalExtensions/Maybe/Extensions/Select.Task.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Threading.Tasks;

namespace CSharpFunctionalExtensions
{
public static partial class MaybeExtensions
{
/// <summary>
/// This method should be used in linq queries. We recommend using Map method.
/// </summary>
public static Task<Maybe<K>> Select<T, K>(this Task<Maybe<T>> maybe, Func<T, K> selector)
{
return maybe.Map(selector);
}
}
}
18 changes: 18 additions & 0 deletions CSharpFunctionalExtensions/Maybe/Extensions/Select.ValueTask.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#if NET5_0_OR_GREATER
using System;
using System.Threading.Tasks;

namespace CSharpFunctionalExtensions.ValueTasks
{
public static partial class MaybeExtensions
{
/// <summary>
/// This method should be used in linq queries. We recommend using Map method.
/// </summary>
public static ValueTask<Maybe<K>> Select<T, K>(in this ValueTask<Maybe<T>> maybe, Func<T, K> selector)
{
return maybe.Map(selector);
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Threading.Tasks;

namespace CSharpFunctionalExtensions
{
public static partial class MaybeExtensions
{
/// <summary>
/// This method should be used in linq queries. We recommend using Bind method.
/// </summary>
public static async Task<Maybe<V>> SelectMany<T, U, V>(
this Task<Maybe<T>> maybeTask,
Func<T, Maybe<U>> selector,
Func<T, U, V> project)
{
Maybe<T> maybe = await maybeTask.DefaultAwait();
return maybe.SelectMany(selector, project);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Threading.Tasks;

namespace CSharpFunctionalExtensions
{
public static partial class MaybeExtensions
{
/// <summary>
/// This method should be used in linq queries. We recommend using Bind method.
/// </summary>
public static Task<Maybe<V>> SelectMany<T, U, V>(
this Maybe<T> maybe,
Func<T, Task<Maybe<U>>> selector,
Func<T, U, V> project)
{
return maybe
.Bind(selector)
.Map(x => project(maybe.Value, x));
}
}
}
40 changes: 40 additions & 0 deletions CSharpFunctionalExtensions/Maybe/Extensions/SelectMany.Task.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Threading.Tasks;

namespace CSharpFunctionalExtensions
{
public static partial class MaybeExtensions
{
/// <summary>
/// This method should be used in linq queries. We recommend using Bind method.
/// </summary>
public static Task<Maybe<K>> SelectMany<T, K>(
this Maybe<T> maybe,
Func<T, Task<Maybe<K>>> selector)
{
return maybe.Bind(selector);
}

/// <summary>
/// This method should be used in linq queries. We recommend using Bind method.
/// </summary>
public static Task<Maybe<K>> SelectMany<T, K>(
this Task<Maybe<T>> maybeTask,
Func<T, Task<Maybe<K>>> selector)
{
return maybeTask.Bind(selector);
}

/// <summary>
/// This method should be used in linq queries. We recommend using Bind method.
/// </summary>
public static async Task<Maybe<V>> SelectMany<T, U, V>(
this Task<Maybe<T>> maybeTask,
Func<T, Task<Maybe<U>>> selector,
Func<T, U, V> project)
{
var maybe = await maybeTask.DefaultAwait();
return await maybe.SelectMany(selector, project).DefaultAwait();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#if NET5_0_OR_GREATER
using System;
using System.Threading.Tasks;

namespace CSharpFunctionalExtensions.ValueTasks
{
public static partial class MaybeExtensions
{
/// <summary>
/// This method should be used in linq queries. We recommend using Bind method.
/// </summary>
public static async ValueTask<Maybe<V>> SelectMany<T, U, V>(
this ValueTask<Maybe<T>> maybeTask,
Func<T, Maybe<U>> selector,
Func<T, U, V> project)
{
Maybe<T> maybe = await maybeTask;
return maybe.SelectMany(selector, project);
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#if NET5_0_OR_GREATER
using System;
using System.Threading.Tasks;

namespace CSharpFunctionalExtensions.ValueTasks
{
public static partial class MaybeExtensions
{
/// <summary>
/// This method should be used in linq queries. We recommend using Bind method.
/// </summary>
public static ValueTask<Maybe<V>> SelectMany<T, U, V>(
this Maybe<T> maybe,
Func<T, ValueTask<Maybe<U>>> selector,
Func<T, U, V> project)
{
return maybe
.Bind(selector)
.Map(x => project(maybe.Value, x));
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#if NET5_0_OR_GREATER
using System;
using System.Threading.Tasks;

namespace CSharpFunctionalExtensions.ValueTasks
{
public static partial class MaybeExtensions
{
/// <summary>
/// This method should be used in linq queries. We recommend using Bind method.
/// </summary>
public static ValueTask<Maybe<K>> SelectMany<T, K>(
this Maybe<T> maybe,
Func<T, ValueTask<Maybe<K>>> selector)
{
return maybe.Bind(selector);
}

/// <summary>
/// This method should be used in linq queries. We recommend using Bind method.
/// </summary>
public static ValueTask<Maybe<K>> SelectMany<T, K>(
this ValueTask<Maybe<T>> maybeTask,
Func<T, ValueTask<Maybe<K>>> selector)
{
return maybeTask.Bind(selector);
}

/// <summary>
/// This method should be used in linq queries. We recommend using Bind method.
/// </summary>
public static async ValueTask<Maybe<V>> SelectMany<T, U, V>(
this ValueTask<Maybe<T>> maybeTask,
Func<T, ValueTask<Maybe<U>>> selector,
Func<T, U, V> project)
{
var maybe = await maybeTask;
return await maybe.SelectMany(selector, project);
}
}
}
#endif
Loading