Click here to Skip to main content
15,867,594 members
Articles / Programming Languages / F#
Article

Class-less Coding - Minimalist C# and Why F# and Function Programming Has Some Advantages

Rate me:
Please Sign up or sign in to vote.
5.00/5 (16 votes)
7 Aug 2017CPOL34 min read 32.4K   98   18   5
Not very classy in C#, but pretty classy in F#.

Contents

Too Long; Didn't Read

In other words, the "abstract."  Actually, this is fairly long in and of itself!

Can we use just the native .NET classes for developing code, rather than immediately writing an application specific class that often is little more than a container?  Can we do this using aliases, a fluent style, and extension methods?  If we're going to just use .NET classes, we're going to end up using generic dictionaries, tuples, and lists, which gets unwieldy very quickly.  We can alias these types with using statements, but this means copying these using statements into every .cs file where we want to use the alias.  A fluent ("dot-style") notation reduces code lines by representing code in a "workflow-style" notation.  In C#, if we don't write classes with member methods, then we have to implement behaviors as extensions methods.  Using aliases improves semantic readability at one level at the cost of confusing generic type nesting in the alias definition.  Extension methods can be taken too far, resulting in two  rules: write lower level functions for semantic expressiveness, and avoid nested parens that require the programmer to maintain a mental "stack" of the workflow.  In contrast to C#'s using aliases, F# type definitions are not aliases, they are concrete types.  New type definitions can be created from existing types.  Type definitions can also be used to specify a function's parameters and return value.  The forward pipe operator |> is similar to the fluent "dot" notation in C#, but the value on the left of the |> operator "populates" the last parameter in the function's parameter list.  When functions are written that return something, the last function must be piped to the ignore function, which is slightly awkward.  F# type dependencies are based on the order of the files in the project, so a type must be defined before you use it.  In C#, creating more complex aliases get messy real fast -- this is an experiment, not a recommendation for coding practices!  In F#, we don't need an Action or Func class for passing functions because F# inherently supports type definitions that declare a function's parameters and return value -- in other words, everything in functional programming is actually a function.  Tuples are a class in C# but native to functional programming, though C# 6.0 makes using tuples very similar to F#.  While C# allows function parameters to be null, in F#, you have to pass in an actual function, even if the function does nothing.  F# uses a nominal ("by name") as opposed to structural inference engine, Giving types semantically meaningful names is very important so that the type inference engine can infer the correct type.  In C#, changing the members of class doesn't affect the class type.  Not so with F# (at least with vanilla records) -- changing the structure of a record changes the record's type.  Changing the members of a C# class can, among other things, lead to incorrect initialization and usage.   Inheritance, particularly in conjunction with mutable fields, can result in behaviors with implicit understanding like "this will never happen" to suddenly break.  Extension methods and overloading creates semantic ambiguity.  Overloading is actually not supported in F# - functions must have semantically different names, not just different types or parameters lists.  Object oriented programming and functional programming both have their pros and cons, with some hopefully concrete discussion presented here.

Introduction

Summary: Can we use just the native .NET classes for developing code, rather than immediately writing an application specific class that often is little more than a container?  Can we do this using aliases, a fluent style, and extension methods?

Sometimes I ask myself questions intended to challenge my fundamental concepts programming.  The question I recently asked myself regarding C# coding is:

  • Do I really need all those classes?

Obviously there are lots of programming languages where you don't necessarily use classes, like Javascript, where classes are "primarily syntactical sugar over JavaScript's existing prototype-based inheritance." (reference)  My basis for this question is that whenever I start coding something in C#, the first thing I do is write class Foo or static class Foo .  C# enforces this behavior -- everything, even extension methods, needs to be wrapped in a class.  So the question immediately evolved into:

  • When writing in C#, do I really need to implement specialized classes that are containers for data and behaviors?

I decided to answer that question by re-implementing three of the core components that I use in most of my coding:

  1. A service manager
  2. A thread pool (a service)
  3. A semantic publisher/subscriber (another service)

