DDD East Anglia 2018 In Review
This past Saturday, 22nd September 2018, the fifth DDD East Anglia event took place in Cambridge at the West Road Concert Hall Building.
This is the same location as the event was in last year and it's been here now for a few years, and with four separate sessions to choose from, this year's agenda was as good as ever.
Unlike in previous year's, I'd left things a little late to book myself into a nearby hotel and so made the brave decision to drive down to Cambridge from Liverpool on the morning of the event and to drive back again afterwards! That's an almost 7 hour round trip, and required me to set off at around 5:30am but it turned out to be well worth the journey.
I arrived at the venue shortly after the opening time, but still in time for breakfast. I quickly signed myself in at the registration desk and made my way to the recital hall where the attendees were gathering and breakfast was being served.
Being a vegetarian, I couldn't avail myself of the bacon butties, but did enjoy a couple of the delightful Danish pastries along with a nice cup of coffee. Given my slightly late arrival, I only had enough time to quickly consume my pastries and have a few slurps of coffee before the organisers entered the room and told us it was time to make our way to the relevant rooms for the first session of the day.
I grabbed the remains of my coffee and headed off towards the main building again to make my way up the stairs to the room for Track 2 where my first session of the day was Chester Burbidge's Hunting Typosquatters With F#.
Chester starts by defining "typo-squatting". This when a person creates a package in a popular package repository (i.e. NPM, Nuget, Maven, Pip etc.) that is very similarly named to another existing package. This is often done for nefarious purposes, such as getting malware onto an unsuspecting developer's computer by tricking people into installing the malware-laden package thinking they're installing the legitimate one. This has been done with such package names as cross-env
(the legitimate package) and crossenv
(the malicious package) and nodemailer
(the legitimate package) and nodemailer-js
(the malicious package).
Chester tells us that NuGet, the official .NET package repository has 106k packages, Java has around 223k package, but NPM has around 580k packages! Chester suggests that this is primarily due to the fact that JavaScript doesn't really have a standard library like both .NET and Java do.
We look at the NPM package eco-system and JavaScript packages in general and can see that packages are used everywhere. Lots of them. Chester uses React as an example and shows how scaffolding a React application with the create-react-app
command will generate you an application that has literally 100's of package dependencies. Packages are big news in JavaScript and so it's easy to see how tricking someone into installing a "fake" package that appears to be a more legitimate one can be very beneficial for an "evil hacker"!
Next, Chester drills into the details of a famous NPM typo-squatting issue known as the "Cross-Env Attack". This involved the attacker publishing a malicious package called crossenv
which masqueraded as the legitimate package, cross-env
. This was a "successful" attack as the cross-env package is rarely used on its own - it's a package that helps with reading and writing environment variables - but is frequently added as a dependency to other packages. Many other legitimate package authors were tricked into including the malicious crossenv package as a dependency to their own package, rather than using the legitimate cross-env package. This meant that the malicious package was able to be installed and spread onto quite a number of machines very quickly and easily. The crossenv package author - someone named Hacktask - had many other nefarious packages in the NPM package repository, all named very similarly to existing legitimate packages.
So, how could we prevent this from happening? Chester talks about a proposal which was to use the Levenshtein distance algorithm for finding similar strings and use it against the various packages in the NPM repository. If a package had a name that was very clearly very close to another package, it's a candidate for further investigation and a potentially malicious package. Chester says he was interested to find a way to implement a solution using this approach, however, such a solution would need to calculate the Levenshtein distance for each package compared to all the other packages within the NPM repository. This would result in a huge number of combinations of pairs of package names that would need to be compared.
One way to reduce the complexity of the task is to use a Trie, also known a Digital Tree. A Trie (pronounced "Try") is a tree data structure that is a specialised form of binary tree.
Each node in the tree usually has a string for the key, and by following nodes from the root of the tree, we can quickly determine (or specifically, short-circuit) the search for letters within a word when no further matches are likely to be found. Trie data structures are what enables Google's search auto-completion algorithm to work so quickly and efficiently. Chester says he'll use a Trie along with multiple copies of concurrent actors to perform the processing and, ultimately, write all the results to a CSV file.
Due to this approach, Chester says that F# was a good choice of language to implement the solution. We gently ease our way into F# by looking at a simple implementation of the classic FizzBuzz problem, firstly using C# and then a more terse, but equally readable version in F#.
string FizzBuzz(int n)
{
if(n % 15 == 0) {
return "FizzBuzz";
} else if (n % 3 == 0) {
return "Fizz";
} else if (n % 5 == 0) {
return "Buzz";
} else {
return n.ToString();
}
}
let fizzbuzz n =
match n with
| x when x % 15 = 0 -> "FizzBuzz"
| x when x % 3 = 0 -> "Fizz"
| x when x % 5 = 0 -> "Buzz"
| x -> "FizzBuzz"
We proceed with a whirlwind tour through the F# language. Chester says that F# is a great language as there's far less "boilerplate" code than C# and there's a clarity and conciseness that's often lacking from other languages. We also learn that F# is great for domain modelling. F# has Sum types and Units Of Measure types meaning that it's far easier to encode domain semantics inside of our F# code. This also helps to reduce bugs that could otherwise occur due to attempting to work with variables that represent different domain concepts.
We look at F#'s Type Providers and Chester shows us a quick demo of writing some F# code to leverage a regular expression based type provider and create a more specialized provider for extracting specific parts (the region) from UK postcode strings. We also look at a CsvProvider
type provider which Chester uses to parse the spending data from Devon County Council (available on GitHub!)
Finally, Chester talks about the code that he wrote to solve the typo-squatting problem and shares the results of his work. His results aggregate the similarly named packages by author, and when looking for the author of the malicious crossenv
package, Hacktask, we can see that he's ranked in 476th position in terms of having many packages with names similar to other packages. Ultimately, Chester concludes that using the Levenshtein distance algorithm to compute the similarity of package names isn't a great solution. The reason for this is that there's too much "noise" in the data and the algorithm produces far too many false positives. There are also simply too many legitimate packages which do have similar, albeit different, names. We're left to ponder what might be a better solution to the problem!
The slides and code for Chester's session are available online.
After Chester's session, it was time for the first coffee break. After pouring myself another cup of coffee, I had some time to look around the recital room at some of the sponsor's stands. As well as the stands of the sponsors, there was a couple of large tables all set up with an array of some old skool computers, kindly brought to the event by the Centre for Computing History in Cambridge. Seeing many of the computers that I'd owned and grown up with from my childhood certainly did bring back some fond memories.
(Image courtesy of @rammesses)
After the brief coffee break, it was time for the next session. I was in the same room as previously, so headed back into the main building and up the stairs to take my seat for Simon Painter's Functional C#.
Simon starts by taking a look at the history of functional programming. We start with the "who" of functional programming. We look at Alonzo Church, who invented Lambda Calculus and together with Alan Turing provided us with the Church-Turing Thesis, Haskell Curry who has many things named after him (at least two programming languages and gives his name to the concept of mathematical currying) and John McCarthy, who invented LISP.
We examine the "what" of functional programming? What is functional programming not?
Well, it's not new - it's been around since the 1950's. It's not a language and it's not imperative or object-oriented. It isn't the solution to all of your problems and it isn't difficult. So what is functional programming? Immutability, higher-order functions, referential transparency, recursive, and stateless. Immutability provides safety of state changes, higher-order functions allow us to compose more complex functions from smaller ones and referential transparency allows us to replace an entire function and it's parameters with the function's return value.
Simon talks about some modern programming languages and as well as the usual functional suspects, Haskell, Lisp, F# and others, languages such as JavaScript and C# are largely functional too these days.
When would you use functional programming? It's not purely just for maths and financial institutions, but tasks such as data processing, building concurrent systems and building high-criticality systems will all benefit greatly from functional programs. This is primarily due to the lack of state and immutability. So, what is functional programming not so good at? Well, pretty much anything that has to handle lots of state changes. A primary example of this is User Interfaces. Other examples are external interactions (calling out to external API's etc.) and IO (input/output) operations. All of these are very mutable and stateful.
We consider why we would use functional programming. After all, non-functional languages can do all of the things that functional programs might help with. But functional programming frequently offers the benefit of being concise, readable, and extremely testable. More than anything, though, Simon says that functional programming can be more fun!
We start by taking a look at LINQ in C#. Simon tells us that if you're already quite familiar with LINQ, you're already doing functional programming. All of the LINQ extension methods, such as .Where()
, .Select()
that take lambda functions are themselves higher-order functions.
Next, Simon shows us how we can combine a number of delegate functions or lambda's together. This is a great example of how we might implement a rules engine for some purpose, for example, validation.
private readonly Func<int, bool> greaterThan100 = x => x > 100;
private readonly Func<int, bool> divisibleBy10 = x => x % 10 == 0;
private readonly Func<int, bool> lessThan10000 = x => x < 10000;
public bool Validate(int input) =>
new[]
{
greaterThan100,
divisibleBy10,
lessThan10000
}.All(x => x(input));
We can also use the function combination to select a specific function that might apply in a given scenario and Simon shares some example code that selects an employee's tax band and performs the relevant calculation according to the employee's salary.
public double ApplyTaxToSalry(double salary) =>
new (Func<double, bool> condition, Func<double, double> calculation)[]
{
(x => x <= 11850, x => x),
(x => x <= 46350, x => x * 0.8),
(x => x <= 150000, x => x * 0.6),
(x => x > 150000, x => x * 0.55),
}
.First(x => x.condition(salary))
.calculation(salary);
Simon says that Object-Oriented code is very prescriptive. It's a sequence of steps to perform some task. He likens functional programming to being like an onion. The outer layer provides the overall description of what we what to compute, whilst diving into each of the inner layers gives the smaller, component part functions that operate on the "outside" data.
Next, we look at currying. C# doesn't have true currying, but it's possible to fake it to a certain extent.
public Func<int, int> CurriedAdd(int a) => b => a + b;
public void CallCurriedAdd()
{
var add20 = CurriedAdd(20);
var answer = add20(30); // 50
}
We then look at immutability. Again, C# doesn't really have true immutability but it can again be faked to a certain extent, so creating a class with read-only properties that must have all it's values set during construction will give you immutability of the values within the object:
public class Customer
{
public string FirstName { get; private set;}
public string LastName { get; private set;}
public DateTime DateOfBirth { get; private set;}
public Customer(string firstName, string lastName, DateTime dateOfBirth)
{
FirstName = firstName;
LastName = lastName;
DateOfBirth = dateOfBirth;
}
}
But, of course, if the Customer
class is a property of another class:
public class Organisation
{
public Customer Customer;
// ... other properties
}
Then we've got no guarantee that the Customer
property on the Organisation
object isn't going to be null, or suddenly re-assigned to some other instance of Customer.
Simon then moves on to look at pattern recognition in C#. As of C# 7.0, we've got better pattern recognition than we've previously had, but it's still not at the levels of F# just yet. Improvements in this area in forthcoming versions of C# will help. We used to have to use a call to typeof()
in order to match on types, but we can now do case Customer c when c.CreditLimit > 10000
as part of a switch statement.
We look at method chaining, and this is where our functional journey gets really interesting. Simon shows us an implementation of a class called Identity<T>
which will be used to "amplify" disparate types into the same "parent" type, allowing us to chain multiple amplified types together (since they're now the same type).
public class Identity<T>
{
public T Value {get;}
public Identity(T val)
{
Value = val;
}
public static implicit operator Identity<T>(T @this) => new Identity<T>(@this);
public static implicit operator T(Identity<T> @this) => @this.Value;
}
The Identity class accepts a generic type parameter and wraps an underlying type. With the use of some extension methods to this type, we can provide the behaviour we need to be able to amplify our own types to become Identity types:
public static class Extensions
{
public static Identity<T> ToIdentity<T>(this T @this)
{
return new Identity<T>(@this);
}
public static Identity<TToType> Bind<TFromType, TToType>(this Identity<TFromType> @this,
Func<TFromType, TToType> f) => f(@this.Value).ToIdentity();
}
The more functionally minded amongst you will recognise this pattern. The Identity class is basically a Monad. Using the Identity class and its extension methods, we can then write some of our own functions that can be composed together into a larger function to provide some behaviour. One example of this is having some functions that perform basic mathematical operations (add, subtract, multiply, divide) and compose those functions into an algorithm for temperature conversion. Simon shows us some sample code that performs such a task:
private readonly Func<decimal, Func<decimal, decimal>> _add = x => y => x + y;
private readonly Func<decimal, Func<decimal, decimal>> _subtract = x => y => y - x;
private readonly Func<decimal, Func<decimal, decimal>> _multiply = x => y => x * y;
private readonly Func<decimal, Func<decimal, decimal>> _divide = x => y => y / x;
public decimal FahrenheitToCelcius(decimal fahrenheitValue) =>
fahrenheitValue.ToIdentity()
.Bind(_subtract(32))
.Bind(_multiply(5))
.Bind(_divide(9))
.Bind(x => Math.Round(x, 2));
public decimal CelcuisToFahrenheit(decimal celciusValue) =>
celciusValue.ToIdentity()
.Bind(_multiply(9))
.Bind(_divide(5))
.Bind(_add(32))
.Bind(x => Math.Round(x, 2));
Keeping on the theme of Monads, we look at the Maybe monad. It's a very popular type of monad and allows us, in C# especially, to use it as a way to reduce the possibility of null references. F# has it's own built in Maybe monad known as the option
type, and F# uses it like so:
type Option<'a> =
| Some of 'a
| None
This is a generic type that allows a
to either have a value of a
(some of a
) or None. It's important to note that None is not the same as null here. It's still a valid type that exists (as opposed to not existing in the case of null) but just means that we don't really have a value for a
. As a result, a
is never null and so checks for null references are no longer required.
For C#, we have to write our own Maybe
After Simon's session, it was time for another coffee break. Since I'd had a long journey that morning, I was fairly tired and needed all the caffeine I could get, so I quickly helped myself to another cup of coffee in the break room. After the liquid refreshment, it was time for the final session before lunch and this one was to be Ian Russell's Domain Modelling In F#
Ian starts his session with an overview of Domain-Driven Design (DDD) before looking at how we can model our domain using F#. He mentions the "blue book" which is the informal name for Eric Evans' Domain-Driven Design book, the original and arguably most seminal work on domain driven design.
Ian asks who's read the "blue book" and a lot of the attendees have. Ian then asks who's only read the last third of the book. He says that the first 2/3rds are the patterns, practices, diagrams etc. The last 1/3rd of the book is where the the real essence of DDD is. Ian suggests that, in order to get the most out of the book, you should read the last 1/3rd first, then start at the beginning and read the whole thing again.
We look at Strategic DDD versus Tactical DDD. Strategic DDD combines a number of fundamental elements of domain driven design. It's our ubiquitous language. This is the language of the domain and the business which both business and developers should agree on and adopt. It's the domain bounded contexts, which consist of the core domain - the thing that is the business's unique proposition and the thing that makes the business money - as well as supportive domains and generic domains. It's the context map which defines how the various domain bounded contexts will communicate with each other, possibly including such elements as anti-corruption layers and a shared kernel.
Ian tells us that strategic DDD is the most important aspect. If we don't get our strategic DDD right, the tactical DDD that follows won't be correct and won't work correctly. The "blue book" gives us the definitions of strategic DDD but Ian says that it never really tells you exactly how something should be done. Ian gives the example of how the book says we should "create bounded contexts" but never really tells us how. However, Ian says there's a technique to help with that, Event Storming.
Event Storming was devised by Alberto Brandolini and is a technique used by both developers and business representatives to drive out the events, entities and communications that happen throughout the business domain. Event Storming is a fairly simple technique to learn and understand and the results of applying it can be quite profound. There's numerous references available to help get started.
After strategic DDD comes tactical DDD. Tactical DDD is all about the creation of the domain artefacts using aggregates, entities, value objects, repositories and services. These are the things that will constitute the actual code that you'll start to write in order to build the software model of your domain. Aggregates represent a transactional boundary of some object within the domain (e.g. A sales order may be made up of one order object with one or more order line item objects but the combination of order and order line items forms an order aggregate as it must all change together at the same time). Entities are the individual parts of our aggregates that have an "identity" within the domain and value objects are those parts of the domain that are identified by value alone.
From here, Ian moves on to show us how we can take all of this DDD knowledge and apply it using the F# language. Ian says that F# has an algebraic type system which helps with DDD in many ways. It helps us to codify concepts such as missing data, simple value types and choices. Being able to efficiently and accurately represent these concepts in code helps ensure we can make illegal states unrepresentable within the software model of our domain.
Ian shows us some F# code. We start by looking at a simple domain model to represent a Contact:
type PersonalInfo = {
FirstName: string
MiddleInitial: string option
LastName: string
}
type EmailAddressInfo = {
EmailAddress: string
IsEmailAddressValid: bool
}
type Contact = {
PersonalInfo: PersonalInfo
EmailAddressInfo: EmailAddressInfo
}
We look at various ways in which we can improve the model. Currently, the AddressInfo
type can have the Address
changed without having the IsAddressValid
flag suitably altered at the same time, so an invalid address could be substituted for a valid one and the valid flag could still show as valid. In DDD parlance, having these two things dependent upon each other in this way represents an "invariant". F# allows us to easily change the EmailAddressInfo to better support this by using a discriminated union:
type UnverifiedData = string
type VerifiedData = string * DateOfValidation
type EmailAddressInfo =
| UnverifiedState of UnverifiedData
| VerifiedState of VerifiedData
Along with additional code that creates email addresses, we can now express our email address in terms of it being verified or unverified and there's no risk of us being able to set one aspect of it (the email itself) without the other (its date of verification). We can go further too, as email addresses aren't merely strings. We know something about email addresses and know that there's a semantic meaning to them. We can therefore encode this within our types:
type EmailAddress = string
let create (s:string) =
if System.Text.RegularExpressions.Regex.IsMatch(s,@"^\S+@\S+\.\S+$")
then Some (EmailAddress s)
else None
let email = EmailAddress.create "someone@example.com"
Once we have some code that enforces how we can create email addresses and an option (discriminated union) to represent both verified and unverified states, we can use pattern matching to determine what state the email address is in and how to process it.
Finally, Ian looks at F# functions. He says that in order to compose our functions, we'd need to return the same type from each function. This can be quite restrictive, but we can get around this by using F#'s built-in Result
type Result<'T,'TError> =
| Ok of ResultValue:'T
| Error of ErrorValue:'TError
and it can be used similarly to the following:
let test() =
let Contact = { Name = "Phillip"; Email = "phillip@contoso.biz" }
let ContactResult = validateRequest (Ok Contact)
match ContactResult with
| Ok Contact -> printfn "My request was valid!"
| Error e -> printfn "Error: %s" e
This is another discriminated union that allows us to specify that a function returns either some kind of Ok result, or some kind of Error result. The Ok/Error effectively "wraps" the underlying function return type, which is another example of a monad, and allows the implementation of railway-oriented programming.
After Ian's talk, it was time for lunch. Lunch was served back in the recital room and consisted of the usual "brown bag" affair of sandwich, crisps, chocolate, drink and fruit. There was a very good selection for all elements of the lunch so it was easy for everyone to find something they liked.
After finding a quiet spot which had a power socket so I could squeeze some extra charge into my phone (I'd used quite a fair bit of it whilst sat nav'ing my way to the event), I settled down to eat my lunch.
As is usual for these events, there were a number of grok talks (aka lightning talks) taking place over the lunch period. Unfortunately, on this occasion, I didn't manage to take in any of the grok talks.
After lunch, it was time for the first of the two afternoon sessions. Continuing on a functional theme, this first session for me was to be Anthony Brown's SAFE Stack: Functional web programming in .Net
Anthony starts his session with a very brief introduction to F# and functional programming in general. He says that there's a common misconception that F# is exclusively for use within the domains of maths and finance, however, F# can be used for much more general purpose tasks.
Anthony introduces us to the SAFE Stack. The SAFE Stack is defined as "an end-to-end, functional-first stack for cloud-ready web development that emphasizes type-safe programming". Essentially, the SAFE Stack consists of four key elements that allow the development of complete web solutions. The elements are a server-side framework known as Saturn, tools to assist with hosting and deployment to the Azure cloud, an F# to JavaScript transpiler called Fable and finally a model-view-update based set of abstractions for building User Interfaces known as Elmish. The first letters of each of these elements spells out SAFE and forms the SAFE Stack. The SAFE Stack came about due to many different F# practitioners using the same group of technologies for building functional web applications. Some members of the F# community decided to get together and ratify the group of technologies into a cohesive whole in order to simplify and streamline adoption of the stack, which is now considered best practice.
We're told that the SAFE Stack is exclusively for .NET Core, so if you're still using the full .NET Framework, you won't be able to benefit from the SAFE Stack.
Anthony introduces Saturn. He says it sits on top of another technology called Giraffe, which itself sits on top of the ASP.NET Core framework and uses ASP.NET Core's Kestrel for hosting. Many of the ASP.NET Core concepts therefore "bleed through" the layers and are exposed within Saturn, making the technology fairly familiar to anyone who has used the ASP.NET Core framework previously. What Saturn brings is to abstract away a lot of the boilerplate code that's normally required by ASP.NET Core, meaning a simple View to output the text "Hello from Saturn" can be written like this:
namespace Hello
open Giraffe.GiraffeViewEngine
open Saturn
module Views =
let index =
div [] [
h2 [] [rawText "Hello from Saturn!"]
]
Saturn has the concept of something called "scopes". This is how a URI path is linked to an F# function and is similar to ASP.NET's Routes. What is great about Saturn's Scopes are that they are an F# function, and like other F# functions, they are completely composable. Saturn also has the concept of Controllers, similar to ASP.NET itself, and like Scopes, they're completely composable too. Saturn is very lightweight and can run anywhere a vanilla ASP.NET Core application can run, even inside an AWS Lambda or Azure Function.
Next, we move on to look at Fable. Anthony tells us that Fable doesn't attempt to rewrite the existing common JavaScript tooling, but works with it. It integrates with WebPack, and also leverages Babel in order to generate its JavaScript. Next we look at Elmish, which works very closely with Fable to allows you to build on top of a set of abstractions using the model-view-update style of user interface state management originally introduced by the Elm language.
Anthony talks about how UI programming is often done "wrong". Mutability within the user interface is a liability and the handling of async operations brings with it a lot of complexity. There's a lack of true composability of components and there's also an inherent difficulty in manipulating the UI on a single, main thread. With Fable and Elmish, the SAFE Stack beings functional concepts to the user interface and the model-view-update pattern helps greatly. Start with an initial state and render it. Every interaction with the UI creates a command which in turn generates an event and generates a whole new state. This new state is fed back through the loop for rendering.
Anthony tells us how Elmish isn't just for the web, but has general purpose applicability for other user interfaces, such as Xamarin or WPF.
We then look at how the SAFE Stack includes specific help for deploying a solution to the Azure cloud and has deep integration with many existing Azure Platform Services, such as various Azure Storage services.
Anthony summarises. What does the SAFE Stack add? It provides a terminology for shared experiences using the shared technologies of Saturn, Azure, Fable and Elmish. Much of the tooling for creating SAFE Stack applications is now automated and there's better end-to-end and overall documentation. Due to the elements of the stack now being brought together, there's better samples and training available on the internet. Also, the SAFE Stack has commercial backing. Anthony tells us that there's at least three different consultancies backing the SAFE Stack.
There's even dotnet tooling to help get you started and up-and-running quickly and easily. You can type dotnet new safe
and have a complete sample SAFE Stack based application scaffolded for you. The template includes numerous options for configuring many different elements of the complete stack.
Anthony also mentions some existing sample applications that are open source and available on the internet that you can examine and extend. There's the SAFE Dojo and the SAFE Bookstore and these will also help you get started with the SAFE Stack.
After Anthony's session, it was time for another coffee break. As well as enjoying the coffee, there was plenty of crisps and chocolate bars left over from lunch, so any attendees that were still feeling peckish could help themselves to more food. The coffee break was soon over and it was time to head to our respective rooms for the final session of the day. This one was to be Ashic Mahtab's Diving into Functional Programming - Beyond the Basics.
Ashic starts by telling us that functional programming is all about separating the description of things from the execution of things. Functional programming helps tremendously with testing. If your programming language allows you to make something effectively "impossible" to do, then you'll never need to test against that impossible condition.
Ashic says he's going to use Scala throughout his session and we start by looking into something called Semigroups. Semigroups are types that have a combiner function. The formal mathematical definition is that they are algebraic structures consisting of a set together with an associative binary operation. They also have to be transitive, which means that combining a
and b
is the same as combining b
and a
. Using some basic C#, this could be a function whose signature looks like:
T Combine(T first, T second)
Semigroups are useful when used in calculation functions that need to be agnostic to the types of things that they're working with. Ashic uses the example of "folding" a list of objects ("folding" is a term used to describe what is effectively iterating over some data structure and performing some operation on each element). Instead of having to write code such as:
(a,b) => a.amount + b.amount
which means hard-coding in the fact that to "combine" a
and b
, we need to use the amount
property and add the two values together, we can defer the actual combination function to that which is defined on the semigroup:
object Price {
implicit val sumSemigroup = new Semigroup[Price] {
override def combine(x: Price, y: Price): Price = Price(x.amount + y.amount)
}
}
this then allows us to write such code as:
list.foldLeft(T)(_ |+| _)
whereby the |+|
symbol basically says, "use the semigroup's combine function to perform the combination". Ashic states that, although things like Semigroups are sometimes considered "advanced" concepts in functional programming, it's really not all that complicated when you cut through the jargon and understand, in simple terms, what a semigroup is and how it can be used.
Ashic then proceeds from Semigroups and starts to look at Monoids. Although it's another scary piece of terminology for the functionally uninitiated, they're really just Semigroups but with an initial, seed value.
Ashic says that Semigroups and Monoids aren't really types themselves but categories of types. Any actual type that has the "properties" of a Semigroup - i.e. a way to combine two of something and return one of the same something - is of the category of Semigroup. The same applies to Monoids. Any actual type that has the "properties" of a Monoid - i.e. a way to combine two of something and return one of the same something with a mandatory initial value - is of the category of Monoid.
Next we look at Functors. Ashic shows us a rather cryptic expression indicating what a Functor is:
(A => B) => (F[A] => F[B])
this is the same as:
F[A] => (A => B) => F[B]
And again, for the functionally uninitiated, this could be somewhat confusing. So we examine this in plain English terms.
Given a function that takes an
A
and returns aB
, the Functor allows taking anF
ofA
and returns anF
ofB
, whereF
in both cases is a generic type that takes a single type parameter.
So, a simple way to think about this in C#, using a well known, built-in generic type of a generic List would be that a Functor would accept something like a List<int>
and return a List<string>
. Other C# types that could represent the F
in the statement above are Nullable<T>
, IEnumerable<T>
or any of the other generic types accepting a single type parameter. Another way of looking at the expression above is that (A => B)
is equivalent to B myFunction(A)
and therefore F[A] => F[B]
is equivalent to List<B> myFunction(List<A>)
.
In examining Functors in a little more depth, we learn that we can supply the function to the Functor that works on the underlying type that is effectively "wrapped" by the generic type. This means that a functor that would take a List<int>
and return a List<string>
could accept a function that takes an int
and returns a string
. The functor will "lift" each element of the List, using the supplied function to apply to each underlying type of each list element - in other words, the functor does the iteration over the list for you and uses your function to operate on each element of the list.
Ashic shows us some Scala code that demonstrates a Functor. First, the simple function that will operate on an int
is defined:
def myFunction(x:Int): Int = x+5
then, we can create the functor that will work on a List
of int's
, using the myFunction
to perform an operation on each list element:
val myFunctor = Functor[List].map(List(1,2,3,4)) (myFunction)
We now have myFunction
that works on a single int
and myFunctor
that works with lists of ints. In the code above, we've supplied a List of the numbers 1,2,3 & 4. Since we're deep into functional programming here, it should come as no surprise that functors are composable. We can create new functors by combining two other functors:
val listOpt = Functor[Option] compose Functor[List]
val flo = listOpt.lift(myFunc)
Above, we're combining a Functor that works with lists and a functor that works with Option
types (Options are the Maybe monad we examined earlier in the day during Simon Painter's session) and by combining the two, we're left with a Functor that works with Option[List]
's! We'd call this like so:
flo(Some(List(1,2,3,4)))
Finally, we get to Monads. Ashic proceeds to explain the sometimes mythical functional Monad. Luckily, we have the benefit of all of the previous functional concepts that have come before, so Ashic tells us that:
Monads are Functors that have a "pure" function (also known as an "identity" function or a "unit" function) and a "bind" function. The "pure" function takes a
A
and returns aF
ofA
(i.e. takes a type and returns a container of that type). The "bind" function takes anF
ofA
and uses theA
to pass to a function that takes anA
and returns anF
ofB
.
In order words, the "pure" function has this expression:
A => F[A]
which is to say that you take a type and return a container of that type. And the "bind" function has this expression:
F[A] => (A => F[B]) => F[B]
which is to say that we take a contained type, then using the "inner" type, we pass that to the function that returns a container of another type.
Ashic shows us some Scala code that demonstrates a simple monad:
object Catalog{
private val books =
Map(
10 -> Book(10, "book-10"),
20 -> Book(20, "book-20"),
30 -> Book(30, "book-30"),
40 -> Book(40, "book-40")
)
def getBook(id:Int) : Option[Book] =
books.get(id)
}
and to give an example in C#, we can see a simple Maybe monad, aka an Option type:
public class Maybe<T> where T : class
{
private readonly T value;
public Maybe(T someValue)
{
if (someValue == null)
throw new ArgumentNullException(nameof(someValue));
this.value = someValue;
}
private Maybe()
{
}
public Maybe<TO> Bind<TO>(Func<T, Maybe<TO>> func) where TO : class
{
return value != null ? func(value) : Maybe<TO>.None();
}
public static Maybe<T> None() => new Maybe<T>();
}
public static class MaybeExtensions
{
public static Maybe<T> Return<T>(this T value) where T : class
{
return value != null ? new Maybe<T>(value) : Maybe<T>.None();
}
}
Ashic concludes his session by telling us that everything we've looked at throughout the session are type classes. They are essentially a way of doing polymorphism. In C#, we use an inheritance model to perform the same function, however, the inheritance model has it's own drawbacks as it's easy to add new types in that we can add a new concrete class that implements some specific interface, but it's hard to add new behaviour. Adding an additional method to an existing interface that has already been implemented by other classes will "break" those other classes (although there's a forthcoming feature of C# 8 called "default interface methods" which will go a long way to fixing this issue).
Ashic's slides and demo code for his talk are available online.
After Ashic's session was over, it was time for the final wrap-up and prize giving session. We all congregated in one of the large rooms in the main building and once all of the attendees were there, the organisers thanked the venue and sponsors for helping to make the event a reality as well as thanking the attendees for attending.
Each sponsor was given the opportunity to say a few words to the assembled crowd of attendees and afterwards there was a prize draw based upon randomly drawing from the session feedback forms. I wasn't lucky enough to win a prize this time around, however, I'd had a brilliant day listening to some great talks and meeting with some old and new friends. The organisers had mentioned in their wrap-up speech that they'd already been discussing the event for next year, so here's looking forward to another brilliant DDD East Anglia in 2019!