Lambda Calculus via C# (6) Combinatory Logic

[FP & LINQ via C# series]

[Lambda Calculus via C# series]

In lambda calculus, the primitive is function, which can have free variables and bound variables. Combinatory logic was introduced by Moses Schönfinkel and Haskell Curry in 1920s. It is equivalent variant lambda calculus, with combinator as primitive. A combinator can be viewed as an expression with no free variables in its body.

Combinator

The following is the simplest function definition expression, with only bound variable and no free variable:

I := λx.x

In combinatory logic it is called I (Id) combinator. The following functions are combinators too:

S := λx.λy.λz.x z (y z)
K := λx.λy.x

Here S (Slider) combinator slides z to between x and y (In some materials S is called Substitution; In presentation of Dana Scott S is called Slider), and K (Killer) combinator kills y.

In C#, just leave the each combinator’s variables as dynamic:

public static partial class SkiCombinators
{
    public static readonly Func<dynamic, Func<dynamic, Func<dynamic, dynamic>>>
        S = x => y => z => x(z)(y(z));

    public static readonly Func<dynamic, Func<dynamic, dynamic>>
        K = x => y => x;

    public static readonly Func<dynamic, dynamic>
        I = x => x;
}

ω is the self application combinator. It applies variable f to f itself:

ω := λf.f f

Just like above f, ω can also be applied with ω itself, which is the definition of Ω:

Ω := ω ω ≡ (λf.f f) (λf.f f)

Here ω is a function definition expression without free variables, and Ω is a function application expression, which contains no free variables. For Ω, its function application can be beta reduced forever:

  (λf.f f) (λf.f f)
≡ (λf.f f) (λf.f f)
≡ (λf.f f) (λf.f f)
≡ ...

So ω ω is an infinite application. Ω is called the looping combinator.

In C#, it is easy to define the type of self applicable function, like above f. Assume the function’s return type is TResult, then this function is of type input –> TResult:

public delegate TResult Func<TResult>(?);

The input type is the function type itself, so it is:

public delegate TResult Func<TResult>(Func<TResult> self)

Above Func<TResult> is the self applicable function type. To be unambiguous with System.Func<TResult>, it can be renamed to SelfApplicableFunc<TResult>:

public delegate TResult SelfApplicableFunc<TResult>(SelfApplicableFunc<TResult> self);

So SelfApplicableFunc<TResult> is equivalent to SelfApplicableFunc<TResult> -> TResult. Since f is of type SelfApplicableFunc<TResult>, f(f) returns TResult. And since ω accept f and returns TResult. ω is of type SelfApplicableFunc<TResult> -> TResult, which is the definition of SelfApplicableFunc<TResult>, so ω is still of type SelfApplicableFunc<TResult>, ω(ω) is still of type TResult:

public static class OmegaCombinators<TResult>
{
    public static readonly SelfApplicableFunc<TResult>
        ω = f => f(f);

    public static readonly TResult
        Ω = ω(ω);
}

SKI combinator calculus

The SKI combinator calculus is a kind of combinatory logic. As a variant of lambda calculus, SKI combinatory logic has no general expression definition rules, or general expression reduction rules. It only has the above S, K, I combinators as the only 3 primitives, and the only 3 function application rules. It can be viewed as a reduced version of lambda calculus, and an extremely simple Turing complete language with only 3 elements: S, K, I.

Take the Boolean values as a simple example. Remember in lambda calculus, True and False are defined as:

True := λt.λf.t
False := λt.λf.f

So that when they are applied:

  True t f
≡ (λt.λf.t) t f
≡ t

  False t f
≡ (λt.λf.f) t f
≡ f

Here in SKI combinator calculus, SKI combinators are the only primitives, so True and False can be defined as:

True := K
False := S K

So that when they are applied, they return the same result as the lambda calculus definition:

  True t f
≡ K t f
≡ t

  False t f
≡ S K t f
≡ K f (t f) 
≡ f

Remember function composition is defined as:

(f2 ∘ f1) x := f2 (f1 x)

In SKI, the composition operator can be equivalently defined as:

Compose := S (K S) K

And this is how it works:

  Compose f2 f1 x
≡ S (K S) K f2 f1 x
≡ (K S) f2 (K f2) f1 x
≡ S (K f2) f1 x
≡ (K f2) x (f1 x)
≡ f2 (f1 x)

In lambda calculus, numerals are defined as:

0 := λf.λx.x
1 := λf.λx.f x
2 := λf.λx.f (f x)
3 := λf.λx.f (f (f x))
...

In SKI, numerals are equivalently defined as:

0 := K I                     ≡ K I
1 := I                       ≡ I
2 := S Compose I             ≡ S (S (K S) K) I
3 := S Compose (S Compose I) ≡ S (S (K S) K) (S (S (K S) K) I)
...

When these numerals are applied, they return the same results as lambda calculus definition:

  0 f x
≡ K I f x
≡ I x
≡ x

  1 f x
≡ I f x
≡ f x

  2 f x
≡ S Compose I f x
≡ Compose f (I f) x
≡ Compose f f x
≡ f (f x)

  3 f x
≡ S Compose (S Compose I) f x
≡ Compose f (S Compose I f) x
≡ Compose f (Compose f f) x
≡ f (f (f x))

...

In SKI, the self application combinator ω is:

ω := S I I

When it is applied with f, it returns f f:

  S I I f
≡ I x (I f) 
≡ f f

So naturally, Ω is defined as:

Ω := (S I I) (S I I)

And it is infinite as in lambda calculus:

  S I I (S I I)
≡ I (S I I) (I (S I I)) 
≡ I (S I I) (S I I) 
≡ S I I (S I I)
...

Actually, I combinator can be defined with S and K in either of the following ways:

I := S K K
I := S K S

And they work the same:

  I x
≡ S K K x
≡ K x (K x)
≡ x

  I x
≡ S K S x
≡ K x (S x)
≡ x

So I is just a syntactic sugar in SKI calculus.

In C#, these combinators can be implemented as:

using static SkiCombinators;

public static partial class SkiCalculus
{
    public static readonly Boolean
        True = new Boolean(K);

    public static readonly Boolean
        False = new Boolean(S(K));

    public static readonly Func<dynamic, dynamic>
        Compose = S(K(S))(K);

    public static readonly Func<dynamic, dynamic>
        Zero = K(I);

    public static readonly Func<dynamic, dynamic>
        One = I;

    public static readonly Func<dynamic, dynamic>
        Two = S(Compose)(I);

    public static readonly Func<dynamic, dynamic>
        Three = S(Compose)(S(Compose)(I));

    // ...

    public static readonly Func<dynamic, Func<dynamic, dynamic>>
        Increase = S(Compose);

    public static readonly Func<dynamic, dynamic>
        ω = S(I)(I);

    public static readonly Func<dynamic, dynamic>
        Ω = S(I)(I)(S(I)(I));

    public static readonly Func<dynamic, dynamic>
        IWithSK = S(K)(K); // Or S(K)(S).
}

SKI compiler: compile lambda calculus expression to SKI calculus combinator

The S, K, I combinators can be composed to new combinator that equivalent to any lambda calculus expression. An arbitrary expression in lambda calculus can be converted to combinator in SKI calculus. Assume v is a variable in lambda calculus, and E is an expression in lambda calculus, the conversion ToSki is defined as:

  1. ToSki (v) => v
  2. ToSki (E1 E2) => (ToSki (E1) (ToSki (E2)))
  3. ToSki (λv.E) => (K (ToSki (E))), if x does not occur free in E
  4. ToSki (λv.v) => I
  5. ToSki (λv1.λv2.E) => ToSki (λv1.ToSki (λv2.E))
  6. ToSki (λv.(E1 E2)) => (S (ToSki (λ.v.E1)) (ToSki (λv.E2)))

Based on these rules, a compiler can be implemented to compile a expression in lambda calculus to combinator in SKI calculus. As mentioned before, the C# lambda expression can be compiled as function, and also expression tree data representing the logic of that function:

internal static void FunctionAsData<T>()
{
    Func<T, T> idFunction = value => value;
    Expression<Func<T, T>> idExpression = value => value;
}

The above idFunction and idExpression shares the same lambda expression syntax, but is executable function, while the idExpression is a abstract syntax tree data structure, representing the logic of idFunction:

Expression<Func<T, T>> (NodeType = Lambda, Type = Func<T, T>)
|_Parameters
| |_ParameterExpression (NodeType = Parameter, Type = T)
|   |_Name = "value"
|_Body
  |_ParameterExpression (NodeType = Parameter, Type = T)
    |_Name = "value"

This metaprogramming feature provides great convenience for the conversion – just build the lambda calculus expression as .NET expression tree, traverse the tree and apply the above rules, and convert the tree to another tree representing the SKI calculus combinator.

A SKI calculus combinator, like above Ω combinator (S I I) (S I I), is a composition of S, K, I. The S, K, I primitives can be represented with a constant expression:

public class CombinatorExpression : Expression
{
    private CombinatorExpression(string name) => this.Name = name;

    public static CombinatorExpression S { get; } = new CombinatorExpression(nameof(S));

    public static CombinatorExpression K { get; } = new CombinatorExpression(nameof(K));

    public static CombinatorExpression I { get; } = new CombinatorExpression(nameof(I));

    public string Name { get; }

    public override ExpressionType NodeType { get; } = ExpressionType.Constant;

    public override Type Type { get; } = typeof(object);
}

The composition can be represented with a function application expression:

public class ApplicationExpression : Expression
{
    internal ApplicationExpression(Expression function, Expression variable)
    {
        this.Function = function;
        this.Variable = variable;
    }

    public Expression Function { get; }

    public Expression Variable { get; }

    public override ExpressionType NodeType { get; } = ExpressionType.Invoke;

    public override Type Type { get; } = typeof(object);
}

So the above Ω combinator (S I I) (S I I) can be represented by the following expression tree:

ApplicationExpression (NodeType = Invoke, Type = object)
|_Function
| |_ApplicationExpression (NodeType = Invoke, Type = object)
|   |_Function
|   | |_ApplicationExpression (NodeType = Invoke, Type = object)
|   |   |_Function
|   |   | |_CombinatorExpression (NodeType = Constant, Type = object)
|   |   |   |_Name = "S"
|   |   |_Variable
|   |     |_CombinatorExpression (NodeType = Constant, Type = object)
|   |       |_Name = "I"
|   |_Variable
|     |_CombinatorExpression (NodeType = Constant, Type = object)
|       |_Name = "I"
|_Variable
  |_ApplicationExpression (NodeType = Invoke, Type = object)
    |_Function
    | |_ApplicationExpression (NodeType = Invoke, Type = object)
    |   |_Function
    |   | |_CombinatorExpression (NodeType = Constant, Type = object)
    |   |   |_Name = "S"
    |   |_Variable
    |     |_CombinatorExpression (NodeType = Constant, Type = object)
    |       |_Name = "I"
    |_Variable
      |_CombinatorExpression (NodeType = Constant, Type = object)
        |_Name = "I"

So in the following SkiCompiler type, the ToSki is implemented to traverse the input abstract syntax tree recursively, and apply the above conversion rules:

public static partial class SkiCompiler
{
    public static Expression ToSki(this Expression lambdaCalculus)
    {
        // Ignore type convertion specified in code or generated by C# compiler.
        lambdaCalculus = lambdaCalculus.IgnoreTypeConvertion();

        switch (lambdaCalculus.NodeType)
        {
            case ExpressionType.Constant:
                // 0. ToSki(S) = S, ToSki(K) = K, ToSki(I) = I.
                if (lambdaCalculus is CombinatorExpression)
                {
                    return lambdaCalculus;
                }
                break;

            case ExpressionType.Parameter:
                // 1. ToSki(v) = v.
                return lambdaCalculus;

            case ExpressionType.Invoke:
                // 2. ToSki(E1(E2)) = ToSki(E1)(ToSKi(E2)).
                ApplicationExpression application = lambdaCalculus.ToApplication();
                return new ApplicationExpression(ToSki(application.Function), ToSki(application.Variable));

            case ExpressionType.Lambda:
                LambdaExpression function = (LambdaExpression)lambdaCalculus;
                ParameterExpression variable = function.Parameters.Single();
                Expression body = function.Body.IgnoreTypeConvertion();

                // 3. ToSki(v => E) = K(ToSki(E)), if v does not occur free in E.
                if (!variable.IsFreeIn(body))
                {
                    return new ApplicationExpression(CombinatorExpression.K, ToSki(body));
                }

                switch (body.NodeType)
                {
                    case ExpressionType.Parameter:
                        // 4. ToSki(v => v) = I
                        if (variable == (ParameterExpression)body)
                        {
                            return CombinatorExpression.I;
                        }
                        break;

                    case ExpressionType.Lambda:
                        // 5. ToSki(v1 => v2 => E) = ToSki(v1 => ToSki(v2 => E)), if v1 occurs free in E.
                        LambdaExpression bodyFunction = (LambdaExpression)body;
                        if (variable.IsFreeIn(bodyFunction.Body))
                        {
                            return ToSki(Expression.Lambda(ToSki(bodyFunction), variable));
                        }
                        break;

                    case ExpressionType.Invoke:
                        // 6. ToSki(v => E1(E2)) = S(ToSki(v => E1))(ToSki(v => E2)).
                        ApplicationExpression bodyApplication = body.ToApplication();
                        return new ApplicationExpression(
                            new ApplicationExpression(
                                CombinatorExpression.S,
                                ToSki(Expression.Lambda(bodyApplication.Function, variable))),
                            ToSki(Expression.Lambda(bodyApplication.Variable, variable)));
                }
                break;
        }
        throw new ArgumentOutOfRangeException(nameof(lambdaCalculus));
    }
}

It calls a few helper functions:

private static Expression IgnoreTypeConvertion(this Expression lambdaCalculus) =>
    lambdaCalculus.NodeType == ExpressionType.Convert
        ? ((UnaryExpression)lambdaCalculus).Operand
        : lambdaCalculus;

private static ApplicationExpression ToApplication(this Expression expression)
{
    switch (expression)
    {
        case ApplicationExpression application:
            return application;
        case InvocationExpression invocation:
            return new ApplicationExpression(invocation.Expression, invocation.Arguments.Single());
    }
    throw new ArgumentOutOfRangeException(nameof(expression));
}

private static bool IsFreeIn(this ParameterExpression variable, Expression lambdaCalculus)
{
    // Ignore type convertion specified in code or generated by C# compiler.
    lambdaCalculus = lambdaCalculus.IgnoreTypeConvertion();

    switch (lambdaCalculus.NodeType)
    {
        case ExpressionType.Invoke:
            ApplicationExpression application = lambdaCalculus.ToApplication();
            return variable.IsFreeIn(application.Function) || variable.IsFreeIn(application.Variable);
        case ExpressionType.Lambda:
            LambdaExpression function = (LambdaExpression)lambdaCalculus;
            return variable != function.Parameters.Single() && variable.IsFreeIn(function.Body);
        case ExpressionType.Parameter:
            return variable == (ParameterExpression)lambdaCalculus;
        case ExpressionType.Constant:
            return false;
    }
    throw new ArgumentOutOfRangeException(nameof(lambdaCalculus));
}

Sometimes, in order to make the lambda calculus expression be compiled, some type information has to be added manually or automatically by C# compiler. These type conversion information is not needed, and can be removed by IgnoreTypeConvertion. In lambda expression, function invocation is compiled as InvocationExpression node with node type Invoke, which is the same as ApplicationExpression. For convenience, ToApplication unifies all Invoke nodes to ApplicationExpression. And IsFreeIn recursively test whether the specified variable occurs free in the specified lambda calculus expression.

Finally, for readability, the following ToSkiString method Converts the compiled SKI calculus expression to string representation:

public static string ToSkiString(this Expression skiCalculus) => skiCalculus.ToSkiString(false);

private static string ToSkiString(this Expression skiCalculus, bool parentheses)
{
    switch (skiCalculus.NodeType)
    {
        case ExpressionType.Invoke:
            ApplicationExpression application = (ApplicationExpression)skiCalculus;
            return parentheses
                ? $"({application.Function.ToSkiString(false)} {application.Variable.ToSkiString(true)})"
                : $"{application.Function.ToSkiString(false)} {application.Variable.ToSkiString(true)}";
        case ExpressionType.Parameter:
            return ((ParameterExpression)skiCalculus).Name;
        case ExpressionType.Constant:
            return ((CombinatorExpression)skiCalculus).Name;
    }
    throw new ArgumentOutOfRangeException(nameof(skiCalculus));
}

The following example demonstrates how to represent 2-tuple in SKI calculus combinator:

internal static void Tuple<T1, T2>()
{
    Expression<Func<T1, Func<T2, Tuple<T1, T2>>>>
        createTupleLambda = item1 => item2 => f => f(item1)(item2);
    Expression createTupleSki = createTupleLambda.ToSki();
    createTupleSki.ToSkiString().WriteLine();
    // S (S (K S) (S (K K) (S (K S) (S (K (S I)) (S (K K) I))))) (K (S (K K) I))
}

To verify the result, a tuple can be created with x as first item, and y as the second item:

  CreateTuple x y
≡ S (S (K S) (S (K K) (S (K S) (S (K (S I)) (S (K K) I))))) (K (S (K K) I)) x y
≡ S (K S) (S (K K) (S (K S) (S (K (S I)) (S (K K) I)))) x (K (S (K K) I) x) y
≡ K S x (S (K K) (S (K S) (S (K (S I)) (S (K K) I))) x) (K (S (K K) I) x) y
≡ S (S (K K) (S (K S) (S (K (S I)) (S (K K) I))) x) (K (S (K K) I) x) y
≡ S (K K) (S (K S) (S (K (S I)) (S (K K) I))) x y (K (S (K K) I) x y)
≡ K K x (S (K S) (S (K (S I)) (S (K K) I)) x) y (K (S (K K) I) x y)
≡ K (S (K S) (S (K (S I)) (S (K K) I)) x) y (K (S (K K) I) x y)
≡ S (K S) (S (K (S I)) (S (K K) I)) x (K (S (K K) I) x y)
≡ K S x (S (K (S I)) (S (K K) I) x) (K (S (K K) I) x y)
≡ S (S (K (S I)) (S (K K) I) x) (K (S (K K) I) x y)
≡ S (K (S I) x (S (K K) I x)) (K (S (K K) I) x y)
≡ S (S I (S (K K) I x)) (K (S (K K) I) x y)
≡ S (S I ((K K) x (I x))) (K (S (K K) I) x y)
≡ S (S I (K (I x))) (K (S (K K) I) x y)
≡ S (S I (K x)) (K (S (K K) I) x y)
≡ S (S I (K x)) (S (K K) I y)
≡ S (S I (K x)) (K K y (I y))
≡ S (S I (K x)) (K (I y))
≡ S (S I (K x)) (K y)

To get the first/second item of the above tuple, apply it with True/False:

  Item1 (CreateTuple x y)
≡ (CreateTuple x y) True
≡ S (S I (K x)) (K y) True
≡ S (S I (K x)) (K y) K
≡ S I (K x) K (K y K)
≡ I K (K x K) (K y K)
≡ K (K x K) (K y K)
≡ K x K
≡ x

  Item2 (CreateTuple x y)
≡ (CreateTuple x y) False
≡ S (S I (K x)) (K y) False
≡ S (S I (K x)) (K y) (S K)
≡ S I (K x) (S K) (K y (S K))
≡ I (S K) (K x (S K)) (K y (S K))
≡ S K (K x (S K)) (K y (S K))
≡ K y (K x (S K) y)
≡ y

So the compiled 2-tuple SKI calculus combinator is equivalent to the lambda calculus expression.

Another example is the logic operator And:

And := λa.λb.a b False ≡ λa.λb.a b (λt.λf.f)

So in C#:

internal static void And()
{
    Expression<Func<Boolean, Func<Boolean, Boolean>>>
        andLambda = a => b => a(b)((Boolean)(@true => @false => @false));
    Expression andSki = andLambda.ToSki();
    andSki.ToSkiString().WriteLine();;
}

Unfortunately, above expression tree cannot be compiled, with error CS1963: An expression tree may not contain a dynamic operation. The reason is, Boolean is the alias of Func<dynamic, Func<dynamic, dynamic>>, and C# compiler does not support dynamic operations in expression tree, like calling a(b) here. At compile time, dynamic is just object, so the solution is to replace dynamic with object, and replace Boolean with object –> object -> object, then the following code can be compiled:

internal static void And()
{
    Expression<Func<Func<object, Func<object, object>>, Func<Func<object, Func<object, object>>, Func<object, Func<object, object>>>>>
        andLambda = a => b => (Func<object, Func<object, object>>)a(b)((Func<object, Func<object, object>>)(@true => @false => @false));
    Expression andSki = andLambda.ToSki();
    andSki.ToSkiString().WriteLine();
    // S (S (K S) (S (S (K S) (S (K K) I)) (K I))) (K (K (K I)))
}

The compilation result can be verified in similar way:

  And True True
≡ S (S (K S) (S (S (K S) (S (K K) I)) (K I))) (K (K (K I))) True True
≡ S (S (K S) (S (S (K S) (S (K K) I)) (K I))) (K (K (K I))) K K
≡ S (K S) (S (S (K S) (S (K K) I)) (K I)) K (K (K (K I)) K) K
≡ K S K (S (S (K S) (S (K K) I)) (K I) K) (K (K (K I)) K) K
≡ S (S (S (K S) (S (K K) I)) (K I) K) (K (K (K I)) K) K
≡ S (S (K S) (S (K K) I)) (K I) K K (K (K (K I)) K K)
≡ S (K S) (S (K K) I) K (K I K) K (K (K (K I)) K K)
≡ K S K (S (K K) I K) (K I K) K (K (K (K I)) K K)
≡ S (S (K K) I K) (K I K) K (K (K (K I)) K K)
≡ S (K K) I K K (K I K K) (K (K (K I)) K K)
≡ K K K (I K) K (K I K K) (K (K (K I)) K K)
≡ K (I K) K (K I K K) (K (K (K I)) K K)
≡ I K (K I K K) (K (K (K I)) K K)
≡ K (K I K K) (K (K (K I)) K K)
≡ K I K K
≡ I K
≡ K
≡ True

  And True False
≡ S (S (K S) (S (S (K S) (S (K K) I)) (K I))) (K (K (K I))) True False
≡ S (S (K S) (S (S (K S) (S (K K) I)) (K I))) (K (K (K I))) K (S K)
≡ (S (K S)) (S (S (K S) (S (K K) I)) (K I)) K (K (K (K I)) K) (S K)
≡ K S K (S (S (K S) (S (K K) I)) (K I) K) (K (K (K I)) K) (S K)
≡ S (S (S (K S) (S (K K) I)) (K I) K) (K (K (K I)) K) (S K)
≡ S (S (K S) (S (K K) I)) (K I) K (S K) (K (K (K I)) K (S K))
≡ S (K S) (S (K K) I) K (K I K) (S K) (K (K (K I)) K (S K))
≡ K S K (S (K K) I K) (K I K) (S K) (K (K (K I)) K (S K))
≡ S (S (K K) I K) (K I K) (S K) (K (K (K I)) K (S K))
≡ S (K K) I K (S K) (K I K (S K)) (K (K (K I)) K (S K))
≡ K K K (I K) (S K) (K I K (S K)) (K (K (K I)) K (S K))
≡ K (I K) (S K) (K I K (S K)) (K (K (K I)) K (S K))
≡ I K (K I K (S K)) (K (K (K I)) K (S K))
≡ K (K I K (S K)) (K (K (K I)) K (S K))
≡ K I K (S K)
≡ I (S K)
≡ S K
≡ False

...

Iota combinator calculus

Another interesting example of combinator logic is Iota combinator calculus. It has only one combinator:

ι := λf.f S K ≡ λf.f (λx.λy.λz.x z (y z)) (λx.λy.x)

That’s the whole combinatory logic. It is an esoteric programming language with minimum element – only 1 single element, but still Turing-complete. With Iota combinator, SKI can be implemented as:

S := ι (ι (ι (ι ι)))
K := ι (ι (ι ι))
I := ι ι

So Iota is as Turing-complete as  SKI. For example:

  I x
≡ ι ι x
≡ (λf.f S K) (λf.f S K) x
≡ (λf.f S K) S K x
≡ (S S K) K x
≡ S K (K K) x
≡ K x ((K K) x)
≡ x

In C#, these combinators can be implemented as:

public static partial class IotaCombinator
{
    public static readonly Func<dynamic, dynamic>
        ι = f => f
            (new Func<dynamic, Func<dynamic, Func<dynamic, dynamic>>>(x => y => z => x(z)(y(z)))) // S
            (new Func<dynamic, Func<dynamic, dynamic>>(x => y => x)); // K
}

public static class IotaCalculus
{
    public static readonly Func<dynamic, Func<dynamic, Func<dynamic, dynamic>>>
        S = ι(ι(ι(ι(ι))));

    public static readonly Func<dynamic, Func<dynamic, dynamic>>
        K = ι(ι(ι(ι)));

    public static readonly Func<dynamic, dynamic>
        I = ι(ι);
}

93 Comments

  • thank you, very useful article

  • گلدن کی به شما در خرید ملک در ترکیه و اخذ اقامت این کشور یاری می رساند.

  • good article

  • Such an acknowledgeable and abstracted article for developing the new generations into the new software like and many other technologies are taken place but we had no choice for the better illustrations from the SEO experts Pakistan they are giving the excellent and adorable website optimizing only for making our website desirable.

  • Wonderful post, Thank you! This article very useful for us.

  • This article is great, I like it very much, thanks to the author for sharing.

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

  • Thanks for Nice and Informative Post. This article is really contains lot more information about This Topic -!!

  • Everything is very open with a precise description of the issues. It was definitely informative. Your site is useful. Thank you for sharing..!

  • Very philosophic as discussion. But I must say that it is debate that change things. Anyway, very good article, thanks for the share.

  • I care for such info a lot. I was seeking this particular information for a very long time. Thank you and good luck.!

  • Amazing how this article help me as a human being that’s help me a lot! This has been a really wonderful post. Thanks for providing this info.

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

  • برند <a href="https://hitemaco.com">هیتما</a> برای اولین بار 25 سال قبل در کشور ایتالیا به ثبت رسید و با تولید سیستم های سردکننده و تهویه مطبوع شروع به کار کرد. از ویژگی های محصولات هیتما، طراحی منحصر به فرد و راندمان بالای این محصول و خدمات پس از فروش گسترده آن میباشد. شما میتوانید برای تهیه محصولات یا کسب اطلاعات بیشتر به وبسایت هیتما مراجعه فرمایید.

  • If you are looking for a Kish tour, you can refer to our site.

  • I am so grateful for your blog.Really looking forward to read more. Really Great.

  • دوربین مدار بسته AHD ,تکنولوژی دوربین مداربسته آنالوگ با کیفیت HD می باشد. این تکنولوژی نسخه پیشرفته دوربین های مداربسته آنالوگ (CVBs) با کیفیت TVL می باشد، که در این نسخه کیفیت تصویر بر پایه مگاپیکسل است. این دوربین ها با نام دوربین مداربسته AHD و یا دوربین مداربسته آنالوگ HD شناخته می شوند.

  • This site was… how do you say it? Relevant!! Finally I have found something that helped me.

  • I believe this web site has got some rattling good info for everyone.

  • Howdy, I think your site could possibly be having browser compatibility problems.

  • Muchos Gracias for your blog article.Thanks Again. Great.

  • it was great , i was looking for this tutorial.

  • Great Post !! Very interesting topic gonna come again every day to check your content and well calculus is very difficult for many students and in the past, this was also difficult for me but now I am expert at calculus and if some wan ts help then just mail me.

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

  • An acknowledged and abstracted article on the development of the next generations of software. Like many other technologies are in into consideration, but we have no choice but to choose the most excellent illustrations provided by the SEO experts. Pakistan they provide the amazing and charming website optimization just to make our website attractive.

  • <a href="https://hot-bet.org">هات بت</a>
    <a href="https://sibbet.org">سیب بت </a>
    <a href="https://hazaratbet.org">حضرات </a>
    <a href="https://tiny-bet.org">تاینی بت </a>
    <a href="https://1xbetting.co">وان ایکس بت </a>
    <a href="https://win-bet.org">وین بت</a>
    <a href="https://pars90.org">پارس ۹۰ </a>
    <a href="https://betbarg.org">بت برگ </a>
    <a href="https://manotobet.org">منوتو بت </a>
    <a href="https://sigaribet.org">سیگاری بت </a>
    <a href="https://enfejar.org">بازی انفجار </a>
    <a href="https://bet-90.org">بت ۹۰ </a>
    <a href="https://balagol.co">بالاگل </a>
    <a href="https://win90.co">وین ۹۰ </a>
    <a href="https://tak-tik.org">تاک تیک </a>
    <a href="https://tak-bet.org">تک بت </a>
    <a href="https://betchi90.org">بت چی نود </a>
    <a href="https://gamekadeh.co">گیم کده </a>
    <a href="https://tehranbet.org">تهران بت </a>
    <a href="https://marshalbet.org">مارشال بت </a>
    <a href="https://dozarb.org">دوضرب </a>
    <a href="https://king-bet.org">کینگ بت</a>
    <a href="https://betbaz.co">بت باز </a>
    <a href="https://mesterbet.org">مستربت </a>
    <a href="https://betforward.win">بت فوروارد </a>
    <a href="https://betfa.win">بت فا </a>
    <a href="https://subra.win">سوبرا </a>
    <a href="https://abt90.org">ای بی تی ۹۰ </a>
    <a href="https://bababet.win">بابابت </a>
    <a href="https://betplus.win">بت پلاس </a>
    <a href="https://mix90.org">میکس ۹۰ </a>
    <a href="https://takbarg.win">تک برگ </a>
    <a href="https://startbet.org">استارت بت</a>
    <a href="https://pinbahis.win">پین باهیس</a>
    <a href="https://bet-cart.org">بت کارت</a>
    <a href="https://cannon-bet.org">کانن بت</a>
    <a href="https://betboro.win">بت برو</a>
    <a href="https://betnub.org">بت ناب</a>
    <a href="https://live-bet.org">لایوبت</a>
    <a href="https://bet45.org">بت 45</a>
    <a href="https://toofanbet.org">طوفان بت</a>
    <a href="https://pasoor.org">بازی پاسور</a>
    <a href="https://takhtenard.win">بازی تخته نرد</a>
    <a href="https://hokm.win">بازی حکم</a>
    <a href="https://rolexbet.org">رولکس بت</a>
    <a href="https://cheetabet.win">چیتا بت</a>
    <a href="https://betfoot.org">بت فوت</a>
    <a href="https://yasbet.org">یاس بت</a>

  • <a title="یاس بت" href="https://yasbet.org">یاس بت</a>

  • Loved reading the above article, really explained everything in detail, the article was very interesting and effective. Thank you and good luck with your upcoming articles.

  • This one is great and has much information. I am happy with the article's quality and presentation. I want to convey my gratitude for the time and work into writing this article. Thanks for sharing this information.

  • Such an understandable and abstracted article for developing new generations into new software such as and many other technologies are taking place, but we had no choice but to go with the better illustrations from the Web Design Company Los Angeles, who are providing excellent and adorable website optimising only to make our website desirable.

  • The ultimate goal is to be the longest snake on the entire board and take down as many Snakes io as you can.

  • Such an understandable and abstracted article for developing new generations into new software such as and many other technologies are occurring, but we had no choice but to go with the better illustrations from Custom Logo Design, who are providing excellent and adorable website optimising only to make our website desirable.

  • We had no choice but to go with the better illustrations from the Book Publishing Service, who provide excellent and adorable writing services at very reasonable prices. Such an understandable and abstracted article for developing new generations into new software such as and many other technologies are taking place, but we had no choice but to go with the better illustrations from the Book Publishing Service, who provide excellent and adorable writing services at very reasonable prices.

  • گیم تایم 60 روزه در حال حاضر تنها گیم تایمی است که از طرف کمپانی blizzard برای بازیکنان گیم ، ورد اف وارکرافت ارائه شده است. در گذشته گیم تایم هایی مانند 30 روزه و 180 روزه هم موجود بود اما به دلیل سیاست های جدید این کمپانی و خط مشی که در نظر گرفته است، تنها گیم تایمی که در حال حاضر امکان فراهم کردن آن برای گیمر های عزیز، گیم تایم 60 روزه می باشد. در ادامه توضیحات جالبی در مورد گیم تایم برای شما جمع آوری کرده ایم که خواندنشان خالی از لطف نیست.

    کاربرد گیم تایم دو ماهه

    در حال حاضر گیم تایم 2 ماهه در تمامی زمینه های world of warcraft کاربرد دارد. اما اگر می خواهید که یک سری تجربه های جذاب و جدید را تجربه کنید باید این گیم تایم را خریداری کنید. این تجربه ها عبارتند از:
    استفاده از اکسپنشن های جدید
    بازی در مپ های جدید
    لول آپ به سبک جدید
    تغییر در شکل بازی

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

  • When I read an article on this topic, Keonhacai the first thought was profound and difficult, and I wondered if others could understand.. My site has a discussion board for articles and photos similar to this topic. Could you please visit me when you have time to discuss this topic?

  • As I am looking at your writing, Keo nha cai I regret being unable to do outdoor activities due to Corona 19, and I miss my old daily life. If you also miss the daily life of those days, would you please visit my site once? My site is a site where I post about photos and daily life when I was free.

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

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

  • Nice post. I am very impressed with the articles. Keep sharing

  • Buy 60 days game time from Jet Game

    If you are looking to buy a 60-day game time for your World of Warcraft game, you can visit the Jet Game store. One of the features of this store is that it is instant. After paying the price, the product code will be delivered to you as soon as possible. Currently, the advantage of the Jet Game store is that it is faster than other stores. And it is operating with an experienced staff and with the support of products provided to users at the most appropriate price.

    The best way to activate 60 days game time
    The easiest way and the best way to activate Gametime is to present it to the Battlenet client. A code will be sent to you after you purchase 60 days of game time from Jet Game. You must enter this code in the Battlenet client in the Redem a Code section to activate the 60-day game time for you. All Blizzard products are available from the Jet Game site.

  • خریدCall of Duty® پویش
    Black Ops Cold War طرفداران را به اعماق نبرد ژئوپلیتیک ناپایدار جنگ سرد در اوایل دهه 1980 می کشاند. در کمپین تک‌نفره جذاب، هیچ‌چیز آنطور که به نظر می‌رسد نیست، جایی که بازیکنان با چهره‌های تاریخی و حقایق سخت روبرو می‌شوند، زیرا در سراسر جهان از طریق مکان‌های نمادین مانند برلین شرقی، ویتنام، ترکیه، مقر KGB شوروی می‌جنگند. و بیشتر.

    خریدCall of Duty® بخش چند نفره
    در Call of Duty®: Black Ops Cold War زرادخانه جنگ سرد از سلاح ها و تجهیزات را به نسل بعدی Multiplayer بیاورید. در عملیات‌های انکارناپذیر به‌عنوان یک عامل زبده با استفاده از ابزارهای پیشرفته حرفه‌ای در تجربیات مختلف، از درگیری‌های کوچک تا جنگ‌های همه جانبه، با سوخت خودرو، شرکت کنید.
    تهیه از سایت جت گیم

  • Are you really the one who wrote it? It's really good. Everything fits really well. I am fascinated by the article you write. like being trapped in a reverie it's really good.

  • When I read an article on this topic, casino online the first thought was profound and difficult, and I wondered if others could understand.. My site has a discussion board for articles and photos similar to this topic. Could you please visit me when you have time to discuss this topic?

  • Muchos Gracias for your blog article.Thanks Again. Great.

  • I am so happy to come across this piece of write up, very much advanced my understanding to the next top level. Great job and continue to do same.

  • I like your all post. You have done really good work. Thank you for the information you provide, it helped me a lot.

  • Digital consultancy is a professional service that helps businesses to improve their online presence, digital marketing efforts, and overall digital strategy. It involves working with clients to identify their digital goals, analyzing their current situation, recommending solutions, and providing guidance on the implementation of these solutions. Digital consultants work with clients across various industries and may specialize in areas such as search engine optimization (SEO), social media marketing, website design and development, e-commerce, and digital transformation. The aim of digital consultancy is to help businesses to make the most of the opportunities provided by digital technologies and to achieve their digital objectives.

  • This is very useful for me and I think your post is also useful for the world.

  • thank you for sharinh this blog.

  • I like to read books very much. Your blog has many types of books. I hope that your next post will be of great help to me and that I will come across an interesting book.

  • Such an understandable and abstracted article for developing new generations into new software such as and many other technologies are occurring, but we had no choice but to go with the better illustrations from Custom Logo Design, who are providing excellent and adorable website optimising only to make our website desirable.

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

  • I KNOW THIS IS EXTREMELY WONDERFUL POST, SO THAT IS WHY I WANT TO VISIT ALL TIME HERE, HAVE A NICE DAY EVERYONE! THANKS

  • WE ARE REALLY GRATEFUL FOR YOUR BLOG, THIS IS THE BLOG THAT I WAS REALLY EXACTLLY SEARCHING FOR. THANKS AND PLEASE KEEP IT UP THE GOOD WORK!

  • THANK YOU FOR SHARING THIS KIND OF GREAT INFORMATION, A GREAT BLOG. I WILL DEFENITELY BACK.

  • This is an exciting and thrilling car drifting game that puts players in control of a powerful sports car.

  • Now you will receive the successful purchase message in your email inbox. Please read it in detail as the details and the expiry of your plan will be provided in it.

  • Everything looks different but interesting in itself. Are you really the one who wrote it? Very good.

  • طراحی و ساخت انواع تابلو برق صنعتی

  • Thank you for saving me from this dilemma. Immediately after searching the Internet and getting a non-powerful solution, I thought my life was over. Not only is it important to be alive without a strategy for the issues you've outlined through this website, but if I hadn't come across your blog, it could have adversely affected my entire career. Your basic expertise and kindness in touching everything was excellent. I don't know what I would have done if I hadn't been in this situation. I can enjoy my future now. Thank you very much for your high quality and reasonable help. I wouldn't think twice about suggesting a website to someone who needs guidance on this topic. 메이저놀이터 https://szzzac.com/

  • How amazing this article is. In my opinion, every paragraph is well explained so I could easily understand. I can also share my opinion here.

  • 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>

  • I like this blog; highly recommended<a href="https://stanfordglobaleducation.com/"> STUDY ABROAD</a>

  • This is actually the kind of information I have been trying to find. Thank you for writing this information.

  • Don't stop at writing articles. Because I will not stop reading your articles. Because it's really good.

  • Get expert assistance in setting up your business in Dubai & UAE with ease. Our website offers comprehensive solutions for company formation, registration, visa and license services. Start your entrepreneurial journey today! <a href="https://businesssetupservicesindubai.com/">Business Setup Services in Dubai</a>

  • Nice blog<a href="https://certificateattestation.ae/">Certificate Attestation</a>

  • Nice blog<a href="https://certificateattestation.ae/">Certificate Attestation</a>

  • Your article has answered the question I was wondering about! I would like to write a thesis on this subject, but I would like you to give your opinion once :D <a href="https://images.google.sh/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">majorsite</a>

  • I came to this site with the introduction of a friend around me and I was very impressed when I found your writing. I'll come back often after bookmarking! <a href="https://images.google.se/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">bitcoincasino</a>

  • From some point on, I am preparing to build my site while browsing various sites. It is now somewhat completed. If you are interested, please come to play with <a href="https://images.google.rw/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">casino online</a> !!

  • I saw your article well. You seem to enjoy <a href="https://images.google.ru/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">totosite</a> for some reason. We can help you enjoy more fun. Welcome anytime :-)

  • Thanks for sharing!
    My name is Tim J. I have been living in the UK and working for UniVapez for the last 2 years. I like research-based work. I love <a href="https://univapez.com/product-category/all-disposable-vapes/"> Disposable Vapes </a>, the best ones are RandM Tornado 9000, Crystal Pro Max 4000, RandM Tornado 7000 and Elux Legend.

  • It’s an amazing blog; <a href="https://businesssetupservicesindubai.com/SPC-free-zone"> SPC Free Zone</a>

  • It’s an amazing blog; <a href="https://businesssetupservicesindubai.com/SPC-free-zone"> SPC Free Zone</a>

  • Es war wirklich toll

  • JNANABHUMI AP provides all the latest educational updates and many more. The main concept or our aim behind this website has been the will to provide resources with full information on each topic https://jnanabhumiap.in/ which can be accessed through the Internet. To ensure that every reader gets what is important and worthy about the topic they search and link to hear from us.

  • Disposable vape Products bundles typically come with a fully charged battery. This means you can start vaping immediately without the need to worry about charging cables or finding power outlets.

  • zarakifabzar.com

  • I am hoping the same best effort from you in the future as well. In fact your creative writing skills has inspired me.

  • Took me time to read all the comments, but I really enjoyed the article. It proved to be Very helpful to me and I am sure to all the commenters here! It’s always nice when you can not only be informed, but also entertained!

  • Fabulous message, you have signified out some amazing factors, I similarly believe this s a really remarkable web site.

  • Hello, this weekend is good for me, since this time i am reading this enormous informative article here at my home.

  • This is a very nice blog that I will definitively come back to more times this year! Thanks for informative post.

  • When your website or blog goes live for the first time, it is exciting. That is until you realize no one but you and your.

  • <a href="https://zarakif.com/">تولیدی کیف ابزار</a>

  • عالی

  • It’s always exciting to read through content from other authors and use something from other sites.

  • I quite like reading through an article that will make people think.

  • I absolutely believe that this site needs a great deal more attention.

  • Thanks again for the article post.Really looking forward to read more. Keep writing.

  • May you never stop developing your thoughts. I'm looking forward to reading your next article.


  • Thank you for contributing to the body of knowledge in [field or industry].

Add a Comment

As it will appear on the website

Not displayed

Your website