without using any classes other than what the .NET framework provides.  (Don't ask why I use a thread pool instead of Task et al -- the answer is outside of the scope of this article, so just go with it, even if you squawk at the implementation.)  C# (with .NET 4.7) has a rich syntax to leverage the existing .NET framework classes, so let's look at what an implementation without writing any custom classes except the container class for extension methods. 

Image 1 (that's a "side note") -- A secondary aspect of this question is to determine whether code can be written such that it is easier to understand.  I'm a firm believer that the less code you have to read to understand what the code is doing, the more easily understood it becomes.  We'll see how it goes.

So, how we go about doing this?  And why does this lead us into looking at F#?

First, Aliasing Types

Summary: If we're going to just use .NET classes, we're going to end up using generic dictionaries, tuples, and lists, which gets unwieldy very quickly.  We can alias these types with using statements, but this means copying these using statements into every .cs file where we want to use the alias.

One of the conveniences of writing a class is that it expresses a strongly typed alias of something else.  A service manager, for example, needs some way of mapping the service "name" to the service implementation.  The service "name" is a somewhat abstract concept.  For example, it could be a string literal, a type (like an interface), a type, or an enumeration.  String literals are a bad idea because of spelling errors and case sensitivity, and enumerations are a bad idea because the numeric value associated with the enumeration name could change.  Interfaces are a bad idea because the service manager would need a reference to the assembly or assemblies defining the interface.  So, we'll go with a map of types, and we'll use a ConcurrentDictionary because I want the service manager to be thread safe and I don't want to deal with thread safety myself since .NET provides a nice thread safe dictionary:

ConcurrentDictionary<Type, Object>

Image 2 The service implementation is an Object, which I'm not to thrilled with but we'll go with that because I don't want to "pollute" my code with interfaces and type constraints.

Having to write ConcurrentDictionary<Type, Object> anywhere we need to pass in the service manager, like this:

void NeedsAService(ConcurrentDictionary<Type, Object> serviceManager)

is unwieldy, ugly, and doesn't convey anything meaningful in the type.  Yuck.  But we can create an alias instead:

using Services = System.Collections.Concurrent.ConcurrentDictionary<System.Type, object>;

Image 3 Something interesting here - note that I'm not calling this a "service manager" -- it's a collection of service types that map to their implementations, so I call it "Services."

The drawback with this approach is that the alias needs to be defined in every .cs file that uses this type.  Another yuck, but we'll go with it, as I can now write:

void NeedsAService(Services serviceManager)

Image 4 In C#, using aliases to .NET's classes defeats the OOP tenet of encapsulation -- the aliased object can be directly manipulated by the caller, who can delete items, make the wrong key-value associations, and so forth.  Ironically, if implemented in F# as immutable dictionaries, tuples, and collections, this becomes a non-issue because manipulating a collection or dictionary results in copy of the dictionary or collection (or at best, a collection that shares the tail portion but can have different heads) -- but this approach is beyond the scope of this article.

Second, Fluency

Summary: A fluent ("dot-style") notation reduces code lines by representing code in a "workflow-style" notation.

The other tenet of what I want to explore as "minimal coding" is a "fluent" (dot notation) style of programming.  Why?  Because this results in a very minimal and linear syntax for doing things.  For example (which I'll expound upon later):

new Thread(new ThreadStart(() => aThread.Forever(t => t.WaitForWork().GetWork().DoWork()))).IsBackground().StartThread();

The typical implementation would look like this:

private void ThreadHandler()
{
  while (true) 
  {
    WaitForWork();
    var work = GetWork();
    DoWork(work);
  }
}

var thread = new Thread(new ThreadStart(ThreadHandler));
thread.IsBackground = true;
thread.Start();

Remember, I'm not debating the pros and cons of the two approaches, I'm exploring what a minimal programming style would look like, and a fluent notation is part of that minimal approach, at least at the higher level of abstraction of "I, as user of my library code." 

Third, Extension Methods

Summary: In C#, if we don't write classes with member methods, then we have to implement behaviors as extensions methods.

If we're not wrapping behaviors within classes (and remember, those classes are little more than aliases for a more general purpose container) then how do we implement semantic behaviors?  With extension methods!  At least with regards to C#, this inverts the whole programming model.  Instead of:

class ASemanticAliasOf
{
  SomeCollection collection;

  void SomeOperationA() {...}
  void SomeOperationB() {...}
}

we have instead:

static void SomeOperationA(this SomeCollection collection) {...}
static void SomeOperationB(this SomeCollection collection) {...}

Of course, extension methods need to be wrapped in a static class.  Can't get away from that!

A Minimal C# Service Manager

Summary: Using aliases improves semantic readability at one level at the cost of confusing generic type nesting in the alias definition.  Extension methods can be taken too far, resulting in two  rules: write lower level functions for semantic expressiveness, and avoid nested parens that require the programmer to maintain a mental "stack" of the workflow.

A dictionary is just a dictionary, it knows nothing about being a service manager, and while we could use it directly in our code, we lose a lot of semantic meaning when we code a key-value assignment:

serviceManager[typeof(MyService)] = myService;

Other useful behaviors are lost as well, for example:

  • Is myService actually of type MyService?
  • Is MyService already assigned to a service?
  • Is myService null?

To mitigate this, the service manager has a few extension methods to improve the semantics of using the dictionary.  First though, we'll define the type aliases:

using Service = System.Object;
using Services = System.Collections.Concurrent.ConcurrentDictionary<System.Type, System.Object>;

Notice that I'm mapping to an object rather than an interface that declares that the instance "is a service type."  This is more to avoid cluttering the examples, particularly the F# examples, with unnecessary types.  It's also a moot point in the C# implementation because getting a service from its type casts the object back to that type, so we're all good (famous last words.)

Limitations of Aliasing

Notice that with C#, we can't use aliases in other aliases.  In other words, we can't say:

using Services = System.Collections.Concurrent.ConcurrentDictionary<System.Type, Service>;

Also notice the explicit "namespace-dot-class" notation.  We also can't do something like this:

using Services = ConcurrentDictionary<Type, Service>;

That's a drawback to using aliases in C#, as the alias notation can get ugly very very quickly -- we'll see this later.

Semantic Extension Methods

We'll implement the minimal extension methods to provide better semantics for the operations of a service manager:

public class ServiceManagerException : Exception
{
  public ServiceManagerException(string msg) : base(msg) { }
}

public static class ServiceManagerExtensionMethods
{
  public static Services Register<T>(this Services services, T service)
  {
    services.ShouldNotContain<T>()[typeof(T)] = service;

    return services;
  }

  public static T Get<T>(this Services services)
  {
    return (T)services.ShouldContain<T>()[typeof(T)];
  }

  private static Services ShouldNotContain<T>(this Services services)
  {
    if (services.ContainsKey(typeof(T)))
    {
      throw new ServiceManagerException(String.Format("Service {0} already registered.", typeof(T).Name));
    }

    return services;
  }

  private static Services ShouldContain<T>(this Services services)
  {
    if (!services.ContainsKey(typeof(T)))
    {
      throw new ServiceManagerException(String.Format("Service {0} is not registered.", typeof(T).Name));
    }

    return services;
  }
}

Notice how I also use a fluent style within the public Register and Get methods.

Example Usage

Assuming we have a thread pool and publisher-subscriber objects, we can now register them as services:

Services services = new Services().Register(aThreadPool).Register(pubsub);

Unless the "programmer-user" inspects the types (granted, one has to put those "using..." alias at the top of the code file), this looks just like instantiated a class and calling methods of that class in a fluent notation.

Taking Fluency and Extension Methods Too Far

One could take this concept of extension methods further, with something like:

private static Services ShouldNotContain<T>(this Services services)
{
  return services.Assert(!services.ContainsKey(typeof(T)), String.Format("Service {0} already registered.", typeof(T).Name)));
}

or even worse:

services.Assert(!services.ContainsKey(typeof(T)), String.Format("Service {0} already registered.", typeof(T).Name)))[typeof(T)] = service;

but I find this to be going in the wrong direction -- the programmer has to read through a bunch of parameters and nested parens to figure out what's going on.  And by the time they've unraveled the parameters and paren nesting, they've forgotten what the outer context is trying to accomplish, namely the mapping of a type to an implementation.

Two Useful Rules

This actually leads to some good guidance on how extension methods should be used:

1. Write lower level methods that encapsulate the semantics of a behavior.

These two methods are good:

private static Services ShouldNotContain<T>(this Services services)
private static Services ShouldContain<T>(this Services services)

because they can be used in a semantically readable manner:

services.ShouldNotContain<T>()[typeof(T)] = service;
return (T)services.ShouldContain<T>()[typeof(T)];

But perhaps you disagree!

2. Avoid complicated nesting of parens.

This is readable:

Services services = new Services().Register(aThreadPool).Register(pubsub);

This becomes less readable:

Services services = new Services().Register(new ThreadPool()).Register(new SemanticPubSub());

So anywhere you end up with stringing (hah!) closing parens together, extract the inner operations until you get something clean.

A Minimal F# Service Manager

Summary: In contrast to C#'s using aliases, type definitions are not aliases, they are concrete types.  New type definitions can be created from existing types.  Type definitions can also be used to specify a function's parameters and return value.  The forward pipe operator |> is similar to the fluent "dot" notation in C#, but the value on the left of the |> operator "populates" the last parameter in the function's parameter list.  When functions are written that return something, the last function must be piped to the ignore function, which is slightly awkward.  F# type dependencies are based on the order of the files in the project, so a type must be defined before you use it.

Image 5 I'm not an functional programming purist.  The world is stateful and I have no problem using mutable C# classes in F# code -- dealing with things like tail recursion and monads to create purist immutable FP code might have a geek coolness to it but results in ridiculously hard to read code, at least for the beginner FP coder.  Certainly every FP programmer should at least know about tail recursion, and probably monads and computational expressions as well.  For the purposes of this article though, I wanted to maintain as much one-for-one comparison between the C# code and the F# code, thus the F# code leverages mutable C# classes.  The other disclaimer is that I am not an FP expert by any stretch of the imagination, so if you are, and there are better ways of doing something, please share your knowledge with us all in the comments section of this article.

I had actually started with the thread pool service, which I'll discuss later, but regardless, at this point I started to realize that my coding looked a lot like functional programming:

  • C# dot-notation vs. F# forward pipe operator |>
  • Extension methods vs. function definitions that can be curried or applied partially
  • No classes, just types (granted, as aliases)
  • Use cases in the thread pool and publisher-subscriber that utilize anonymous methods (functions)

So I decided to look at what an implementation in F# looks like.

First, the module (like a namespace, I'm not going into the details here) and the .NET references:

module ServiceManagerModule

open System
open System.Collections.Concurrent

Second, the type definitions:

type ServiceManagerException = Exception
type Service = Object
type Services = ConcurrentDictionary<Type, Service>

Wow, that's a lot cleaner because we're not creating aliases, we're creating actual types!  Notice that the types don't need a fully qualified "namespace-dot-class" notation and that types can reference previously declared types.

We can also create types that "type" a couple functions:

type RegisterService = Service -> Services -> Services
type GetService<'T> = Services -> 'T

The first type reads "a function that takes a Service and a Services type and returns a Services type".

The second type reads "a function with the generic parameter 'T that takes a Services type and returns a type of type 'T.

Declaring a function type semantically rather than stringing a bunch of (var : type) expressions together in the function itself can be very handy.

Next, the implementation:

let ShouldContain<'T> (services : Services) =
  if not (services.ContainsKey(typeof<'T>)) then
    raise (ServiceManagerException("Service is not registered."))
  services

let ShouldNotContain<'T> (services : Services) =
  if (services.ContainsKey(typeof<'T>)) then
    raise (ServiceManagerException("Service already registered."))
  services

let RegisterService:RegisterService = fun service services ->
  (services |> ShouldNotContain).[service.GetType()] <- service
  services

let GetService<'T> : GetService<'T> = fun services ->
  (services |> ShouldNotContain).[typeof<'T>] :?> 'T

Notice the slight syntax differences when we specify the function type rather than inferring it from the function parameters and return value.  Also notice the bizarre :?> operator, which is the "downcast" operator, the equivalent of what we did in C# with the (T) cast:

return (T)services.ShouldContain<T>()[typeof(T)];

Another advantage with F# is that the types that I declare don't have to be re-declared in every .fs file because F# code is compiled in the order of the project's .fs files -- as long as a .fs file declares the type before it is used lower down in the project's file list, you're fine.  This is "dependency ordering", and can be awkward to work with if you have recursive type definitions.  A good discussion on resolving this is here and there's also the and keyword when working with forward type references within a single file, see an example here.

Lastly, notice that, as with C#, each function (exception for the GetService function) returns the Services type, allowing for the forward piping operator to continue evaluation additional expressions.  This brings up something important -- the forward pipe operator always provides the last parameter (or parameters) in a function.  So, to achieve the "fluent" style we were using in C#, the type we want to "forward on to the next function" needs to be the last parameter.  Here's a simple example that illustrates this in F# interactive:

> let f a b = printfn "%d %d" a b;;
f 1 2;;
1 |> f 2;;
val f : a:int -> b:int -> unit

> 1 2
val it : unit = ()

> 2 1
val it : unit = ()

In the second form 1 |> f 2;; notice that "1" is printed second.  When writing functions with the intent to use forward piping (in general and as a fluent notation) this is an important design consideration.

Using this notation, we can write in F# what looks very similar in C# (except no parens):

let services = new Services() |> RegisterService threadPool |> RegisterService pubsub

But there's a caveat.  let's say we want to write it this way:

let services = new Services()
services |> RegisterService threadPool |> RegisterService pubsub |> ignore

Notice the |> ignore at the end.  Because the function RegisterService returns a type, F# (and this is true I believe with any FP language) expects you to do something with that type.  Because we're not doing anything with that type, we have to pass it to the ignore function, which returns a "unit" -- meaning nothing.  You can see this in the definition of the function "f" above:

val f : a:int -> b:int -> unit

Here, the function returns a "unit" because the printfn function is defined to return a "unit."

As a result, a "fluent" notation in F# can result in littering your F# code with |> ignore expressions.  This can be avoided by having your F# functions return "unit", but then you lose the "fluency" of the syntax.

A Minimal Thread Pool

Summary: In C#, creating more complex aliases get messy real fast -- this is an experiment, not a recommendation for coding practices!  In F#, we don't need an Action or Func class for passing functions because F# inherently supports type definitions that declare a function's parameters and return value -- in other words, everything in functional programming is actually a function.  Tuples are a class in C# but native to functional programming, though C# 6.0 makes using tuples very similar to F#.  While C# allows function parameters to be null, in F#, you have to pass in an actual function, even if the function does nothing.

The previous section covers everything, but I found it interesting to continue this approach in C# and F# to explore any further nuances, particularly the use of tuples.  I'll illustrate the C# and F# code more side-by-side from here on.  If you're wondering why I'm implementing my own thread pool instead of using .NET's ThreadPool, the answer is related to performance, as discussed in this 14 year old article - in my latest testing, the ThreadPool still behaves this way.

The C# version uses these aliases:

using Work = System.Action;
using ThreadExceptionHandler = System.Action<System.Exception>;
using ThreadGate = System.Threading.Semaphore;
using ThreadQueue = System.Collections.Concurrent.ConcurrentQueue<System.Action>;
using AThread = System.Tuple<System.Threading.Semaphore, System.Collections.Concurrent.ConcurrentQueue<System.Action>, System.Action<System.Exception>>;
using ThreadPool = System.Collections.Concurrent.ConcurrentBag<System.Tuple<System.Threading.Semaphore, System.Collections.Concurrent.ConcurrentQueue<System.Action>, System.Action<System.Exception>>>;
using ThreadAction = System.Tuple<System.Tuple<System.Threading.Semaphore, System.Collections.Concurrent.ConcurrentQueue<System.Action>, System.Action<System.Exception>>, System.Action>;

Notice how messy this gets!  The actual thread pool object is aliased as ThreadPool, the rest are for semantic convenience.  To break that down for you:

using ThreadPool = 
  System.Collections.Concurrent.ConcurrentBag<
    System.Tuple<
      System.Threading.Semaphore, 
      System.Collections.Concurrent.ConcurrentQueue<
        System.Action>, 
      System.Action<System.Exception>>>;

A thread pool is a collection of semaphore - queue - exception handler triplets, where each queue is an action.  The idea is that a single semaphore manages each queue, where a separate thread is pulling data from the queue, and you can provide a custom exception handler for each thread that manages a queue (why I did this is not important and is actually sort of silly.)

Conversely, notice the type declarations in F#:

type Work = unit -> unit
type ThreadExceptionHandler = Exception -> unit

type ThreadGate = Semaphore
type ThreadQueue = ConcurrentQueue<Work>
type AThread = ThreadGate * ThreadQueue * ThreadExceptionHandler
type ThreadPool = ConcurrentBag<AThread>
type ThreadWork = AThread * Work
type GetWork = AThread -> ThreadWork

type AddWorkToQueue = Work -> AThread -> AThread
type AddWorkToPool = Work -> ThreadPool -> ThreadPool

There are a few extra here, including function type definitions, that improve the readability of the F# code.  Notice we're not using C#'s Action class, we're instead defining an item of work as function that takes no parameters and returns nothing:

type Work = unit -> unit

This is functional programming after all!

The C# implementation, as extension methods, looks like this:

public static class ThreadPoolExtensions
{
  public static ThreadPool AddWork(this ThreadPool pool, Work work)
  {
    pool.MinBy(q => q.Item2.Count).AddWork(work).Item1.Release();

    return pool;
  }

  public static ThreadPool Start(this ThreadPool pool)
  {
    pool.ForEach(aThread => aThread.Start());

    return pool;
  }

  public static AThread Start(this AThread aThread)
  {
    new Thread(new ThreadStart(() => aThread.Forever(t => t.WaitForWork().GetWork().DoWork()))).IsBackground().StartThread();

    return aThread;
  }

  private static Thread IsBackground(this Thread thread)
  {
    thread.IsBackground = true;

    return thread;
  }

  private static Thread StartThread(this Thread thread)
  {
    thread.Start();

    return thread;
  }

  private static AThread AddWork(this AThread aThread, Work work)
  {
    (var _, var queue, var _) = aThread;
    queue.Enqueue(work);

    return aThread;
  }

  private static AThread WaitForWork(this AThread aThread)
  {
    (var gate, var _, var _) = aThread;
    gate.WaitOne();

    return aThread;
  }

  private static ThreadAction GetWork(this AThread aThread)
  {
    Work action = null;
    (var _, var queue, var _) = aThread;
    queue.TryDequeue(out action);

    return new ThreadAction(aThread, action);
  }

  private static ThreadAction DoWork(this ThreadAction threadAction)
  {
    ((var _, var _, var exceptionHandler), var action) = threadAction;
    exceptionHandler.Try(() => action());

    return threadAction;
  }

  private static ThreadExceptionHandler Try(this ThreadExceptionHandler handler, Work action)
  {
    try
    {
      action?.Invoke();
    }
    catch (Exception ex)
    {
      handler?.Invoke(ex);
    }

    return handler;
  }
}

There's a few things to note here. 

The Forever extension method

public static void Forever<T>(this T src, Action<T> action)
{
  while (true) action(src);
}

In F#, this is implemented as:

let rec Forever fnc = 
  fnc()
  Forever fnc

Notice the rec keyword -- this tells the compiler that the function will be called recursively (hence "rec") but because the function is declared recursively, it is implemented iteratively otherwise the program would eventually run out of stack space.  You cannot actually write this function without the rec keyword -- you get a compiler error that Forever is not defined!  Whew.

Tuples

Tuples in C# 6 are much easier to work with.  Note some examples of this syntax:

(var gate, var _, var _) = aThread;
gate.WaitOne();

Here we just care about the semaphore, the queue and exception handler can be ignored.

Work action = null;
(var _, var queue, var _) = aThread;
queue.TryDequeue(out action);

Here we only care about the queue, the semaphore and exception handler are ignored.

return new ThreadAction(aThread, action);

Here we are creating a tuple.  Neat, eh?

MinBy

Here:

pool.MinBy(q => q.Item2.Count).AddWork(work).Item1.Release();

We are finding a queue with the least amount of work queued on it and adding the work to that queue.  As an aside, this is dubious at best because the determination of what thread to queue the work onto is based on the queue size, not whether the thread is already busy doing work!  Ignore that, it's not the point of this article.

The F# implementation is similar -- notice the syntax similarity when working with tuples, which are more "native" to functional programming:

let AddWorkToThread:AddWorkToQueue = fun work athread ->
  let gate, queue, _ = athread
  queue.Enqueue(work)
  gate.Release() |> ignore
  athread

let AddWorkToPool:AddWorkToPool = fun work pool ->
  pool.MinBy(fun (_, q, _) -> q.Count) |> AddWorkToThread work |> ignore
  pool

let rec Forever fnc = 
  fnc()
  Forever fnc

let WaitForWork(athread : AThread) =
  let gate, _, _ = athread
  gate.WaitOne() |> ignore
  athread

let GetWork:GetWork= fun athread ->
  let _, queue, _ = athread
  let _, work = queue.TryDequeue()
  (athread, work)

let DoWork threadWork =
  try 
    (snd threadWork)()
  with
    | ex -> 
      let (_, _, handler) = fst threadWork
      handler(ex)
  threadWork

let StartThread athread : AThread =
  let thread = new Thread(new ThreadStart(fun() -> Forever <| fun() -> athread |> WaitForWork |> GetWork |> DoWork |> ignore))
  thread.IsBackground <- true
  thread.Start()
  athread

let StartThreadPool pool : ThreadPool =
  for athread in pool do athread |> StartThread |> ignore
  pool

I also was lazy and didn't implement the IsBackground() and StartThread() extension methods equivalent in F#.

Also notice in F# the "reverse pipe" operator <| here:

fun() -> Forever <| fun() -> athread |> WaitForWork |> GetWork |> DoWork |> ignore

F#'s expressiveness has its advantages.  We could also have written without the reverse pipe operator as:

let thread = new Thread(new ThreadStart(fun () -> Forever (fun() -> athread |> WaitForWork |> GetWork |> DoWork |> ignore)))

but that would require putting parens around the function that we want performed forever.

Null vs. Unit

To state the obvious, C# has the concept of null, so in the case that the user passes in a null for the exception handler, we test that using the null conditional operator ?. :

action?.Invoke();

Image 6  Conversely, while F# has a concept of null for compatibility with C# with regards to parameter passing and matching against return values, natively, the closest equivalence to C#'s null is the None option value.  You can't pass None in as a parameter when a function is expected.  In the code above, if you don't want to supply an exception handler, you still have to provide a "do nothing" function:

fun (_) -> ()

The _ is necessary because the exception handler expects a parameter.  But this is exactly the definition of ignore, so we can instead write:

let threadPool = new ThreadPool(Seq.map(fun _ -> (new ThreadGate(0, Int32.MaxValue), new ThreadQueue(), ignore)) {1..20})

Lincoln Atkinson has a great write-up on the topic of handling nulls in F# here.

Example Usage in C#

Here's how we can use this in C# in a minimal way (arguably), without for loops, etc:

ThreadPool aThreadPool = new ThreadPool(Enumerable.Range(0, 20).Select((_) => 
  new AThread(
    new ThreadGate(0, int.MaxValue), 
    new ThreadQueue(), 
    ConsoleThreadExceptionHandler)));
DateTime startTime = DateTime.Now;

Enumerable.Range(0, 10).ForEach(n => aThreadPool.AddWork(() =>
  {
    Thread.Sleep(n * 1000);
    Console.WriteLine("{0} thread ID:{1} when:{2}ms", 
      n, 
      Thread.CurrentThread.ManagedThreadId, 
     (int)(DateTime.Now - startTime).TotalMilliseconds);
    int q = 10 / n; // forces an exception when n==0
  }));

aThreadPool.Start();

This creates 20 threads and 10 worker items, the first of which throws an exception, and then starts the threads.  The reason the threads are deferred is to force each work item to run on its own thread.  Here's the output:

Image 7

Example Usage in F#

let exceptionHandler (ex : Exception) = printfn "%s" ex.Message
let threadPool = new ThreadPool(Seq.map(fun _ -> (new ThreadGate(0, Int32.MaxValue), new ThreadQueue(), exceptionHandler)) {1..20})
let startTime = DateTime.Now

for i in {0..9} do
  threadPool |> AddWorkToPool (fun() -> 
    let q = 10 / i
    Thread.Sleep(i * 1000)
    printfn "%d Thread ID:%d when:%ims" i Thread.CurrentThread.ManagedThreadId (int ((DateTime.Now - startTime).TotalMilliseconds)))
  |> StartThreadPool |> ignore

Notice here I use a for loop - I could have done Seq.map again, but F#'s for - in syntax is so much more expressive that C#'s that it only obscures to code in my opinion.  The advantage of using Seq.map in the instantiation of the ThreadPool is that this creates a collection (though we ignore the sequence number). The underlying type, ConcurrentBag<AThread>, accepts an IEnumerable as a parameter.  (I must say there is a really nice interoperability between C# and F#.)  This is additional code obfuscation if we iterate a simple Seq in F# - the {0..9} is the sequence!

And the output:

Image 8

Image 9There is a slight difference because of how I write the code.  I cannot write:

Thread.Sleep(i * 1000)
printfn "%d Thread ID:%d when:%ims" i Thread.CurrentThread.ManagedThreadId (int ((DateTime.Now - startTime).TotalMilliseconds))
let q = 10 / i

because a "let" cannot be the final code element in a block - a function must evaluate to something, at a minimum a "unit", and a let statement as the last code element in a function is an assignment function, not an evaluation.  To fix that, I would have to write it to return a "unit" like this:

Thread.Sleep(i * 1000)
printfn "%d Thread ID:%d when:%ims" i Thread.CurrentThread.ManagedThreadId (int ((DateTime.Now - startTime).TotalMilliseconds))
let q = 10 / i
()

Then I get the same output as in the C# example:

Image 10

Ah, the nuances of functional programming!  Also, I have no idea why the thread ID's are so different between C# and F#.

The Semantic Publisher-Subscriber

Summary: F# uses a nominal ("by name") as opposed to structural inference engine, so in F#, giving types semantically meaningful names is very important so that the type inference engine can infer the correct type.

The last piece!  The idea here is to register subscribers will be triggered when a particular type is published.  The caller can determine whether the subscribers of the type should process the type immediately or whether processing can be performed asynchronously using the thread pool created earlier. 

The C# Aliases

These are the aliases defined by the pub-sub: 

using PubSubExceptionHandler = System.Action<System.Exception>;
using PubSubTypeReceiverMap = System.Collections.Concurrent.ConcurrentDictionary<System.Type, System.Collections.Concurrent.ConcurrentBag<object>>;
using PubSubReceivers = System.Collections.Concurrent.ConcurrentBag<object>;
using SemanticPubSub = System.Tuple<System.Collections.Concurrent.ConcurrentDictionary<System.Type, System.Collections.Concurrent.ConcurrentBag<object>>, System.Action<System.Exception>>;

using ThreadPool = System.Collections.Concurrent.ConcurrentBag<System.Tuple<System.Threading.Semaphore, System.Collections.Concurrent.ConcurrentQueue<System.Action>, System.Action<System.Exception>>>;

Notice that the ThreadPool alias has to be included, as we're using the thread pool for the async publish process:

Again, this gets nasty, so here's the breakdown of the SemanticPubSub type:

SemanticPubSub = System.Tuple<
  System.Collections.Concurrent.ConcurrentDictionary<
    System.Type, 
    System.Collections.Concurrent.ConcurrentBag<object>>, 
  System.Action<System.Exception>>;

The SemanticPubSub is a map - exception handler pair, where the map associates a type with a collection of receivers that act on an instance of the type, though that last part isn't obvious because it's a collection of objects.  As with the service manager, we would ideally use an interface type so we could replace object with Action<ISubscriberData> or something like that, but the cast, when we call the subscribers, handles this for us.  We may also not have the ability to add ISubscriberData to the type that's being passed to the subscribers, particularly if we don't have the code for the data.

The F# Types

In F#, this is a lot more readable:

open ThreadPoolModule

type PubSubExceptionHandler = Exception -> unit
type Subscribers = ConcurrentBag<Object>
type Subscription<'T> = 'T -> unit
type Subscriptions = ConcurrentDictionary<Type, Subscribers>
type SemanticPubSub = Subscriptions * PubSubExceptionHandler

type Subscribe<'T> = Subscription<'T> -> SemanticPubSub -> SemanticPubSub

type Publish<'T> = 'T -> SemanticPubSub -> SemanticPubSub
type AsyncPublish<'T> = ThreadPool -> 'T -> SemanticPubSub -> SemanticPubSub
type CreateMissingBag = SemanticPubSub -> SemanticPubSub

Notice the open ThreadPoolModule, which pulls in the types defined in that module.  Here again we use Object for the list of subscribers.  I've been exploring the concept of statically resolving type parameters in F# with the ^T notation, as well as Tomas Petricek's article on dynamic lookup, but my F#-fu is not there yet.

These types are function type declarations:

type PubSubExceptionHandler = Exception -> unit
type Subscription<'T> = 'T -> unit
type Subscribe<'T> = Subscription<'T> -> SemanticPubSub -> SemanticPubSub
type Publish<'T> = 'T -> SemanticPubSub -> SemanticPubSub
type AsyncPublish<'T> = ThreadPool -> 'T -> SemanticPubSub -> SemanticPubSub

This takes an Exception and returns nothing:

type PubSubExceptionHandler = Exception -> unit

This takes a generic parameter of type T and returns nothing:

type Subscription<'T> = 'T -> unit

This takes a Subscription and the pubsub and returns the pubsub:

type Subscribe<'T> = Subscription<'T> -> SemanticPubSub -> SemanticPubSub

This takes a generic parameter and the pubsub and returns a pubsub:

type Publish<'T> = 'T -> SemanticPubSub -> SemanticPubSub

This takes a generic type, a thread pool, and the pubsub and returns a pubsub:

type AsyncPublish<'T> = ThreadPool -> 'T -> SemanticPubSub -> SemanticPubSub

So, reading this, from the bottom up:

type PubSubExceptionHandler = Exception -> unit
type Subscribers = ConcurrentBag<Object>
type Subscriptions = ConcurrentDictionary<Type, Subscribers>
type SemanticPubSub = Subscriptions * PubSubExceptionHandler

A SemanticPubSub is a tuple consisting of subscriptions and an exception handler, where each subscription is a map between a Type and collection of subscribers implemented as an Object.  Why an object?  Because the subscription:

type Subscription<'T> = 'T -> unit

defines a generic type as a parameter, and this type varies and is not known to the pub-sub.  Yes, as with C#, this could be implemented by enforcing that any subscription implements an interface, but particularly in functional programming, where the type can be anything, including another function, we don't want to enforce the requirement that the generic parameter is "class-like."

The C# Implementation

Again, extension methods are used for the implementation.  The three methods that are exposed are:

  • Subscribe
  • Publish
  • AsyncPublish

Note that AsyncPublish is not awaitable (it's not supposed to be awaitable), otherwise I would have called the method PublishAsync for naming convention consistency.

public static class SemanticPubSubExtensionMethods
{
  public static SemanticPubSub Subscribe<T>(this SemanticPubSub pubsub, Action<T> receiver)
  {
    pubsub.CreateMissingBag<T>().Item1[typeof(T)].Add(receiver);

    return pubsub;
  }

  public static SemanticPubSub Publish<T>(this SemanticPubSub pubsub, T data)
  {
    // No listeners does not throw an exception.
    if (pubsub.Item1.ContainsKey(typeof(T)))
    {
      pubsub.Item1[typeof(T)].ForEach(r => pubsub.Item2.Try((Action<T>)r, data));
    }

    return pubsub;
  }

  public static SemanticPubSub AsyncPublish<T>(this SemanticPubSub pubsub, ThreadPool threadPool, T data)
  {
    // No listeners does not throw an exception.

    if (pubsub.Item1.ContainsKey(typeof(T)))
    {
      // No Try here because threadpool handles the exception.
      pubsub.Item1[typeof(T)].ForEach(r => threadPool.AddWork(() => ((Action<T>)r)(data)));
    }

    return pubsub;
  }

  private static SemanticPubSub CreateMissingBag<T>(this SemanticPubSub pubsub)
  {
    Type t = typeof(T);

    if (!pubsub.Item1.ContainsKey(t))
    {
      pubsub.Item1[t] = new PubSubReceivers();
    }

    return pubsub;
  }

  private static PubSubExceptionHandler Try<T>(this PubSubExceptionHandler handler, Action<T> action, T data)
  {
    try
    {
      action?.Invoke(data);
    }
    catch (Exception ex)
    {
      handler?.Invoke(ex);
    }

    return handler;
  }
}

Note the downcast ((Action<T>)r)(data)) which is perfectly safe because we are only calling methods that subscribed with type T.

The F# Implementation

The F# implementation is similar, again note the downcast operator :?>

let CreateMissingBag<'T> : CreateMissingBag = fun pubsub ->
  let t = typeof<'T>
  let (dict, _) = pubsub

  if not (dict.ContainsKey(t)) then
    dict.[t] <- new Subscribers();

  pubsub

let Subscribe<'T> : Subscribe<'T> = fun fnc pubsub ->
  let t = typeof<'T>
  let (dict, _) = pubsub |> CreateMissingBag<'T>
  dict.[t].Add(fnc)
  pubsub

let TryPublish fnc pubsub =
  let (_, handler) = pubsub
  try
    fnc()
  with
    | ex -> 
    handler(ex)

let Publish<'T> : Publish<'T> = fun data pubsub ->
  let t = typeof<'T>
  let (dict, handler) = pubsub

  if (dict.ContainsKey(t)) then
    for subscriber in dict.[t] do 
      pubsub |> TryPublish (fun () -> data |> (subscriber :?> Subscription<'T>))

  pubsub

let AsyncPublish<'T> : AsyncPublish<'T> = fun threadPool data pubsub ->
  let t = typeof<'T>
  let (dict, _) = pubsub

  if (dict.ContainsKey(t)) then
    for subscriber in dict.[t] do 
      threadPool |> AddWorkToPool (fun () -> data |> (subscriber :?> Subscription<'T>)) |> ignore

pubsub

C# Usage Example

Here's a C# usage example -- note the fluent notation and anonymous method:

public class Counter
{
  public int N { get; protected set; }

  public Counter(int n) { N = n; }
}

public class SquareMe
{
  public int N { get; protected set; }

  public SquareMe(int n) { N = n; }
}

pubsub.Subscribe<Counter>(r =>
{
  int n = r.N;
  Thread.Sleep(n * 1000);
  Console.WriteLine("{0} thread ID:{1} when:{2}ms", 
    n, 
    Thread.CurrentThread.ManagedThreadId, 
    (int)(DateTime.Now - startTime).TotalMilliseconds);
  int q = 10 / n; // forces an exception when n==0
}).Subscribe<SquareMe>(r =>
{
  Console.WriteLine("Second subscriber : {0}^2 = {1}", r.N, r.N * r.N);
});

The above creates a couple subscribers, and this is how the types are published -- the Counter instances are published asynchronously, the SquareMe instances is published synchronously:

Enumerable.Range(0, 10).ForEach(n => pubsub.AsyncPublish(aThreadPool, new Counter(n)).Publish(new SquareMe(n)));
 

The result is:

Image 11

F# Usage Example

The same example, but in F#:

type Counter = {N : int}
type SquareMe = {N : int}

pubsub |> Subscribe<Counter> (fun r ->
  let n = r.N
  let q = 10 / n
  Thread.Sleep(n * 1000)
  printfn "%d Thread ID:%d when:%ims" 
    n 
    Thread.CurrentThread.ManagedThreadId 
    (int ((DateTime.Now - startTime).TotalMilliseconds)))
|> Subscribe<SquareMe> (fun r -> printfn "%d^2 = %d" r.N (r.N * r.N)) |> ignore

Note the brevity of creating a "record" type, otherwise everything else looks pretty much the same.  Publishing the two instances is also very similar:

for i in {0..9} do
  pubsub |> AsyncPublish tp {Counter.N = i} |> Publish {SquareMe.N = i} |> ignore

Again, a bit more concise (terser, perhaps) than the C# code.  Do note however that the we have to explicitly state which record to create:

{Counter.N = i}
{SquareMe.N = i}

because both records are nominally (as in, by "name" - nominclature) the same, having a single member N.  If we named them this way:

type Counter = {Ctr : int}
type SquareMe = {Sqr : int}

Then publishing these record instances would be even terser (fixing up the subscriber methods as well):

for i in {0..9} do
  ps |> AsyncPublish tp {Ctr = i} |> Publish {Sqr = i} |> ignore

Image 12  This points out the usefulness of giving types semantically meaningful names so that the type inference engine can infer the type and also the dangers of having records that have the same names - F#'s inference engine will use the last record, so if we do this:

type Counter = {N : int}
type SquareMe = {N : int}

without specifying the record type:

ps |> AsyncPublish tp {N = i} |> Publish {N = i} |> ignore

we get this:

Image 13

Note the odd output due to the fact that one set of the computations are running on separate threads.  In the code above, it's rather obvious that something is wrong, but this is less obvious with more complex code.  F# uses nominal type inference rather than structural type inference, so this can result in very strange compiler error messages if you think you're using type A when instead your using type B.

Putting it All Together

Summary: Here we see that all the using aliases in C# have to be pulled in to this file.  With F#, we don't have to declare the types over again.

Lastly, we want both the thread pool and the pub-sub to be services, so we'll tie in the service manager here.

The Full C# Example

Here's what the final example in C# looks like, demonstrating that we're getting the pubsub and thread pool services from the service manager.  Notice we have all those ugly using aliases:

using System;
using System.Linq;
using System.Threading;

using Services = System.Collections.Concurrent.ConcurrentDictionary<System.Type, object>;

using PubSubTypeReceiverMap = System.Collections.Concurrent.ConcurrentDictionary<System.Type, System.Collections.Concurrent.ConcurrentBag<object>>;
using SemanticPubSub = System.Tuple<System.Collections.Concurrent.ConcurrentDictionary<System.Type, System.Collections.Concurrent.ConcurrentBag<object>>, System.Action<System.Exception>>;

using ThreadGate = System.Threading.Semaphore;
using ThreadQueue = System.Collections.Concurrent.ConcurrentQueue<System.Action>;
using AThread = System.Tuple<System.Threading.Semaphore, System.Collections.Concurrent.ConcurrentQueue<System.Action>, System.Action<System.Exception>>;
using ThreadPool = System.Collections.Concurrent.ConcurrentBag<System.Tuple<System.Threading.Semaphore, System.Collections.Concurrent.ConcurrentQueue<System.Action>, System.Action<System.Exception>>>;

namespace MinApp
{
  public class Counter
  {
    public int N { get; protected set; }

    public Counter(int n) { N = n; }
  }

  public class SquareMe
  {
    public int N { get; protected set; }

    public SquareMe(int n) { N = n; }
  }

  class Program
  {
    static void Main(string[] args)
    {
      ThreadPool aThreadPool = new ThreadPool(Enumerable.Range(0, 20).Select((_) => 
      new AThread(
        new ThreadGate(0, int.MaxValue), 
        new ThreadQueue(), 
        ConsoleThreadExceptionHandler)));

      SemanticPubSub pubsub = new SemanticPubSub(new PubSubTypeReceiverMap(), ConsolePubSubExceptionHandler);
      Services services = new Services().Register(aThreadPool).Register(pubsub);
      var pool = services.Get<ThreadPool>(); // example of getting a service.
      DateTime startTime = DateTime.Now;

      pubsub.Subscribe<Counter>(r =>
      {
        int n = r.N;
        Thread.Sleep(n * 1000);
        Console.WriteLine("{0} thread ID:{1} when:{2}ms", n, Thread.CurrentThread.ManagedThreadId, (int)(DateTime.Now - startTime).TotalMilliseconds);
        int q = 10 / n; // forces an exception when n==0
      }).Subscribe<SquareMe>(r =>
      {
        Console.WriteLine("Second subscriber : {0}^2 = {1}", r.N, r.N * r.N);
      });

      Enumerable.Range(0, 10).ForEach(n => pubsub.AsyncPublish(pool, new Counter(n)).Publish(new SquareMe(n)));

      pool.Start();
      Console.ReadLine();
    }

    static void ConsoleThreadExceptionHandler(Exception ex)
    {
      Console.WriteLine(ex.Message);
    }

    static void ConsolePubSubExceptionHandler(Exception ex)
    {
      Console.WriteLine(ex.Message);
    }
  }
}

The Full F# Example

By contrast, here's the F# example.  Notice we don't have to redefine the types:

open System
open System.Collections.Concurrent
open System.Threading
open ThreadPoolModule
open SemanticPubSubModule
open ServiceManagerModule

type Counter = {N : int}
type SquareMe = {N : int}

[<EntryPoint>]
let main argv = 
  let exceptionHandler (ex : Exception) = printfn "%s" ex.Message
  let threadPool = new ThreadPool(Seq.map(fun _ -> (new ThreadGate(0, Int32.MaxValue), new ThreadQueue(), exceptionHandler)) {1..20})
  let pubsub = (new Subscriptions(), exceptionHandler)

  let services = new Services()
  services |> RegisterService threadPool |> RegisterService pubsub |> ignore

  let startTime = DateTime.Now
  let tp = services |> GetService<ThreadPool>
  let ps = services |> GetService<SemanticPubSub>
  ps |> Subscribe<Counter> (fun r ->
    let n = r.N
    let q = 10 / n
    Thread.Sleep(n * 1000)
    printfn "%d Thread ID:%d when:%ims" n Thread.CurrentThread.ManagedThreadId (int ((DateTime.Now - startTime).TotalMilliseconds)))
  |> Subscribe<SquareMe> (fun r -> printfn "%d^2 = %d" r.N (r.N * r.N)) |> ignore

  for i in {0..9} do
    ps |> AsyncPublish tp {Counter.N = i} |> Publish {SquareMe.N = i} |> ignore

  tp |> StartThreadPool |> ignore
  Console.ReadLine() |> ignore
  0

Extending Behaviors - The Dangers in Object Oriented Programming and the Argument for Functional Programming

Summary: In C#, changing the members of class doesn't affect the class type.  Not so with F# (at least with vanilla records) -- changing the structure of a record changes the record's type.  Changing the members of a C# class can, among other things, lead to incorrect initialization and usage.   Inheritance, particularly in conjunction with mutable fields, can result in behaviors with implicit understanding like "this will never happen" to suddenly break.  Extension methods and overloading creates semantic ambiguity.  Overloading is actually not supported in F# - functions must have semantically different names, not just different types or parameters lists.

As an exercise in minimizing the creating of classes in C# code, while it is doable with using aliases and extension methods, it is a very non-standard approach. The using aliases are ugly, unwieldy, and totally break the rules of OOP, particularly encapsulation and inheritance, making anything you write in this form brittle because it will completely break your code everywhere if you make a change to the organization of a tuple or dictionary.  Also, aliases allow direct manipulation of the dictionary, collection or tuple, which is another problem, because they are mutable -- a problem I carried along in the F# examples. 

Typically, the maintainability (by this I mean that at some point you're going to want to extend the behavior of existing code) of OOP code relies on one or more of the following options:

  1. Change the class -- if you have the luxury of having the source code, you can change the class (add/remove methods/fields/properties/delegates) and at worse you have to recompile all the code depending on the concrete implementation.
  2. Inheritance -- if you can't change the code but class isn't sealed, you might at least be able to create a new type that inherits the behaviors of the old type and lets you extend the behavior in your inherited type.
  3. Encapsulation -- if you can't inherit the type (as in, it's sealed) you can implement a wrapper for the class and pass through the behaviors you want to keep the same, change/add/remove other behaviors.
  4. Extension methods -- another way of adding behaviors, but not member variables, to a class, whether it's sealed or not.
  5. Wrapper methods - what we used to do before extension methods (no further discussion on this option.)

So let's take a considerable detour and look at these OOP techniques with C# and what happens when we try them with F#.  Along the way I'll come up with some of the reasons why OOP can be dangerous, why functional programming avoids those dangers, and how even in functional programming to enter into the danger zone.

Change the Container

With regards to C#, changing the container does not change the container's type.  For example:

public class Foo
{
  public int A { get; set; }
}

public class UsesFoo
{
  public void CreateFoo()
  {
    var foo = new Foo() { A = 5 };
  }
}

We now change Foo:

public class Foo
{
  public int A { get; set; }
  public int B { get; set; }
}

The method CreateFoo still works, though something else will probably break because B isn't initialized.

Now consider this in F#, using a record (a record is an aggregate of named values and are reference types by default, like classes as opposed to structs):

type Foo = {A : int}
let bar = {A=5}

Now let's change Foo, adding B:

Image 14

Whoops!  We have changed the type!  Now granted, we could use classes in F#, but the point (academic, perhaps) of this whole article is to avoid using classes!

This is sort of like doing this in C#:

using Foo = System.Int32;
...
public void FooType()
{
  var foo = new Foo();
  foo = 5;
}

and then, changing Foo:

using Foo = System.Tuple<System.Int32, System.Int32>;

Image 15

Ah!  So here we get to what I consider to be a critical flaw in object oriented programming.  When we modify a class on OOP, the type definition for that class doesn't change!  This is a great convenience but also inherently dangerous because it results in the usage of the type with potentially unexpected results when members of the class are improperly initialized.

Image 16 The contrived F# equivalent example would be more like this:

type Foo2 = int
let mutable bar2 = new Foo2()
bar2 <- 5

Then changing Foo2 to:

type Foo2 = int * int

Image 17

The point being that F# (as long as you don't use classes) doesn't even allow us to modify the "aliasing" of a primitive type, if you were to do something as silly as the above contrived example.

Inheritance and Encapsulation

Consider this example instead:

public class Foo
{
  public int A { get; set; }
}

public class Foo2 : Foo
{
  public int B { get; set; }
}

public class UsesFoo
{
  public void CreateFoo()
  {
    var foo = new Foo() { A = 5 };
  }
}

Here we have preserved the "good" behavior of Foo in all places that use Foo, and we have introduced a new type Foo2 for when we want the extended behavior.

This is a good practice on OOP, especially once your code is in production -- anything new that want to do that leverages Foo should be derived from Foo so you don't inadvertently break some existing usage!

The F# equivalent, using records, would look like this:

type Foo = {A : int}
type Foo2 = {foo : Foo; B : int}
let bar = {A=5}
let bar2 = {foo=bar; B=5}

Image 18  But this isn't inheritance, it's encapsulation!  You can't inherit from a record type because record types are compiled to sealed classes!

So the actual C# equivalent would be:

public class Foo2
{
  public Foo Foo { get; set; }
  public int B { get; set; }
}

The advantage of inheritance in C# is that Foo2 acquires all the public/protected members of Foo.  However, if you make the encapsulated member Foo protected or private, you have to explicitly expose the members that you still want to pass through to Foo.  There may be good reasons to do this - for example, throwing an exception on an inappropriate use of Foo2.

Image 19  You cannot do this in F#:

Image 20

All record fields must be designated as private:

type Foo2 = private {foo : Foo; B : int}
let bar = {A=5}
let bar2 = {foo=bar; B=5}
let a = bar2.foo.A
let b = bar2.B

And as the code above illustrates, it gets weirder, if you want to read this.

Inheritance, in F#, is not the same as using the record type:

type Foo(a) =
  member this.A = a

type Foo2(a, b) = 
  inherit Foo() 
  member this.B = b

This is actually the correct equivalent to the C# example, and as I discuss in the section "Inheritance and Mutable and Immutable" below.

Extension Methods and Overloading - More Bad OOP Practice

If you can't change the class members and you can't inherit the class, and you don't want to encapsulate the class, you can still extend the behavior of a class with extension methods.  Consider this contrived example:

public static class Extensions
{
  public static int Add(this Foo foo, int val)
  {
    return foo.A + val;
  }
}

public class Foo
{
  public int A { get; set; }
  public int Add()
  {
    return A + 1;
  }
}

public class UsesFoo
{
  public void CreateFoo()
  {
    var foo = new Foo() { A = 5 };
    int six = foo.Add();
    int seven = foo.Add(2);
  }
}

Notice we have effectively overloaded the poorly named Add method with an extension method of the same name.  Granted, while this is a contrived example, extension methods (introduced in C# 2.0) are a great convenience as previously we had to write static helper classes to extend the behavior of sealed classes.  And while object oriented programming has supported method overloading (aka, one of the features of polymorphism) from basically day 1, it can easily lead to semantic confusion if used inappropriately.

Conversely, in F#, overloading is not permitted (unless you go through some hoops, see below):

Image 21

While you can coerce F# to use extension methods like this (thanks to TheBurningMonk for an example):

open System.Runtime.CompilerServices

type Foo = {A : int}

let Add (foo : Foo) = foo.A + 1

[<Extension>]
type FooExt =
[<Extension>] 
static member Add(foo : Foo, n : int) = foo.A + n

let foo = {A = 5}
let six = foo.Addlet seven = foo.Add(2)

Do not do this!  It breaks the purity of functional programming, which, among other things, enforces good semantic naming practices:

type  Foo = {A : int}

let IncrementFoo (foo : Foo) = foo.A + 1
let AddToFoo (foo : Foo) (n : int) = foo.A + n

let foo = {A = 5}
let six = IncrementFoo foo
let seven = 2 |> AddToFoo foo
let eight = AddToFoo foo 3

Inheritance and Mutable vs. Immutable

Lastly, we come to what every imperative language supports: mutability.

public class Foo
{
  public int A { get; set; }

  public void Increment()
  {
    A = A + 1;
  }
}

In F#, the = operator is a comparison, not an assignment:

Image 22

If you want to make an assignment, you have to use the <- operator:

Image 23

Oops.  You have to make the type mutable:

type Foo = {mutable A : int}

let IncrementFoo (foo : Foo) =
foo.A <- foo.A + 1

let foo = {A = 5}
let b = IncrementFoo foo

This demonstrates another problem with OOP.  Let's say you have this (granted, contrived):

public class Foo
{
  public int A { get; set; }
}

public class Foo2 : Foo
{
  public void Increment()
  {
    A = A + 1;
  }
}

class Program
{
  static void Main(string[] args)
  {
    var myFoo = new Foo2() { A = 5 };
    myFoo.Increment();
    ShowYourFoo(myFoo);
  }

  static void ShowYourFoo(Foo foo)
  {
    Console.WriteLine("Hey, who change my A! " + foo.A);
  }
}

Image 24

Mutability combined with inheritance lets me change my inherited concept of an instance in ways the original implementation did not expect!  But wait, the whole point of inheritance and mutability is to do exactly that, and yes, there are many advantages to this scenario.  There are also many dangers:

class Program
{
  static void Main(string[] args)
  {
    var myFoo = new Foo2() { A = -1 };
    myFoo.Increment();
    DivideByA(myFoo);
  }

  // Having written Foo, I know for a fact that Foo.A is never 0.
  static void DivideByA(Foo foo)
  {
    int d10 = 10 / foo.A;
  }
}

Image 25

Come now, how many times have you discovered a bug (in production, no less) where an existing method in a base class, having been implemented with knowledge that certain things cannot happen, suddenly breaks because that very same method of the base class now breaks because of something a derived class changed?

Yes, you can coerce F# into the same problem, and there are many times when a mutable type is required -- I always argue that pure functional programming is unrealistic because we live in a mutable world.  Realistically though, functional programming helps you avoid mutation when you don't really need it.  It does this by requiring that you use the mutable keyword and (again, as long as you avoid inheritance) by constructing new types with semantic names for the functions that operate on those types.

With regards to inheritance in F#, this is reasonable because the types are immutable:

type Foo(a) =
  member this.A = a
  member this.Increment() = 
  this.A + 1


type Foo2(a, b) = 
  inherit Foo(a) 
  member this.B = b

let foo = new Foo2(1, 2)
let newA = foo.Increment()

This example:

type Foo(a) =
  let mutable A = a
  member this.Increment() = 
  A <- A + 1


type Foo2(a, b) = 
  inherit Foo(a) 
  member this.B = b

let foo = new Foo2(1, 2)
foo.Increment()

Gets you into the same problem as described above.  The conclusion here (which one can argue until the cows come home) is that inheritance with mutable types is dangerous!

Conclusion

What to say?  While you'd never code in C# like this, it's a lot more natural in F# and I would imagine with functional programming in general.  In the process of writing this, I think I identified some concrete pitfalls of OOP that functional programming overcomes, though FP (at least with F#) can definitely be coerced to exhibit the same behaviors.  I also believe I came up with some specific differences with regards to object oriented vs. functional programming -- not that one is better than the other, but at least specific talking points to the advantages and disadvantages of both.  Hopefully you will concur!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Ehsan Sajjad20-Sep-17 23:32
professionalEhsan Sajjad20-Sep-17 23:32 
GeneralMy vote of 5 Pin
Raul Iloc8-Sep-17 7:07
Raul Iloc8-Sep-17 7:07 
GeneralMy vote of 5 Pin
BillWoodruff13-Aug-17 3:27
professionalBillWoodruff13-Aug-17 3:27 
QuestionIs this related? Pin
Dong Xie8-Aug-17 3:37
Dong Xie8-Aug-17 3:37 
AnswerRe: Is this related? <--- Everyone should read this! Pin
Marc Clifton9-Aug-17 2:28
mvaMarc Clifton9-Aug-17 2:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.