This project is read-only.

Jolt : Functor Creation and Manipulation

A functor is a type that generally implements a single function or operation, and optionally contain state that participates in the execution of its operation. Examples of functors are C++ Standard Template Library types std::equal_to and std::less, implementations of the Command pattern, and the .NET System.MulticastDelegate type. All of the types in these examples are usable as a function, but also provide additional state or structure that increase the type's flexibility. The command pattern provides both Do() and Undo() operations, std::equal_to and std::less are both concrete std::binary_function types, and System.MulticastDelegate encapsulates an invocation list.

With the release of Linq in version 3.5 of the .NET Framework, the use of delegates to represent predicates and other functors is becoming more prolific. Consequenly, it is important to understand how to best utilize any delegate-supporting features of your .NET compiler (e.g. C# anonymous delegates). With this in mind, the Jolt.NET functor library aims to facilitate the management of delegates by providing generic operations that promote delegate reuse.

Table of Contents

  1. Code Reuse With Generic Functors
  2. Design Scope
  3. Usage Examples
    1. Function argument binding
    2. Function composition
    3. Adapting Func delegates to Action
    4. Adapting Predicate and equivalent Func delegates
    5. Adapting Action and equivalent EventHandler delegates
    6. Creating idempotent functions
    7. Using the no-operation functors
    8. Using the identity functor
    9. Using the True/False predicates

Code Reuse With Generic Functors

Well-designed functors implement small units of reusable work. Take for example, the following predicate functions for the given Linq queries.

using System.Collections.Generic;
using System.Linq;
 
void string FindString(IEnumerable<string> strings)
{
  return strings.First(s => s == "hello");
}
 
void string FindAnotherString(IEnumerable<string> strings)
{
  return strings.First(s => s != "world");
}

When the C# compiler parses this code, it will generate a function for each of the lambda expressions (1). Although both functions perform different tasks, duplication is present in the form of operator use. The following example demonstrates how to use predefined functors to remove the duplication, and consequently all generated code.

using System;
using System.Collections.Generic;
using System.Linq;
 
void string FindString(IEnumerable<string> strings)
{
  // Assumes existance of parameter binder Bind, and EqualsTo functor.
  Func<string, bool> equalToHello = Bind.Second(EqualsTo, "hello");
  return strings.First(equalToHello);
}
 
void string FindAnotherString(IEnumerable<string> strings)
{
  // Assumes existance of parameter binder Bind, and NotEqualsTo functor.
  Func<string, bool> notEqualToWorld = Bind.Second(NotEqualTo, "world");
  return strings.First(notEqualToWorld);
}

Design Scope

The Jolt functor library is designed to support the following features.
  • Provide generic operations that operate upon and/or create instances of existing .NET delegates
  • Construction of idempotent functions
  • Construction of argument-bound functions
    • Binding an argument to a constant value
    • Binding an argument to the execution and result of another function (i.e. function composition)
  • Adaptors for existing .NET delegates
  • Predefined functors for common operations

Usage Examples


The following example demonstrates how to use Bind to create a System.Action delegate with its first, second, third, or fourth argument bound to a constant value. This example also applies to the System.Func family of delegates.

using System;
using Jolt.Functional;
 
void BindArguments(Action<int, int, int, int> actionToBind)
{
  Action<int, int, int> threeArgs = Bind.Fourth(actionToBind, 123);
  Action<int, int> twoArgs = Bind.Third(threeArgs, 246);
  Action<int> oneArg = Bind.Second(twoArgs, 369);
  Action noArgs = Bind.First(oneArg, 0);
 
  // Equivalent to invoking actionToBind(0, 369, 246, 123)
  noArgs();
}


Function composition is similar to argument binding except that a delegate is bound to an argument instead of a constant value. Consequently, the evaluation of the bound delegate occurs when the composite delegate is invoked. In order for a delegate to bind to an argument, its return type must match the type of the argument that is being bound. Because of this, it is not possible to bind a System.Action delegate to any argument since System.Action does not return a value.

The following example demonstrates how to use Compose to create a System.Func delegate with its first, second, third, or fourth argument bound to an equivalent delegate. This example also applies to the System.Action family of delegates.

using System;
using Jolt.Functional;
 
void ComposeDelegates(Func<int, int, int, int, bool> functorToCompose)
{
  // Equivalent to functorToCompose(no-arg(), int, int, int)
  Func<int> no-arg = delegate { return 0; };
  Func<int, int, int, bool> composite_firstArg = Compose.First(functorToCompose, no-arg);
 
  // Equivalent to functorToCompose(int, one-arg(double), int, int, int)
  Func<double, int> one-arg = delegate(double d) { return 0; };
  Func<int, double, int, int, bool> composite_secondArg = Compose.Second(functorToCompose, one-arg);
 
  // Equivalent to functorToCompose(int, int, two-arg(TimeSpan, TimeSpan), int,)
  Func<TimeSpan, TimeSpan, int> two-arg = delegate(TimeSpan t0, TimeSpan t1) { return 0; };
  Func<int, int, TimeSpan, TimeSpan, int, bool> composite_thirdArg= Compose.Third(functorToCompose, two-arg);

  // Equivalent to functorToCompose(int, int,, int, three-arg(byte, byte, byte))
  Func<TimeSpan, TimeSpan, int> three-arg = delegate(byte b0, byte b1, byte b2) { return 0; };
  Func<int, int, int, byte, byte, byte, bool> composite_fourthArg = Compose.Fourth(functorToCompose, three-arg);
}

The Jolt library supports function composition for one through four argument delegates, with zero through four argument delegates. When composing functions that result in a delegate exceeding four arguments (the maximum supported by the .NET Framework), the Jolt library returns a delegate from its own namespace that supports up to seven arguments. These delegates complement the System.Action and System.Func families, and may be used by any client of the library.


The following example demonstrates how to create a System.Action adaptor for a System.Func delegate that matches in argument signature. The result is a delegate that invokes the given functor, but ignores the return value.

using System;
using Jolt.Functional;
 
void AdaptToAction(Func<int, int, int, int, string> adaptee)
{
  // Also available for 0, 1, 2, and 3 generic arg variants.
  Action<int, int, int, int> funcAdaptor = Functor.ToAction(adaptee);
  
  // Equivalent to calling adaptee(1,2,3,4)
  funcAdaptor(1,2,3,4);
}


The following example demonstrates how to create a System.Predicate adaptor for a predicate expressed as a System.Func delegate, and vice versa.

using System;
using Jolt.Functional;
 
void AdaptPredicates(Func<int, bool> funcPredicate)
{
  Predicate<int> predicate = Functor.ToPredicate(funcPredicate);
  Func<int, bool> anotherFuncPredicate = Functor.ToFuncPredicate(predicate);
  
  // All of the following delegate invocations are equivalent.
  bool result = funcPredicate(100);
  result = predicate(100);
  result = anotherFuncPredicate(100);
}


The following example demonstrates how to create a System.Action adaptor for an event handler expressed as a System.EventHandler delegate, and vice versa.

using System;
using Jolt.Functional;
 
void AdaptEventHandler(Action<object, EventArgs> action)
{
  EventHandler<EventArgs> eventHandler = Functor.ToEventHandler(action);
  Action<object, EventArgs> anotherEventHandler = Functor.ToAction(eventHandler);
 
  // All of the following delegate invocations are equivalent.
  EventArgs args = new EventArgs();
  action(this, args);
  eventHandler(this, args);
  anotherEventHandler(this, args);
}


The following example demonstrates how to create an idempotent System.Func delegate. The result is a
delegate that returns a user-specified value, for any input values.

using System;
using Jolt.Functional;
 
void CreateIdempotencyFunc()
{
  Func<int, int, int, int, string> alwaysHello = Functor.Idempotency<int, int, int, int, string>("hello");
  Func<int, int, int, string> alwaysWorld = Functor.Idempotency<int, int, int, string>("world");
  Func<int, int, object> alwaysNull = Functor.Idempotency<int, int, object>(null);
  Func<int, char> alwaysZ = Functor.Idempotency<int, char>('z');
  Func<TimeSpan> alwaysZero = Functor.Idempotency<TimeSpan>(TimeSpan.Zero);
  
  Random rng = new Random();
  alwaysHello(rng.Next(), rng.Next(), rng.Next(), rng.Next());  // returns "hello"
  alwaysWorld(rng.Next(), rng.Next(), rng.Next());  // returns "world"
  alwaysNull(rng.Next(), rng.Next());  // returns null
  alwaysZ(rng.Next());  // returns 'z';
  alwaysZero();  // returns 00:00:00
}


The following example demonstrates how to obtain a System.Action delegate that returns immediately.

using System;
using Jolt.Functional;
 
void RegisterCallback(Action<int> callback, int callbackParamter);
 
void UtilizeNoOperationAction()
{
  // Also available for 0, 2, 3, and 4 generic arg variants.
  Action<int> callback = Functor.NoOperation<int>();
  
  // Creates a callback that does nothing.
  RegisterCallback(callback, 0);
}


The following example demonstrates how to obtain an identity delegate, always returning the value given in its sole argument.

using System;
using Jolt.Functional;
 
void IdentityTransformation()
{
  int[] array = { 1, 2, 3, 4, 5, 6 };
  
  // Performs an identity transformation on the array.
  Apply(array, Functor.Identity<int>());
}
 
// Applies an element transformation function to each element in an array.
void Apply<T>(T[] array, Func<T, T> transform)
{
  for(int i = 0; i < array.Length; ++i)
  {
    array[i] = transform(array[i]);
  }
}


The following example demonstrates how to obtain predicates that return true or false for any of their input values.

using System;
using System.Linq;
using Jolt.Functional;
 
void ValidateNumbers()
{
  int[] array = {1, 2, 3, 4, 5, 6};
  
  // Returns all elements.
  int[] sixElements = FilterArray(array, Functor.TrueForAll<int>());
  
  // Returns no elements.
  int[] zeroElements = FilterArray(array, Functor.FalseForAll<int>());
}
 
// Filters the elements of an array according to a given predicate.
T[] FilterArray(T[] array, Func<T, bool> isElementValid)
{
  return array.Where(isElementValid).ToArray();
}


[1] Refer to "Anonymous Methods and Jolt Functors: Behind the Scenes" for an analysis of the generated code produced by the C# compiler for anonymous methods.

Last edited Aug 26, 2009 at 3:55 PM by SteveGuidi, version 5

Comments

No comments yet.