Category Theory via C# (7) Monad and LINQ to Monads

[FP & LINQ via C# series]

[Category Theory via C# series]

Monad

As fore mentioned endofunctor category can be monoidal (the entire category. Actually, an endofunctor In the endofunctor category can be monoidal too. This kind of endofunctor is called monad. Monad is another important algebraic structure in category theory and LINQ. Formally, monad is an endofunctor equipped with 2 natural transformations:

  • Monoid multiplication ◎ or μ, which a natural transformation ◎: F(F) ⇒ F, which means, for each object X, ◎ maps F(F(X)) to F(X). For convenience, this mapping operation is also denoted F ◎ F ⇒ F.
  • Monoid unit η, which is a natural transformation η: I ⇒ F. Here I is the identity functor, which maps each object X to X itself. For each X, there is η maps I(X) to F(X). Since I(X) is just X, η can also be viewed as mapping: X → F(X).

So monad F is a monoid (F, ◎, η) in the category of endofunctors. Apparently it must preserve the monoid laws:

  • Associativity preservation α: (F ◎ F) ◎ F ≡ F ◎ (F ◎ F)
  • Left unit preservation λ: η ◎ F ≡ F, and right unit preservation ρ: F ≡ F ◎ η

So that, the following diagram commutes:

image

In DotNet category, monad can be defined as:

// Cannot be compiled.
public partial interface IMonad<TMonad<>> : IFunctor<TMonad<>> where TMonad<> : IMonad<TMonad<>>
{
    // From IFunctor<TMonad<>>:
    // Select: (TSource -> TResult) -> (TMonad<TSource> -> TMonad<TResult>)
    // Func<TMonad<TSource>, TMonad<TResult>> Select<TSource, TResult>(Func<TSource, TResult> selector);

    // Multiply: TMonad<TMonad<TSource>> -> TMonad<TSource>
    TMonad<TSource> Multiply<TSource>(TMonad<TMonad<TSource>> sourceWrapper);
        
    // Unit: TSource -> TMonad<TSource>
    TMonad<TSource> Unit<TSource>(TSource value);
}

LINQ to Monads and monad laws

Built-in IEnumerable<> monad

The previously discussed IEnumerable<> functor is a built-in monad, it is straightforward to implement its (Multiply, Unit) method pair:

public static partial class EnumerableExtensions // IEnumerable<T> : IMonad<IEnumerable<>>
{
    // Multiply: IEnumerable<IEnumerable<TSource>> -> IEnumerable<TSource>
    public static IEnumerable<TSource> Multiply<TSource>(this IEnumerable<IEnumerable<TSource>> sourceWrapper)
    {
        foreach (IEnumerable<TSource> source in sourceWrapper)
        {
            foreach (TSource value in source)
            {
                yield return value;
            }
        }
    }

    // Unit: TSource -> IEnumerable<TSource>
    public static IEnumerable<TSource> Unit<TSource>(TSource value)
    {
        yield return value;
    }
}

The monoid unit η is exactly the same as the Wrap method for monoidal functor. It is easy to verify the above implementation preserves the monoid laws:

internal static void MonoidLaws()
{
    IEnumerable<int> source = new int[] { 0, 1, 2, 3, 4 };

    // Associativity preservation: source.Wrap().Multiply().Wrap().Multiply() == source.Wrap().Wrap().Multiply().Multiply().
    source.Enumerable().Multiply().Enumerable().Multiply().WriteLines();
    // 0 1 2 3 4
    source.Enumerable().Enumerable().Multiply().Multiply().WriteLines();
    // 0 1 2 3 4
    // Left unit preservation: Unit(source).Multiply() == f.
    Unit(source).Multiply().WriteLines(); // 0 1 2 3 4
    // Right unit preservation: source == source.Select(Unit).Multiply().
    source.Select(Unit).Multiply().WriteLines(); // 0 1 2 3 4
}

As discussed in LINQ to Object chapter, for IEnumerable<>, there is already a query method SelectMany providing the same ability to flatten hierarchy an IEnumerable<IEnumerable<T>> sequence to an IEnumerable<T> sequence. Actually, monad can be alternatively defined with SelectMany and η/Wrap:

public partial interface IMonad<TMonad> where TMonad<> : IMonad<TMonad<>>
{
    // SelectMany: (TMonad<TSource>, TSource -> TMonad<TSelector>, (TSource, TSelector) -> TResult) -> TMonad<TResult>
    TMonad<TResult> SelectMany<TSource, TSelector, TResult>(
        TMonad<TSource> source,
        Func<TSource, TMonad<TSelector>> selector,
        Func<TSource, TSelector, TResult> resultSelector);

    // Wrap: TSource -> IEnumerable<TSource>
    TMonad<TSource> Wrap<TSource>(TSource value);
}

And the alternative implementation is very similar:

public static partial class EnumerableExtensions // IEnumerable<T> : IMonad<IEnumerable<>>
{
    // SelectMany: (IEnumerable<TSource>, TSource -> IEnumerable<TSelector>, (TSource, TSelector) -> TResult) -> IEnumerable<TResult>
    public static IEnumerable<TResult> SelectMany<TSource, TSelector, TResult>(
        this IEnumerable<TSource> source,
        Func<TSource, IEnumerable<TSelector>> selector,
        Func<TSource, TSelector, TResult> resultSelector)
    {
        foreach (TSource value in source)
        {
            foreach (TSelector result in selector(value))
            {
                yield return resultSelector(value, result);
            }
        }
    }

    // Wrap: TSource -> IEnumerable<TSource>
    public static IEnumerable<TSource> Enumerable<TSource>(this TSource value)
    {
        yield return value;
    }
}

The above 2 versions of monad definition are equivalent. First, the (SelectMany, Wrap) methods can be implemented with the (Select, Multiply, Unit) methods:

public static partial class EnumerableExtensions // (Select, Multiply, Unit) implements (SelectMany, Wrap).
{
    // SelectMany: (IEnumerable<TSource>, TSource -> IEnumerable<TSelector>, (TSource, TSelector) -> TResult) -> IEnumerable<TResult>
    public static IEnumerable<TResult> SelectMany<TSource, TSelector, TResult>(
        this IEnumerable<TSource> source,
        Func<TSource, IEnumerable<TSelector>> selector,
        Func<TSource, TSelector, TResult> resultSelector) =>
            (from value in source
             select (from result in selector(value)
                     select resultSelector(value, result))).Multiply();
            // Compiled to:
            // source.Select(value => selector(value).Select(result => resultSelector(value, result))).Multiply();

    // Wrap: TSource -> IEnumerable<TSource>
    public static IEnumerable<TSource> Enumerable<TSource>(this TSource value) => Unit(value);
}

And the (Select, Multiply, Unit) methods can be implemented with (SelectMany, Wrap) methods too:

public static partial class EnumerableExtensions // (SelectMany, Wrap) implements (Select, Multiply, Unit).
{
    // Select: (TSource -> TResult) -> (IEnumerable<TSource> -> IEnumerable<TResult>).
    public static Func<IEnumerable<TSource>, IEnumerable<TResult>> Select<TSource, TResult>(
        Func<TSource, TResult> selector) => source =>
            from value in source
            from result in value.Enumerable()
            select result;
            // source.SelectMany(Enumerable, (result, value) => value);

    // Multiply: IEnumerable<IEnumerable<TSource>> -> IEnumerable<TSource>
    public static IEnumerable<TSource> Multiply<TSource>(this IEnumerable<IEnumerable<TSource>> sourceWrapper) =>
        from source in sourceWrapper
        from value in source
        select value;
        // sourceWrapper.SelectMany(source => source, (source, value) => value);

    // Unit: TSource -> IEnumerable<TSource>
    public static IEnumerable<TSource> Unit<TSource>(TSource value) => value.Enumerable();
}

So monad support is built-in in the C# language. As discussed in the LINQ query expression pattern part, SelectMany enables multiple from clauses, which can chain operations together to build a workflow, for example:

internal static void Workflow<T1, T2, T3, T4>(
    Func<IEnumerable<T1>> source1,
    Func<IEnumerable<T2>> source2,
    Func<IEnumerable<T3>> source3,
    Func<T1, T2, T3, IEnumerable<T4>> source4)
{
    IEnumerable<T4> query = from value1 in source1()
                            from value2 in source2()
                            from value3 in source3()
                            from value4 in source4(value1, value2, value3)
                            select value4; // Define query.
    query.WriteLines(); // Execute query.
}

Here N + 1 from clauses are compiled to N SelectMany fluent calls:

internal static void CompiledWorkflow<T1, T2, T3, T4>(
    Func<IEnumerable<T1>> source1,
    Func<IEnumerable<T2>> source2,
    Func<IEnumerable<T3>> source3,
    Func<T1, T2, T3, IEnumerable<T4>> source4)
{
    IEnumerable<T4> query = source1()
        .SelectMany(value1 => source2(), (value1, value2) => new { Value1 = value1, Value2 = value2 })
        .SelectMany(result2 => source3(), (result2, value3) => new { Result2 = result2, Value3 = value3 })
        .SelectMany(
            result3 => source4(result3.Result2.Value1, result3.Result2.Value2, result3.Value3),
            (result3, value4) => value4); // Define query.
    query.WriteLines(); // Execute query.
}

In LINQ, if monad’s SelectMany implements deferred execution, then monad enables imperative programming paradigm (a sequence of commands) in a purely functional way. In above LINQ query definition, the calls to the commands are not executed. When trying to pull results from the LINQ query, the workflow stars, and the commands executes sequentially.

Monad law and Kleisli composition

Regarding monad (F, ◎, η) can be redefined as (F, SelectMany, Wrap), the monoid laws now can be expressed by SelectMany and Wrap too, which are called monad laws:

  • Associativity law: SelectMany is the associative operator, since it is equivalent to Multiply.
  • Left unit law and right unit law: Wrap is the unit η, since it is identical to Unit.
internal static void MonadLaws()
{
    IEnumerable<int> source = new int[] { 0, 1, 2, 3, 4 };
    Func<int, IEnumerable<char>> selector = int32 => new string('*', int32);
    Func<int, IEnumerable<double>> selector1 = int32 => new double[] { int32 / 2D, Math.Sqrt(int32) };
    Func<double, IEnumerable<string>> selector2 =
        @double => new string[] { @double.ToString("0.0"), @double.ToString("0.00") };
    const int Value = 5;

    // Associativity: source.SelectMany(selector1).SelectMany(selector2) == source.SelectMany(value => selector1(value).SelectMany(selector2)).
    (from value in source
     from result1 in selector1(value)
     from result2 in selector2(result1)
     select result2).WriteLines();
    // 0.0 0.00 0.0 0.00
    // 0.5 0.50 1.0 1.00
    // 1.0 1.00 1.4 1.41
    // 1.5 1.50 1.7 1.73
    // 2.0 2.00 2.0 2.00
    (from value in source
     from result in (from result1 in selector1(value)
                     from result2 in selector2(result1)
                     select result2)
     select result).WriteLines();
    // 0.0 0.00 0.0 0.00
    // 0.5 0.50 1.0 1.00
    // 1.0 1.00 1.4 1.41
    // 1.5 1.50 1.7 1.73
    // 2.0 2.00 2.0 2.00
    // Left unit: value.Wrap().SelectMany(selector) == selector(value).
    (from value in Value.Enumerable()
     from result in selector(value)
     select result).WriteLines(); // * * * * *
    selector(Value).WriteLines(); // * * * * *
    // Right unit: source == source.SelectMany(Wrap).
    (from value in source
     from result in value.Enumerable()
     select result).WriteLines(); // 0 1 2 3 4
}

However, the monad laws are not intuitive. The Kleisli composition ∘ can help. For 2 monadic selector functions that can be passed to SelectMany,are also called Kleisli functions like s1: TSource –> TMonad<TMiddle> and s2: TMiddle –> TMonad<TResult>, their Kleisli composition is still a monadic selector (s2 ∘ s1): TSource –> TMonad<TResult>:

public static Func<TSource, IEnumerable<TResult>> o<TSource, TMiddle, TResult>( // After.
    this Func<TMiddle, IEnumerable<TResult>> selector2,
    Func<TSource, IEnumerable<TMiddle>> selector1) =>
        value => selector1(value).SelectMany(selector2, (result1, result2) => result2);
        // Equivalent to:
        // value => selector1(value).Select(selector2).Multiply();

Or generally:

// Cannot be compiled.
public static class FuncExtensions
{
    public static Func<TSource, TMonad<TResult>> o<TMonad<>, TSource, TMiddle, TResult>( // After.
        this Func<TMiddle, TMonad<TResult>> selector2,
        Func<TSource, TMonad<TMiddle>> selector1) where TMonad<> : IMonad<TMonad<>> =>
            value => selector1(value).SelectMany(selector2, (result1, result2) => result2);
            // Equivalent to:
            // value => selector1(value).Select(selector2).Multiply();
}

Now above monad laws can be expressed by monadic selectors and Kleisli composition:

  • Associativity law: the Kleisli composition of monadic selectors is now the monoid multiplication, it is associative. For monadic selectors s1, s2, s3, there is (s3 ∘ s2) ∘ s1 = s3 ∘ (s2 ∘ s1).
  • Left unit law and right unit law: Wrap is still the monoid unit η, it is of type TSource –> TMonad<TSource>, so it can also be viewed as a monadic selector too. For monadic selector s, there is η ∘ s = s and s = s ∘ η.
internal static void KleisliComposition()
{
    Func<bool, IEnumerable<int>> selector1 =
        boolean => boolean ? new int[] { 0, 1, 2, 3, 4 } : new int[] { 5, 6, 7, 8, 9 };
    Func<int, IEnumerable<double>> selector2 = int32 => new double[] { int32 / 2D, Math.Sqrt(int32) };
    Func<double, IEnumerable<string>> selector3 =
        @double => new string[] { @double.ToString("0.0"), @double.ToString("0.00") };

    // Associativity: selector3.o(selector2).o(selector1) == selector3.o(selector2.o(selector1)).
    selector3.o(selector2).o(selector1)(true).WriteLines();
    // 0.0 0.00 0.0 0.00
    // 0.5 0.50 1.0 1.00
    // 1.0 1.00 1.4 1.41
    // 1.5 1.50 1.7 1.73
    // 2.0 2.00 2.0 2.00
    selector3.o(selector2.o(selector1))(true).WriteLines();
    // 0.0 0.00 0.0 0.00
    // 0.5 0.50 1.0 1.00
    // 1.0 1.00 1.4 1.41
    // 1.5 1.50 1.7 1.73
    // 2.0 2.00 2.0 2.00
    // Left unit: Unit.o(selector) == selector.
    Func<int, IEnumerable<int>> leftUnit = Enumerable;
    leftUnit.o(selector1)(true).WriteLines(); // 0 1 2 3 4
    selector1(true).WriteLines(); // 0 1 2 3 4
    // Right unit: selector == selector.o(Unit).
    selector1(false).WriteLines(); // 5 6 7 8 9
    Func<bool, IEnumerable<bool>> rightUnit = Enumerable;
    selector1.o(rightUnit)(false).WriteLines(); // 5 6 7 8 9
}

Kleisli category

With monad and Kleisli composition, a new kind of category called Kleisli category can be defined. Given a monad (F, ◎, η) in category C, there is a Kleisli category of F, denoted CF:

  • Its objects ob(CF) are ob(C), all objects in C.
  • Its morphisms hom(CF) are Kleisli morphisms. A Kleisli morphisms m from object X to object Y is m: X → F(Y). In DotNet, the Kleisli morphisms are above monadic selector functions.
  • The composition of Kleisli morphisms is the above Kleisli composition.
  • The identity Kleisli morphism is η of the monad, so that ηX: X → F(X).

image

As already demonstrated, Kleisli composition and η satisfy the category associativity law and identity law.

Monad pattern of LINQ

So LINQ SelectMany query’s quintessential mathematics is monad. Generally, in DotNet category, a type is a monad if:

  • This type is an open generic type definition, which can be viewed as type constructor of kind * –> *, so that it maps a concrete type to another concrete monad-wrapped type.
  • It is equipped with the standard LINQ query method SelectMany, which can be either instance method or extension method.
  • The implementation of SelectMany satisfies the monad laws, so that the monad’s monoid structure is preserved.

As Brian Beckman said in this Channel 9 video:

LINQ is monad. It is very carefully designed by Erik Meijer so that it is monad.

Eric Lippert also mentioned:

The LINQ syntax is designed specifically to make operations on the sequence monad feel natural, but in fact the implementation is more general; what C# calls "SelectMany" is a slightly modified form of the "Bind" operation on an arbitrary monad.

On the other hand, to enable the monad LINQ query expression (multiple from clauses with select clause) for a type does not require that type to be strictly a monad. This LINQ workflow syntax can be enabled for any generic or non generic type as long as it has such a SelectMany method, which can be virtually demonstrated as:

// Cannot be compiled.
internal static void Workflow<TMonad<>, T1, T2, T3, T4, TResult>( // Non generic TMonad can work too.
    Func<TMonad<T1>> operation1,
    Func<TMonad<T2>> operation2,
    Func<TMonad<T3>> operation3,
    Func<TMonad<T4>> operation4,
    Func<T1, T2, T3, T4, TResult> resultSelector) where TMonad<> : IMonad<TMonad<>>
{
    TMonad<TResult> query = from /* T1 */ value1 in /* TMonad<T1> */ operation1()
                            from /* T2 */ value2 in /* TMonad<T1> */ operation2()
                            from /* T3 */ value3 in /* TMonad<T1> */ operation3()
                            from /* T4 */ value4 in /* TMonad<T1> */ operation4()
                            select /* TResult */ resultSelector(value1, value2, value3, value4); // Define query.
}

Monad vs. monoidal/applicative functor

Monad is monoidal functor and applicative functor. Monads’ (SelectMany, Wrap) methods implement monoidal functor’s Multiply and Unit methods, and applicative functor’s (Apply, Wrap) methods. This can be virtually demonstrated as:

// Cannot be compiled.
public static partial class MonadExtensions // (SelectMany, Wrap) implements (Multiply, Unit).
{
    // Multiply: (TMonad<T1>, TMonad<T2>) => TMonad<(T1, T2)>
    public static TMonad<(T1, T2)> Multiply<TMonad<>, T1, T2>(
        this TMonad<T1> source1, TMonad<T2> source2) where TMonad<> : IMonad<TMonad<>> =>
            from value1 in source1
            from value2 in source2
            select (value1, value2);
            // source1.SelectMany(value1 => source2 (value1, value2) => value1.ValueTuple(value2));

    // Unit: Unit -> TMonad<Unit>
    public static TMonad<Unit> Unit<TMonad<>>(
        Unit unit = default) where TMonad<> : IMonad<TMonad<>> => unit.Wrap();
}

// Cannot be compiled.
public static partial class MonadExtensions // (SelectMany, Wrap) implements (Apply, Wrap).
{
    // Apply: (TMonad<TSource -> TResult>, TMonad<TSource>) -> TMonad<TResult>
    public static TMonad<TResult> Apply<TMonad<>, TSource, TResult>(
        this TMonad<Func<TSource, TResult>> selectorWrapper, 
        TMonad<TSource> source) where TMonad<> : IMonad<TMonad<>> =>
            from selector in selectorWrapper
            from value in source
            select selector(value);
            // selectorWrapper.SelectMany(selector => source, (selector, value) => selector(value));

    // Monad's Wrap is identical to applicative functor's Wrap.
}

If monad is defined with the (Multiply, Unit) methods, they implement monoidal functor’s Multiply and Unit methods, and applicative functor’s (Apply, Wrap) methods too:

// Cannot be compiled.
public static class MonadExtensions // Monad (Multiply, Unit) implements monoidal functor (Multiply, Unit).
{
    // Multiply: (TMonad<T1>, TMonad<T2>) => TMonad<(T1, T2)>
    public static TMonad<(T1, T2)> Multiply<TMonad<>, T1, T2>(
        this TMonad<T1> source1, TMonad<T2> source2) where TMonad<> : IMonad<TMonad<>> =>
            (from value1 in source1
             select (from value2 in source2
                     select (value1, value2))).Multiply();
            // source1.Select(value1 => source2.Select(value2 => (value1, value2))).Multiply();

    // Unit: Unit -> TMonad<Unit>
    public static TMonad<Unit> Unit<TMonad>(Unit unit = default) where TMonad<>: IMonad<TMonad<>> => 
        TMonad<Unit>.Unit<Unit>(unit);
}

// Cannot be compiled.
public static partial class MonadExtensions // Monad (Multiply, Unit) implements applicative functor (Apply, Wrap).
{
    // Apply: (TMonad<TSource -> TResult>, TMonad<TSource>) -> TMonad<TResult>
    public static TMonad<TResult> Apply<TMonad<>, TSource, TResult>(
        this TMonad<Func<TSource, TResult>> selectorWrapper, 
        TMonad<TSource> source)  where TMonad<> : IMonad<TMonad<>> =>
            (from selector in selectorWrapper
             select (from value in source
                     select selector(value))).Multiply();
            // selectorWrapper.Select(selector => source.Select(value => selector(value))).Multiply();

    // Wrap: TSource -> TMonad<TSource>
    public static TMonad<TSource> Wrap<TMonad<>, TSource>(
        this TSource value) where TMonad<>: IMonad<TMonad<>> => TMonad<TSource>.Unit<TSource>(value);
}

So the monad definition can be updated to implement monoidal functor and applicative functor too:

// Cannot be compiled.
public partial interface IMonad<TMonad<>> : IMonoidalFunctor<TMonad<>>, IApplicativeFunctor<TMonad<>>
{
}

More LINQ to Monads

Many other open generic type definitions provided by .NET can be monad. Take Lazy<> functor as example, first, apparently it is a type constructor of kind * –> *. Then, its SelectMany query method can be defined as extension method:

public static partial class LazyExtensions // Lazy<T> : IMonad<Lazy<>>
{
    // Multiply: Lazy<Lazy<TSource> -> Lazy<TSource>
    public static Lazy<TSource> Multiply<TSource>(this Lazy<Lazy<TSource>> sourceWrapper) =>
        sourceWrapper.SelectMany(Id, False);

    // Unit: TSource -> Lazy<TSource>
    public static Lazy<TSource> Unit<TSource>(TSource value) => Lazy(value);

    // SelectMany: (Lazy<TSource>, TSource -> Lazy<TSelector>, (TSource, TSelector) -> TResult) -> Lazy<TResult>
    public static Lazy<TResult> SelectMany<TSource, TSelector, TResult>(
        this Lazy<TSource> source,
        Func<TSource, Lazy<TSelector>> selector,
        Func<TSource, TSelector, TResult> resultSelector) => 
            new Lazy<TResult>(() => resultSelector(source.Value, selector(source.Value).Value));
}

Its Wrap method has been implemented previously, as a requirement of applicative functor. The following is an example of chaining operations into a workflow with Lazy<> monad:

internal static void Workflow()
{
    Lazy<string> query = from filePath in new Lazy<string>(Console.ReadLine)
                         from encodingName in new Lazy<string>(Console.ReadLine)
                         from encoding in new Lazy<Encoding>(() => Encoding.GetEncoding(encodingName))
                         from fileContent in new Lazy<string>(() => File.ReadAllText(filePath, encoding))
                         select fileContent; // Define query.
    string result = query.Value; // Execute query.
}

Since SelectMany implements deferred execution, the above LINQ query is pure and the workflow is deferred. When the query is executed by calling Lazy<>.Value, the workflow is started.

Func<> functor is also monad, with the following SelectMany:

public static partial class FuncExtensions // Func<T> : IMonad<Func<>>
{
    // Multiply: Func<Func<T> -> Func<T>
    public static Func<TSource> Multiply<TSource>(this Func<Func<TSource>> sourceWrapper) => 
        sourceWrapper.SelectMany(source => source, (source, value) => value);

    // Unit: Unit -> Func<Unit>
    public static Func<TSource> Unit<TSource>(TSource value) => Func(value);

    // SelectMany: (Func<TSource>, TSource -> Func<TSelector>, (TSource, TSelector) -> TResult) -> Func<TResult>
    public static Func<TResult> SelectMany<TSource, TSelector, TResult>(
        this Func<TSource> source,
        Func<TSource, Func<TSelector>> selector,
        Func<TSource, TSelector, TResult> resultSelector) => () =>
        {
            TSource value = source();
            return resultSelector(value, selector(value)());
        };
}

And the workflow is similar to Lazy<> monad’s workflow, because Lazy<T> is just a wrapper of Func<T> factory function:

internal static void Workflow()
{
    Func<string> query = from filePath in new Func<string>(Console.ReadLine)
                         from encodingName in new Func<string>(Console.ReadLine)
                         from encoding in new Func<Encoding>(() => Encoding.GetEncoding(encodingName))
                         from fileContent in new Func<string>(() => File.ReadAllText(filePath, encoding))
                         select fileContent; // Define query.
    string result = query(); // Execute query.
}

The Optional<> monad is monad too, with the following SelectMany:

public static partial class OptionalExtensions // Optional<T> : IMonad<Optional<>>
{
    // Multiply: Optional<Optional<TSource> -> Optional<TSource>
    public static Optional<TSource> Multiply<TSource>(this Optional<Optional<TSource>> sourceWrapper) =>
        sourceWrapper.SelectMany(source => source, (source, value) => value);

    // Unit: TSource -> Optional<TSource>
    public static Optional<TSource> Unit<TSource>(TSource value) => Optional(value);

    // SelectMany: (Optional<TSource>, TSource -> Optional<TSelector>, (TSource, TSelector) -> TResult) -> Optional<TResult>
    public static Optional<TResult> SelectMany<TSource, TSelector, TResult>(
        this Optional<TSource> source,
        Func<TSource, Optional<TSelector>> selector,
        Func<TSource, TSelector, TResult> resultSelector) => new Optional<TResult>(() =>
            {
                if (source.HasValue)
                {
                    Optional<TSelector> result = selector(source.Value);
                    if (result.HasValue)
                    {
                        return (true, resultSelector(source.Value, result.Value));
                    }
                }
                return (false, default);
            });
}

The LINQ workflow of Optional<> monad is also pure and deferred, where each operation in the chaining is an Optional<T> instance:

internal static void Workflow()
{
    string input;
    Optional<string> query =
        from filePath in new Optional<string>(() => string.IsNullOrWhiteSpace(input = Console.ReadLine())
            ? (false, default) : (true, input))
        from encodingName in new Optional<string>(() => string.IsNullOrWhiteSpace(input = Console.ReadLine())
            ? (false, default) : (true, input))
        from encoding in new Optional<Encoding>(() =>
            {
                try
                {
                    return (true, Encoding.GetEncoding(encodingName));
                }
                catch (ArgumentException)
                {
                    return (false, default);
                }
            })
        from fileContent in new Optional<string>(() => File.Exists(filePath)
            ? (true, File.ReadAllText(filePath, encoding)) : (false, default))
        select fileContent; // Define query.
    if (query.HasValue) // Execute query.
    {
        string result = query.Value;
    }
}

So Optional<> covers the scenario that each operation of the workflow may not have invalid result. When an operation has valid result (Optional<T>.HasValue returns true), its next operation executes. And when all all the operations have valid result, the entire workflow has a valid query result.

The ValueTuple<> functor is also monad. Again, its SelectMany cannot defer the call of selector, just like its Select:

public static partial class ValueTupleExtensions // ValueTuple<T, TResult> : IMonad<ValueTuple<T,>>
{
    // Multiply: ValueTuple<T, ValueTuple<T, TSource> -> ValueTuple<T, TSource>
    public static (T, TSource) Multiply<T, TSource>(this (T, (T, TSource)) sourceWrapper) =>
        sourceWrapper.SelectMany(source => source, (source, value) => value); // Immediate execution.

    // Unit: TSource -> ValueTuple<T, TSource>
    public static (T, TSource) Unit<T, TSource>(TSource value) => ValueTuple<T, TSource>(value);

    // SelectMany: (ValueTuple<T, TSource>, TSource -> ValueTuple<T, TSelector>, (TSource, TSelector) -> TResult) -> ValueTuple<T, TResult>
    public static (T, TResult) SelectMany<T, TSource, TSelector, TResult>(
        this (T, TSource) source,
        Func<TSource, (T, TSelector)> selector,
        Func<TSource, TSelector, TResult> resultSelector) =>
            (source.Item1, resultSelector(source.Item2, selector(source.Item2).Item2)); // Immediate execution.
}

So its workflow is the immediate execution version of Lazy<> monad’s workflow:

public static partial class ValueTupleExtensions
{
    internal static void Workflow()
    {
        ValueTuple<string> query = from filePath in new ValueTuple<string>(Console.ReadLine())
                                   from encodingName in new ValueTuple<string>(Console.ReadLine())
                                   from encoding in new ValueTuple<Encoding>(Encoding.GetEncoding(encodingName))
                                   from fileContent in new ValueTuple<string>(File.ReadAllText(filePath, encoding))
                                   select fileContent; // Define and execute query.
        string result = query.Item1; // Query result.
    }
}

The Task<> functor is monad too. Once again, its SelectMany is immediate and impure, just like its Select:

public static partial class TaskExtensions // Task<T> : IMonad<Task<>>
{
    // Multiply: Task<Task<T> -> Task<T>
    public static Task<TResult> Multiply<TResult>(this Task<Task<TResult>> sourceWrapper) =>
        sourceWrapper.SelectMany(source => source, (source, value) => value); // Immediate execution, impure.

    // Unit: TSource -> Task<TSource>
    public static Task<TSource> Unit<TSource>(TSource value) => Task(value);

    // SelectMany: (Task<TSource>, TSource -> Task<TSelector>, (TSource, TSelector) -> TResult) -> Task<TResult>
    public static async Task<TResult> SelectMany<TSource, TSelector, TResult>(
        this Task<TSource> source,
        Func<TSource, Task<TSelector>> selector,
        Func<TSource, TSelector, TResult> resultSelector) =>
            resultSelector(await source, await selector(await source)); // Immediate execution, impure.
}

So the following LINQ workflow with Task<> monad is also immediate and impure:

internal static async Task WorkflowAsync(string uri)
{
    Task<string> query = from response in new HttpClient().GetAsync(uri) // Return Task<HttpResponseMessage>.
                         from stream in response.Content.ReadAsStreamAsync() // Return Task<Stream>.
                         from text in new StreamReader(stream).ReadToEndAsync() // Return Task<string>.
                         select text; // Define and execute query.
    string result = await query; // Query result.
}

It is easy to verify all the above SelectMany methods satisfy the monad laws, and all the above (Multiply, Unit) methods preserve the monoid laws. However, not any SelectMany or (Multiply, Unit) methods can automatically satisfy those laws. Take the ValueTuple<T,> functor as example, here are its SelectMany and (Multiply, Unit):

public static partial class ValueTupleExtensions // ValueTuple<T, TResult> : IMonad<ValueTuple<T,>>
{
    // Multiply: ValueTuple<T, ValueTuple<T, TSource> -> ValueTuple<T, TSource>
    public static (T, TSource) Multiply<T, TSource>(this (T, (T, TSource)) sourceWrapper) =>
        sourceWrapper.SelectMany(source => source, (source, value) => value); // Immediate execution.

    // Unit: TSource -> ValueTuple<T, TSource>
    public static (T, TSource) Unit<T, TSource>(TSource value) => ValueTuple<T, TSource>(value);

    // SelectMany: (ValueTuple<T, TSource>, TSource -> ValueTuple<T, TSelector>, (TSource, TSelector) -> TResult) -> ValueTuple<T, TResult>
    public static (T, TResult) SelectMany<T, TSource, TSelector, TResult>(
        this (T, TSource) source,
        Func<TSource, (T, TSelector)> selector,
        Func<TSource, TSelector, TResult> resultSelector) =>
            (source.Item1, resultSelector(source.Item2, selector(source.Item2).Item2)); // Immediate execution.
}

The above (Multiply, Unit) implementations cannot preserve the monoid left unit law:

internal static void MonoidLaws()
{
    (string, int) source = ("a", 1);

    // Associativity preservation: source.Wrap().Multiply().Wrap().Multiply() == source.Wrap().Wrap().Multiply().Multiply().
    source
        .ValueTuple<string, (string, int)>()
        .Multiply()
        .ValueTuple<string, (string, int)>()
        .Multiply()
        .WriteLine(); // (, 1)
    source
        .ValueTuple<string, (string, int)>()
        .ValueTuple<string, (string, (string, int))>()
        .Multiply()
        .Multiply()
        .WriteLine(); // (, 1)
    // Left unit preservation: Unit(f).Multiply() == source.
    Unit<string, (string, int)>(source).Multiply().WriteLine(); // (, 1)
    // Right unit preservation: source == source.Select(Unit).Multiply().
    source.Select(Unit<string, int>).Multiply().WriteLine(); // (a, 1)
}

And the above SelectMany implementation breaks the left unit monad law too:

internal static void MonadLaws()
{
    ValueTuple<string, int> source = ("a", 1);
    Func<int, ValueTuple<string, char>> selector = int32 => ("b", '@');
    Func<int, ValueTuple<string, double>> selector1 = int32 => ("c", Math.Sqrt(int32));
    Func<double, ValueTuple<string, string>> selector2 = @double => ("d", @double.ToString("0.00"));
    const int Value = 5;

    // Associativity: source.SelectMany(selector1).SelectMany(selector2) == source.SelectMany(value => selector1(value).SelectMany(selector2)).
    (from value in source
        from result1 in selector1(value)
        from result2 in selector2(result1)
        select result2).WriteLine(); // (a, 1.00)
    (from value in source
        from result in (from result1 in selector1(value) from result2 in selector2(result1) select result2)
        select result).WriteLine(); // (a, 1.00)
    // Left unit: value.Wrap().SelectMany(selector) == selector(value).
    (from value in Value.ValueTuple<string, int>()
        from result in selector(value)
        select result).WriteLine(); // (, @)
    selector(Value).WriteLine(); // (b, @)
    // Right unit: source == source.SelectMany(Wrap).
    (from value in source
        from result in value.ValueTuple<string, int>()
        select result).WriteLine(); // (a, 1)
}

85 Comments

  • فروش کولر گازی

  • thats perfect and good

  • veryyy thaanks

  • Amazon.com/mytv - enter the 6 digit amazon mytv code you receive at screen at www.amazon.com/mytv to regiter your device. contact amazon support for hel

  • خبرآنلاین سایت خبررسان

  • This article is really fantastic and thanks for sharing the valuable post.

  • This post is really astounding one! I was delighted to read this, very much useful. Many thanks

  • Great Article it its really informative and innovative keep us posted with new updates. its was really valuable. 

  • Thanks for writing such a good article, I stumbled onto your blog and read a few post. I like your style of writing...

  • Great Post !! Very interesting topic will bookmark your site to check if you write more about in the future.

  • Very interesting topic will bookmark your site to check if you Post more about in the future.

  • I am coming back to your blog for more soon

  • I am coming back to your blog for more soon

  • I am coming back to your blog for more soon

  • I am coming back to your blog for more soon

  • I am coming back to your blog for more soon

  • I am coming back to your blog for more soon

  • ما به عنوان دبستان غیر دولتی پیشرو برای اولین بار در ایران با ارائه طرح کیف در مدرسه توانستیم گام به گام با آموزش نوین دنیا پیش رفته و کیفیت آموزش را ارتقا بخشیم و توانایی کودکانمان را در تمامی مهارت های زندگی مانند ایجاد تفکر واگرا و همگرا ، قدرت حل مسئله ، مسئولیت پذیری ،عزت نفس و توجه و تمرکز در آنان ایجاد نموده و در آموزش کامپیوتر و زبان انگلیسی که از مهارت های بسیار لازم فردای کودکانمان است همواره پیشگام بوده ایم.

  • I am no longer positive where you’re getting your info, however great topic.

  • You have noted very interesting points ! ps nice site

  • whoah this blog is magnificent i love reading your posts. Keep up the good work!

  • Excellent post however I was wondering if you could write a litte more on this subject?

  • I really like reading a post that will make men and women think.

  • This article is very useful for everyone as it is the well explained.

  • Very useful blog for increasing knowledge.

  • مصمم مواقع ووردبريس

  • It was great , i was looking for this tutorial

  • This would be a lot more useful if it could actually compile. Maybe a second post where you encode the open TMonad generics as their equivalent encodings rather than the unsupported HKT?

  • Посмотри здесь ее оргазм https://pornmondial.com/cat/6/gruppovoe: на сайте

  • Посмотри здесь ее оргазм: на сайте...

  • Nice post. I used to be checking forever this weblog and I am impressed. Very useful information, Specially the last time I take care of such in order a lot. I wish to give opinion you some interesting things or tips. I used to be looking for this hopeful information for a long time. Thanks and good luck. I wish for to read more belongings about it. You can also get better your language by reading English novels, newspaper and also by watching films get it here. If it is the first time you are trying to write my custom essay is then there are lots of errors in your writing.

  • https://ma-study.blogspot.com/

  • The ICICI Bank (Industrial Credit and Investment Corporation of India) has offered multiple methods along with Toll-Free Numbers to check the account’s available balance and access to various banking facilities. https://jnanabhumiap.in/icici-bank-balance-check-number/ By using the ICICI Balance Check Number, customers can access their final available balance of the fund through Missed Call Banking, SMS Banking, USSD, WhatsApp, and other ways. There are multiple online & offline ways are introduced to know account balance. Advised to use mobile banking facilities to quick balance enquiry from follow above link.

  • ارتباط گیم تایم به شدولند
    از همان روز اولی که شدولند به دنیای world of warcraft آمد گیم تایم نیز ارائه شد. می توان گفت که اصلی ترین هدف ارتباط گیم تایم به شدولند جلوگیری از چیت زدن است. چرا که برای اینکه شما بتوانید گیم تایم را بازی کنید باید هزینه زیادی را پرداخت کنید. از طرفی دیگر قوی کردن سرور ها است. بعد از به وجود آمدن سرور های گیم تایم سرور های بازی خود وارکرافت نیز قوی تر شده است.




    سخن آخر خرید گیم تایم 60 روزه
    جمع بندی که می توان از این مطلب داشته باشیم این است که شما می توانید برای خرید گیم تایم 60 روزه از فروشگاه جت گیم آن را خریداری کنید. گیم تایم 60 روزه دارای سرور اروپا و آمریکا است که بهتر است سرور گیم تایم شما با شدولند شما یکی باشد تا از لحاظ پینگی مشکلی را به وجود نیاورد. امیدوارم مطالب برای علاقمندان این گیم جذاب مفید قرار گرفته باشه با تشکر.

  • برخی از بازی های  شرکت بلیزارد بصورت رایگان دردسترس گیمرها و کاربران نخواهد بود. و این کاربران برای استفاده از بازی  گیم تایم یا همان گیم کارت خریداری کنند. یکی از این بازی ها،‌ بازی محبوب و پرطرفدار ورلدآف وارکرافت است. به شارژ ماهیانه بازی وارکرافت در سرورهای بازی بلیزارد  گیم تایم می گویند ، که در فروشگاه جت گیم موجود می باشد.

    خرید گیم تایم 60 روزه ازفروشگاه جت گیم:

    در واقع گیم تایم 60 روزه نمونه ای جدید است از گیم تایم ها برای استفاده دربازی World of Warcraft  . که در ادامه بیشتر در مورد این محصول و نحوه استفاده از آن توضیح می دهیم .

    شما با خرید گیم تایم 60 روزه در مدت زمان آن گیم تایم ( 60 روز ) به امکاناتی در بازی World of Warcraft درسترسی پیدا خواهید کرد که این امکانات شامل موارد زیر میباشند :

    1 - اجازه لول آپ کردن تا لول 50 ( بدون گیم تایم فقط می توانید تا لول 20 بازی کنید )

    2 - اجازه  چت کردن با دیگران درون بازی ( بدون گیم تایم نمی توانید در بازی  چت کنید )

    3 - دسترسی به بازی World of Warcraft Classic

    در نتیجه برای بازی در World of Warcraft حتمآ به تهیه گیم تایم نیاز دارید.

    نکته 1 : گیم تایم یا همان زمان بازی ورد اف وارکرفت برای توانایی انلاین بازی کردن استفاده می شود و بدون گیم تایم امکان بازی کردن بازی محبوب ورد اف وارکرفت را نخواهید داشت.

    نکته 2 : درصورتی که گیم تایم نداشته باشید امکان بازی ورد اف وارکرفت کلاسیک را ندارید و شما میتوانید جهت خرید این محصول از وبسایت ما اقدام نمایید

    نکته 3 : نیازی به وارد کردن مشخصات اکانت بلیزارد شما نمی باشد زیرا کد گیم تایم  توسط خود شما و پس از دریافت کد، وارد می شود  ( آموزش وارد کردن در پایین صفحه قرار دارد )

  • VERY INTERESTING, I WISH TO SEE MUCH MORE LIKE THIS. THANK YOU FOR SHARING THIS KIND OF INFORMATION!

  • WHAT'S UP, I READ YOUR BLOGS, YOUR WRITING SKILL IS SO GOOD, KEEP IT UP!

  • HI, EXCELLENT BLOG. THIS IS HELPFUL FACTS TO US, KEEP IT UP.
    HAVE A GREAT DAY 🤞

  • HELLO! I HAVE READ YOUR POST. THIS IS A GREAT JOB, I WANT TO SAY THANK YOU FOR THIS POST. 😘😘😘

  • very helpful post .Feel Free to ask any questions .

  • Thanks for sharing your info. I truly appreciate your efforts and I am waiting for your next post thanks once again.

  • You are wonderful! Thanks! I will definitely comeback.

  • ข่าวสด Pg slot วันนี้ ข่าวสดวันนี้ เล่นเกมกับ Pg slot เล่นง่ายเพียงไม่กี่นาที มีเกมให้เล่นมากกว่า100เกม เเตกง่ายได้รับความสนุกสนาน อัปเดตเกมใหม่อย่าต่อเนื่อง ได้เจอกับเกมมากมายสุดฟิน https://pg-slot.game/

  • Freelance project, the best website design for all types of medical, corporate, lawyer websites
    Designing store, personal, industrial websites, etc.
    SEO site (increasing the ranking of the site in the Google search engine)
    Responsive site design and high loading speed

  • I've been searching for hours on this topic and finally found your post. Keonhacai I have read your post and I am very impressed. We prefer your opinion and will visit this site frequently to refer to your opinion. When would you like to visit my site?

  • This article was in one word very good
    you exist

  • Your explanation is organized very easy to understand!!! I understood at once. Could you please post about ?? Please!!

  • HI, GOOD DAY! YOU MADE SUCH AN INTERESTING PIECE TO READ, THIS IS GREAT!
    THANKS FOR SHARING THIS!

  • HI! I NEED TO SAY THANK YOU FOR THIS FANTASTIC POST THAT I READ AT THIS MOMENT, I REALLY LIKED EVERY PART OF THIS POST. THANKS FOR THIS KIND OF ARTICLE.

  • THIS IS SUCH A GREAT POST AND I WAS THINKING MUCH THAT SAME AS TO MYSELF. THANK YOU!

  • بیش از 400 پارک ملی در اروپا وجود دارد و مناظر آن تا جایی که می تواند متنوع است، از سواحل آفتابی زیبا گرفته تا کوه های آلپ خیره کننده و پوشیده از برف گرفته تا تندراهای اسکاندیناوی.

  • UFAU388 เว็บไซต์เดิมพันออนไลน์ชั้นนำของประเทศไทย

  • Satisfying blog I am highly satisfied I have been here

  • This didn’t just inspire this also teaches me a lot good ideas.

  • Your article has proven very informative and very useful to me. It was very obvious that you are much knowledgeable in this area.

  • What an amazing article. Genuinely love it, thank you! I really adore your blog.

  • خریدبازیDiablo Prime Evil Collectionفضای بیشتر برای غارت بیشتر
    همه اقلامی را که به سختی به دست آورده اید در انبار شخصی جدید و توسعه یافته ذخیره کنید. سپس، از برگه ذخیره مشترک استفاده کنید تا غنائم جنگی را به سرعت و به آسانی به دست شخصیت های دیگر خود بیاورید.

    پیشرفت خود را در هر کجا که بازی می کنید حفظ کنید
    با ویژگی برنامه ریزی شده cross-progression، می توانید به شخصیت های خود دسترسی داشته باشید و پیشرفت آنها را در هر کجا که Diablo II: Resurrected را بازی می کنید حفظ کنید. شخصیت‌ها و غارت‌های خود را در تمامی پلتفرم‌های پشتیبانی شده بیاورید و سطح، پیشرفت جستجو، مهارت‌ها و استعداد خود را حفظ کنید. پیشرفت متقابل به یک حساب Battle.net مرتبط و خرید جداگانه Diablo II: Resurrected برای هر پلتفرم پشتیبانی نیاز دارد.

  • Thank you so much for the detailed explanation.

  • 4 پالادین ها: یا همان جنگجویان مقدس در warcroft گروهی از دورف ها و انسان های باقی مانده از امپراطوری لرداوران هستند که پیروان راه نور و آتش مقدس بشمار می آیند. بزرگ ترین خصم ایشان آندد ها هستند که پالادین ها با استفاده از آتش مقدس خویش به جنگیدن علیه ایشان می پردازند. تقریبا گروه پالادین و جنگجو تفاوتی چندانی با هم نداشته، چون هم از سلاح و هم از لباس های مشابه استفاده می کنند. تنها تفاوت عمده ی این دو کلاس در warcroft انرژی پایه و رشته هاست.

  • article is very good. I like it. It's interesting. Thanks for sharing. Thank you.<a href="https://popmovie888.com/" rel="bookmark" title=" หนังใหม่ชนโรง "> หนังใหม่ชนโรง </a>

  • very exciting. I just read this article for the first time, thanks for sharing, thank you.<a href="https://popmovie888.com/" rel="bookmark" title="หนังออนไลน์ 2023 พากย์ไทย">หนังออนไลน์ 2023 พากย์ไทย</a>

  • خرید گیم تایم نیز دراین نسخه در نظر گرفته شده و این مورد برای منتقل کردن بهتر احساسات کاراکتر‌ها به گیمر کمک بسیاری میرساند. البته نا گفته نماند از آنجایی که کلیت دیالوگ‌های گیم در نسخه اصلی هم دارای کیفیت بالایی بود، در نسخه ریمستر نوشته‌ها هیچگونه تغییری نخواهند کرد

  • خرید دراگون فلایت دنیای این گیم بیشتر آشنا کنیم. با انتشار آخرین بسته این گیم که
    نام دارد، بلیزارد  بار دیگر ثابت کرد که می‌تواند همه را راضی نگه دارد. همراهمان باشید تا به آموزش WoW برای تازه واردان بپردازیم.

  • Satisfying blog I am highly satisfied I have been here

  • Now you will receive the successful purchase message in your email inbox. Here are some most popular Error Code from Disney Plus

  • You really are well-rounded. every story you write It's something new, not monotonous, unique, I really like it.

  • Durch die Integration von Generative Fill in Photoshop hat Adobe die KI-Bildbearbeitung einem breiteren Publikum zugänglich gemacht. Schauen wir uns genauer an, was dieses Tool kann und welche kreativen Möglichkeiten es bietet. https://pavzi.com/de/so-verwenden-sie-das-ai-generative-fill-tool-von-photoshop/ Sie müssen einige einfache Anforderungen erfüllen, um das Generative AI-Tool von Adobe AI verwenden zu können. Sie können das Generative AI-Tool von Adobe AI Photoshop optimal nutzen, um Ihre Bearbeitungsfähigkeiten zu verbessern und seine kreativen Möglichkeiten zu erkunden, indem Sie diese Anforderungen erfüllen.

  • لباس بچه‌گانه یکی از خرید‌های سخت در عین حال دلچسب برای والدین و کودکان است. بچه‌ها سلیقه خیلی متفاوتی دارند و جذابیت ظاهر لباس برایشان اهمیت زیادی دارد. این امر خرید لباس نوزاد را برای والدین دشوار می‌کند. علاوه بر این، کیفیت لباس نیاز به توجه زیادی دارد. بنابراین سایز، کیفیت و زیبایی از نکات مهم در خرید لباس بچه گانه است. اما مطمئناً پیگیری همه این موارد برای بسیاری از والدین آسان نیست. در این مطلب با ما همراه باشید تا اطلاعات کافی در این زمینه کسب کنید.

  • بسیار عالی

  • I REALLY LOVED WHAT YOU HAD TO SAY, AND MORE THAN THAT, HOW YOU PRESENTED IT. TOO COOL!

  • I AM SATISFIED TO SEARCH OUT A LOT OF HELPFUL INFORMATION RIGHT HERE IN THE PUBLISH, WE WANT WORK OUT MORE TECHNIQUES ON THIS REGARD, THANKS FOR SHARING!

  • I WAS STUDYING SOME OF YOUR CONTENT ON THIS INTERNET SITE AND I THINK THIS INTERNET SITE IS VERY INFORMATIVE!

  • I AM ACTUALLY HAPPY TO READ THIS POSTS WHICH CARRIES PLENTY OF HELPFUL DATA.

  • THANKS FOR PROVIDING THIS KINDS OF DATA.

  • Tak mungkin kamu menemukan situs terbaik selain di <a href="https://bursa188.pro/"rel="dofollow">BURSA188</a> <a href="https://bursa188.store/"rel="dofollow">BURSA188</a>

  • amazing blog to read, thanks for sharing, and move on.thanks for sharing this type of useful article to read. this is nice blog thanks for sharing.

  • کاور شوفاژ یا کاور رادیاتور یکی از کاربردی ترین محصولات است که از آن جهت پوشاندن رادیاتور ها استفاده می شود، کاور شوفاژ علاوه بر محافظت از شوفاژ ظاهری شبیه به کنسول چوبی داشته و موجب زیبایی بخشی به فضای محیط می شود.

    https://tikhomekala.com/portfolio/heater-cover/

  • گروه تولیدی panel systems (پلاستوفوم مرکزی)با 20 سال سابقه فعالیت در زمینه صنعت ساختمان و انجام بیش از 1000 پروژه مختلف با سازه 3d panel افتخار دارد که در خدمت ...

  • Let me read your articles every day. Because it's really fun.

  • I AM PLEASED TO FIND A LOT OF USEFUL INFORMATION HERE IN THE PUBLICATION; PLEASE HELP US DEVELOP MORE TECHNIQUES IN THIS REGARD!

  • بسیاری از بیماران آقا در منزل و بیمارستان ترجیح می دهند که توسط پرستار آقا مراقبت و نگهداری شوند، بیماران مرد احساس راحتی بیشتری با پرستاران آقا خواهند داشت و بهتر می توانند با آن ها ارتباط برقرار کنند.

  • I have been looking for articles on these topics for a long time. <a href="https://maps.google.ci/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">casinosite</a> I don't know how grateful you are for posting on this topic. Thank you for the numerous articles on this site, I will subscribe to those links in my bookmarks and visit them often. Have a nice day

  • I've been searching for hours on this topic and finally found your post. <a href="https://maps.google.ch/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">baccaratsite</a>, I have read your post and I am very impressed. We prefer your opinion and will visit this site frequently to refer to your opinion. When would you like to visit my site?

  • First of all, thank you for your post. <a href="https://maps.google.cg/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">slotsite</a> Your posts are neatly organized with the information I want, so there are plenty of resources to reference. I bookmark this site and will find your posts frequently in the future. Thanks again ^^

  • I'm writing on this topic these days, <a href="https://maps.google.cf/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">baccaratcommunity</a>, but I have stopped writing because there is no reference material. Then I accidentally found your article. I can refer to a variety of materials, so I think the work I was preparing will work! Thank you for your efforts.

Add a Comment

As it will appear on the website

Not displayed

Your website