Some of the greatest crimes in modern computing are committed in the name of good object-orientated abstractions. Over time, a code base gets so polluted with abstract classes, factories and heavy dependencies on frameworks that a lot of it ends up serving as boilerplate to work around the rest. I’ve spent the last few months learning Haskell (A pure functional language) and Go (An imperative language that allows OO and functional styles) and it’s led me to the conclusion that John Carmack is right, sometimes all you really need is a function.
Go’s approach to OO struck me as very interesting because instead of complex class hierarchies they took a very simple approach and allowed you to associate a function with a type. There’s no this keyword, you assign the type a name and use that to access it’s properties. Here’s a very simple example:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import "fmt" | |
type Person struct { | |
name string | |
} | |
func (p Person) GetName() string { | |
return p.name | |
} |
Note: Unused variables are compile-time errors in Go, hence why this is much simpler than the upcoming C# examples.
We can do something similar in C#. Here’s a very simple OO example in C#. In pure OO, an object is a bundle of state and methods that operate on that state so I’m going with a contrived sample that demonstrates this.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace OOCSharpExample | |
{ | |
internal class OOCSharp | |
{ | |
private static void Main(string[] args) | |
{ | |
var person1 = new Person("Thomas", "Dublin", 23); | |
var person2 = new Person("John", "Sligo", 22); | |
Console.WriteLine(person1.Name + " is " + person1.CompareAge(person2) + " " + person2.Name); | |
} | |
} | |
internal class Person | |
{ | |
public string Name { get; set; } | |
public string Location { get; set; } | |
public int Age { get; set; } | |
public Person(string name, string location, int age) | |
{ | |
Name = name; | |
Location = location; | |
Age = age; | |
} | |
public string CompareAge(Person p) | |
{ | |
if (Age == p.Age) | |
{ | |
return "the same age as"; | |
} | |
return Age > p.Age ? "older than" : "younger than"; | |
} | |
} | |
} | |
Now lets look at the functional solution:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using Person = System.Tuple<string,string,int>; | |
namespace FunctionalCsharpExample | |
{ | |
class FunctionalCSharp | |
{ | |
static string CompareAge(Person a, Person b) | |
{ | |
if (a.Item3 == b.Item3) | |
{ | |
return "the same age as"; | |
} | |
return a.Item3 > b.Item3 ? "older than" : "younger than"; | |
} | |
static void Main(string[] args) | |
{ | |
var person1 = new Person("Thomas", "Dublin", 23); | |
var person2 = new Person("John", "Sligo", 22); | |
Console.WriteLine(person1.Item1 + " is " + CompareAge(person1,person2) + " " + person2.Item1); | |
} | |
} | |
} |
Note on line 2 we’ve given Tuples of type <string, string, int> the alias of Person. Since System.Tuple is a class, we create our Person tuple almost exactly the same way as our OO solution. The biggest difference is that there is no maintained state. Our CompareAge function takes two Person tuples and computes the required value. There’s also a lot less worrying about encapsulation, since we can explicitly see that our people tuples only exist in the scope where we create them.
I’ll expand on this post in the future with some more example of how aliasing types in C# can be used to write concise functional code. If you’d like to try this out, have a go at aliasing a list of Persons and writing a function that iterates over them and returns the total age of everyone (this is a classic example of a functional fold operation!)
I’m trying a more lean, agile approach to blogging because the tax on adding links and sources with my limited free time is too great and I don’t want to stop completely. If you want me to expand on anything in this post feel free to leave a comment or drop me a line and I’ll try my best.